From 24da8408bd80db26189d193e43d1dfc75a0d9799 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 18 Jun 2021 15:13:21 -0300 Subject: [PATCH 01/25] fix: TokenBar balance crashes when opened if has no transactions for HTR (#207) --- src/components/TokenBar.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/TokenBar.js b/src/components/TokenBar.js index f46fe684..ae71ef77 100644 --- a/src/components/TokenBar.js +++ b/src/components/TokenBar.js @@ -105,7 +105,11 @@ class TokenBar extends React.Component { */ getTokenBalance = (uid) => { const balance = this.props.tokensBalance[uid]; - const total = balance.available + balance.locked; + let total = 0; + if (balance) { + // If we don't have any transaction for the token, balance will be undefined + total = balance.available + balance.locked; + } return hathorLib.helpers.prettyValue(total); } From c958b39195c5c3d6cb9d1274bea045568bceb01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Thu, 29 Jul 2021 13:55:43 -0300 Subject: [PATCH 02/25] feat: ledger update --- public/ledger.js | 52 +++++++++++++++++++++++++++++++++++---------- src/utils/ledger.js | 29 +++++++++++++++++++------ 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/public/ledger.js b/public/ledger.js index 371e1cf7..16c73c13 100644 --- a/public/ledger.js +++ b/public/ledger.js @@ -21,9 +21,20 @@ class Ledger { switch (e.statusCode) { case 0x6985: return {status: e.statusCode, message: 'Request denied by user on Ledger'}; - case 0x6b00: // SW_DEVELOPER_ERROR - case 0x6b01: // SW_INVALID_PARAM - case 0x6b02: // SW_IMPROPER_INIT + case 0x6a86: // WRONG_P1P2 + case 0x6a87: // SW_WRONG_DATA_LENGTH + case 0x6d00: // SW_INS_NOT_SUPPORTED + case 0x6e00: // SW_CLA_NOT_SUPPORTED + case 0xb000: // SW_WRONG_RESPONSE_LENGTH + case 0xb001: // SW_DISPLAY_BIP32_PATH_FAIL + case 0xb002: // SW_DISPLAY_ADDRESS_FAIL + case 0xb003: // SW_DISPLAY_AMOUNT_FAIL + case 0xb004: // SW_WRONG_TX_LENGTH + case 0xb005: // SW_TX_PARSING_FAIL + case 0xb006: // SW_TX_HASH_FAIL + case 0xb007: // SW_BAD_STATE + case 0xb008: // SW_SIGNATURE_FAIL + case 0xb009: // SW_INVALID_TX default: return {status: e.statusCode, message: 'Error communicating with Ledger'}; } @@ -31,6 +42,24 @@ class Ledger { return e; } + static formatPathData(index) { + pathArr = [ + 44 + 0x80000000, // 44' + 280 + 0x80000000, // 280' + 0 + 0x80000000, // 0' + 0, // 0 + ]; + if (index !== undefined) { + pathArr.push(index); + } + const buffer = Buffer.alloc(21); + buffer[0] = 5; + pathArr.forEach((element, index) => { + buffer.writeUInt32BE(element, 1 + 4 * index); + }); + return buffer; + } + constructor() { // Electron main window to send command to renderer process this.mainWindow = null; @@ -74,10 +103,10 @@ class Ledger { // Ledger commands this.commands = { - 'VERSION': 0x01, - 'ADDRESS': 0x02, - 'SEND_TX': 0x04, - 'PUBLIC_KEY_DATA': 0x10, + 'VERSION': 0x03, + 'ADDRESS': 0x04, + 'PUBLIC_KEY_DATA': 0x05, + 'SEND_TX': 0x06, } // Queue of commands to send to ledger so we don't send them in paralel @@ -197,7 +226,7 @@ class Ledger { getPublicKeyData = async () => { try { const transport = await this.getTransport(); - return await this.sendToLedgerOrQueue(transport, this.commands.PUBLIC_KEY_DATA, 0, 0); + return await this.sendToLedgerOrQueue(transport, this.commands.PUBLIC_KEY_DATA, 0, 0, Ledger.formatPathData()); } catch (e) { throw Ledger.parseLedgerError(e); } @@ -213,7 +242,7 @@ class Ledger { checkAddress = async (index) => { try { const transport = await this.getTransport(); - const result = await this.sendToLedgerOrQueue(transport, this.commands.ADDRESS, 0, 0, index); + const result = await this.sendToLedgerOrQueue(transport, this.commands.ADDRESS, 0, 0, Ledger.formatPathData(index)); return result; } catch (e) { throw Ledger.parseLedgerError(e); @@ -241,9 +270,10 @@ class Ledger { let offset = 0; try { const transport = await this.getTransport(); + let i=0; while (offset < data.length) { const toSend = data.slice(offset, offset + maxLedgerBuffer); - await this.sendToLedgerOrQueue(transport, this.commands.SEND_TX, 0, 0, toSend); + await this.sendToLedgerOrQueue(transport, this.commands.SEND_TX, 0, i++, toSend); offset += maxLedgerBuffer; } } catch (e) { @@ -273,7 +303,7 @@ class Ledger { try { const transport = await this.getTransport(); for (const index of indexes) { - const value = await this.sendToLedgerOrQueue(transport, this.commands.SEND_TX, 1, 0, index); + const value = await this.sendToLedgerOrQueue(transport, this.commands.SEND_TX, 1, 0, Ledger.formatPathData(index)); // we remove the last 2 bytes as they're just control bytes from ledger to say if // the communication was successful or not values.push(value.slice(0, -2)); diff --git a/src/utils/ledger.js b/src/utils/ledger.js index f6adda16..1fea9a1d 100644 --- a/src/utils/ledger.js +++ b/src/utils/ledger.js @@ -16,6 +16,24 @@ import hathorLib from '@hathor/wallet-lib'; */ export class LedgerError extends Error {} +const formatPathData = (index) => { + pathArr = [ + 44 + 0x80000000, // 44' + 280 + 0x80000000, // 280' + 0 + 0x80000000, // 0' + 0, // 0 + ]; + if (index !== undefined) { + pathArr.push(index); + } + const buffer = Buffer.alloc(21); + buffer[0] = 5; + pathArr.forEach((element, index) => { + buffer.writeUInt32BE(element, 1 + 4 * index); + }); + return buffer; +}; + let ledger = null; if (IPC_RENDERER) { @@ -71,12 +89,10 @@ if (IPC_RENDERER) { // first assemble data to be sent const arr = []; if (changeIndex > -1) { - // With change output - arr.push(hathorLib.transaction.intToBytes(1, 1)); - // Change index of array - arr.push(hathorLib.transaction.intToBytes(changeIndex, 1)); + // encode the bit indicating existence of change and change index on first byte + arr.push(hathorLib.transaction.intToBytes(0x80 | changeIndex, 1)) // Change key index of the address - arr.push(hathorLib.transaction.intToBytes(changeKeyIndex, 4)); + arr.push(formatPathData(changeKeyIndex)); } else { // no change output arr.push(hathorLib.transaction.intToBytes(0, 1)); @@ -102,8 +118,7 @@ if (IPC_RENDERER) { const arr = []; for (const input of data.inputs) { const index = keys[input.address].index; - const indexBytes = hathorLib.transaction.intToBytes(index, 4); - arr.push(indexBytes); + arr.push(formatPathData(index)); } IPC_RENDERER.send("ledger:getSignatures", arr); }, From 88e7d99e1d9b896de0c27398e2e28fcacefaa299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Fri, 30 Jul 2021 01:54:16 -0300 Subject: [PATCH 03/25] feat: startWallet with xpub --- public/ledger.js | 25 ++++++++++++++++++++++--- src/constants.js | 2 +- src/screens/WalletType.js | 5 ++--- src/utils/wallet.js | 6 ++++-- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/public/ledger.js b/public/ledger.js index 16c73c13..14d49798 100644 --- a/public/ledger.js +++ b/public/ledger.js @@ -17,6 +17,22 @@ const ledgerCLA = 0xe0; class Ledger { static parseLedgerError(e) { + const errorMap = { + 0x6a86: "WRONG_P1P2", + 0x6a87: "SW_WRONG_DATA_LENGTH", + 0x6d00: "SW_INS_NOT_SUPPORTED", + 0x6e00: "SW_CLA_NOT_SUPPORTED", + 0xb000: "SW_WRONG_RESPONSE_LENGTH", + 0xb001: "SW_DISPLAY_BIP32_PATH_FAIL", + 0xb002: "SW_DISPLAY_ADDRESS_FAIL", + 0xb003: "SW_DISPLAY_AMOUNT_FAIL", + 0xb004: "SW_WRONG_TX_LENGTH", + 0xb005: "SW_TX_PARSING_FAIL", + 0xb006: "SW_TX_HASH_FAIL", + 0xb007: "SW_BAD_STATE", + 0xb008: "SW_SIGNATURE_FAIL", + 0xb009: "SW_INVALID_TX", + }; if (e.name && e.name === 'TransportStatusError') { switch (e.statusCode) { case 0x6985: @@ -36,6 +52,7 @@ class Ledger { case 0xb008: // SW_SIGNATURE_FAIL case 0xb009: // SW_INVALID_TX default: + console.log("LedgerError: ", errorMap[e.statusCode] || 'UNKNOWN_ERROR'); return {status: e.statusCode, message: 'Error communicating with Ledger'}; } } @@ -43,17 +60,19 @@ class Ledger { } static formatPathData(index) { - pathArr = [ + const pathArr = [ 44 + 0x80000000, // 44' 280 + 0x80000000, // 280' 0 + 0x80000000, // 0' 0, // 0 ]; + let pathLen = 4; if (index !== undefined) { pathArr.push(index); + pathLen++; } - const buffer = Buffer.alloc(21); - buffer[0] = 5; + const buffer = Buffer.alloc(1+(4*pathLen)); + buffer[0] = pathLen; pathArr.forEach((element, index) => { buffer.writeUInt32BE(element, 1 + 4 * index); }); diff --git a/src/constants.js b/src/constants.js index a6fbe19c..3363de43 100644 --- a/src/constants.js +++ b/src/constants.js @@ -129,4 +129,4 @@ export const IPC_RENDERER = ipcRenderer; /** * Flag to hide/show elements after ledger integration is done */ -export const LEDGER_ENABLED = false; +export const LEDGER_ENABLED = true; diff --git a/src/screens/WalletType.js b/src/screens/WalletType.js index 265fbe5e..49ec12df 100644 --- a/src/screens/WalletType.js +++ b/src/screens/WalletType.js @@ -115,9 +115,8 @@ class WalletType extends React.Component { const chainCode = Buffer.from(data.slice(65, 97)); const fingerprint = Buffer.from(data.slice(97, 101)); const xpub = hathorLib.wallet.xpubFromData(compressedPubkey, chainCode, fingerprint); - // First we clean what can still be there of a last wallet - wallet.cleanWallet(); - wallet.startHardwareWallet(xpub); + + wallet.startWallet(null, '', null, '', this.props.history, false, xpub); hathorLib.wallet.markBackupAsDone(); this.props.history.push('/wallet/'); } else { diff --git a/src/utils/wallet.js b/src/utils/wallet.js index 60a08529..921d3eb2 100644 --- a/src/utils/wallet.js +++ b/src/utils/wallet.js @@ -114,7 +114,7 @@ const wallet = { * @memberof Wallet * @inner */ - async startWallet(words, passphrase, pin, password, routerHistory, fromXpriv = false) { + async startWallet(words, passphrase, pin, password, routerHistory, fromXpriv = false, xpub = null) { // Set loading addresses screen to show store.dispatch(loadingAddresses(true)); // When we start a wallet from the locked screen, we need to unlock it in the storage @@ -149,6 +149,7 @@ const wallet = { const walletConfig = { seed: words, xpriv, + xpub, store: STORE, passphrase, connection, @@ -237,6 +238,7 @@ const wallet = { store.dispatch(loadingAddresses(true)); const accessData = { xpubkey: xpub, + from_xpub: true, } const promise = oldWalletUtil.startWallet(accessData, true); promise.then(() => { @@ -279,7 +281,7 @@ const wallet = { const address = storage.getItem('wallet:address'); const lastSharedIndex = storage.getItem('wallet:lastSharedIndex'); - const lastGeneratedIndex = wallet.getLastGeneratedIndex(); + const lastGeneratedIndex = oldWalletUtil.getLastGeneratedIndex(); store.dispatch(historyUpdate({historyTransactions, allTokens, lastSharedIndex, lastSharedAddress: address, addressesFound: lastGeneratedIndex + 1, transactionsFound})); }, From 364313ef29c58d91bec76b78610192d018443ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Fri, 30 Jul 2021 08:32:11 -0300 Subject: [PATCH 04/25] feat: don't show all adresses for hardware wallets --- src/App.js | 3 +++ src/components/WalletAddress.js | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 0734c5ea..0887b160 100644 --- a/src/App.js +++ b/src/App.js @@ -183,6 +183,9 @@ const returnStartedRoute = (Component, props, rest) => { if (rest.loaded) { // When the wallet is opened, the path that is called is '/', which currenctly redirects to the Wallet component // in that case, if the wallet is not loaded but it's started, it should redirect to the signin/wallet type screen + if (hathorLib.wallet.isHardwareWallet()) { + return ; + } return ; } else { return ; diff --git a/src/components/WalletAddress.js b/src/components/WalletAddress.js index 7d85719c..845ed048 100644 --- a/src/components/WalletAddress.js +++ b/src/components/WalletAddress.js @@ -135,7 +135,9 @@ class WalletAddress extends React.Component { } - {t`See all addresses`} + {hathorLib.wallet.isSoftwareWallet() && // hide all addresses for hardware wallet + {t`See all addresses`} + } ); } From e3020cf3c1531da5d9915994f93d812b64f06491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Fri, 30 Jul 2021 13:13:34 -0300 Subject: [PATCH 05/25] fix: send correct address index to ledger --- public/ledger.js | 6 ++---- src/components/WalletAddress.js | 2 +- src/utils/ledger.js | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/public/ledger.js b/public/ledger.js index 14d49798..4338f583 100644 --- a/public/ledger.js +++ b/public/ledger.js @@ -66,13 +66,11 @@ class Ledger { 0 + 0x80000000, // 0' 0, // 0 ]; - let pathLen = 4; if (index !== undefined) { pathArr.push(index); - pathLen++; } - const buffer = Buffer.alloc(1+(4*pathLen)); - buffer[0] = pathLen; + const buffer = Buffer.alloc(1+(4*pathArr.length)); + buffer[0] = pathArr.length; pathArr.forEach((element, index) => { buffer.writeUInt32BE(element, 1 + 4 * index); }); diff --git a/src/components/WalletAddress.js b/src/components/WalletAddress.js index 845ed048..6b6ec603 100644 --- a/src/components/WalletAddress.js +++ b/src/components/WalletAddress.js @@ -81,7 +81,7 @@ class WalletAddress extends React.Component { if (hathorLib.wallet.isHardwareWallet()) { $('#ledgerAlert').modal('show'); - ledger.checkAddress(hathorLib.transaction.intToBytes(this.props.lastSharedIndex, 4)); + ledger.checkAddress(this.props.lastSharedIndex); } } diff --git a/src/utils/ledger.js b/src/utils/ledger.js index 1fea9a1d..93fc5e65 100644 --- a/src/utils/ledger.js +++ b/src/utils/ledger.js @@ -26,8 +26,8 @@ const formatPathData = (index) => { if (index !== undefined) { pathArr.push(index); } - const buffer = Buffer.alloc(21); - buffer[0] = 5; + const buffer = Buffer.alloc(1+4*pathArr.length); + buffer[0] = pathArr.length; pathArr.forEach((element, index) => { buffer.writeUInt32BE(element, 1 + 4 * index); }); From 6dde0b7cdd86b7455419b708f43871b41456c172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Fri, 30 Jul 2021 18:11:36 -0300 Subject: [PATCH 06/25] chore: minor changes --- src/screens/SendTokens.js | 2 ++ src/utils/ledger.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/screens/SendTokens.js b/src/screens/SendTokens.js index cef648d3..2766ea3f 100644 --- a/src/screens/SendTokens.js +++ b/src/screens/SendTokens.js @@ -234,6 +234,8 @@ class SendTokens extends React.Component { this.data = data; $('#pinModal').modal('show'); } else { + // currently we only support HTR + data.tokens = []; this.executeSendLedger(data); } } catch(e) { diff --git a/src/utils/ledger.js b/src/utils/ledger.js index 93fc5e65..569f0b4d 100644 --- a/src/utils/ledger.js +++ b/src/utils/ledger.js @@ -118,7 +118,7 @@ if (IPC_RENDERER) { const arr = []; for (const input of data.inputs) { const index = keys[input.address].index; - arr.push(formatPathData(index)); + arr.push(index); } IPC_RENDERER.send("ledger:getSignatures", arr); }, From 83889424d22d17ec2b0660cd2dfe299d32fa624b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Sun, 1 Aug 2021 16:52:12 -0300 Subject: [PATCH 07/25] fix: hardware wallet send tokens --- src/components/SendTokensOne.js | 19 +++++++++++++++++++ src/screens/SendTokens.js | 3 +++ 2 files changed, 22 insertions(+) diff --git a/src/components/SendTokensOne.js b/src/components/SendTokensOne.js index b359bb7b..2e423d9c 100644 --- a/src/components/SendTokensOne.js +++ b/src/components/SendTokensOne.js @@ -132,6 +132,25 @@ class SendTokensOne extends React.Component { return data; } + /** + * Validate inputs and outpus + * 1. If inputs were not selected, select inputs from outputs amount + * 2. If amount of selected inputs is larger than outputs amount, we create a change output + * 3. If inputs were selected, check if they are valid and add address key to input + */ + handleInitialData = (data) => { + const noInputs = this.noInputs.current.checked; + const walletData = hathorLib.wallet.getWalletData(); + const history = 'historyTransactions' in walletData ? walletData['historyTransactions'] : {}; + const result = hathorLib.wallet.prepareSendTokensData(data, this.state.selected, noInputs, history, this.state.selectedTokens); + if (result.success === false) { + this.props.updateState({ errorMessage: result.message, loading: false }); + return null; + } + + return result.data; + } + /** * Called when select input checkbox is clicked * diff --git a/src/screens/SendTokens.js b/src/screens/SendTokens.js index 2766ea3f..c85591be 100644 --- a/src/screens/SendTokens.js +++ b/src/screens/SendTokens.js @@ -136,6 +136,9 @@ class SendTokens extends React.Component { const instance = ref.current; const dataOne = instance.getData(); if (!dataOne) return; + if (hathorLib.wallet.isHardwareWallet()) { + dataOne = instance.handleInitialData(dataOne); + } data['inputs'] = [...data['inputs'], ...dataOne['inputs']]; data['outputs'] = [...data['outputs'], ...dataOne['outputs']]; } From 06795f64c5995198b70043edfe7625a997cb628f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Mon, 2 Aug 2021 00:19:25 -0300 Subject: [PATCH 08/25] chore: docstrings --- public/ledger.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/public/ledger.js b/public/ledger.js index 4338f583..9b9caa28 100644 --- a/public/ledger.js +++ b/public/ledger.js @@ -59,6 +59,21 @@ class Ledger { return e; } + /** + * Format bip32 path data to transmit to ledger + * + * We use the index to form the path m/44'/280'/0'/0/ + * in a format that the ledger can read + * + * - 1 byte with the path length + * - unsigned int 32b (4 bytes) for each level + * - hardened levels (with a `'`) have a 0x80000000 shift + * - if index is not passed, use m/44'/280'/0'/0 + * + * @param {number} index Index level of the path we want + * + * @return {Buffer} A buffer with the formatted bip32 path + */ static formatPathData(index) { const pathArr = [ 44 + 0x80000000, // 44' @@ -252,6 +267,8 @@ class Ledger { /** * Send address command so ledger can show specific address for user to validate * + * We send the bip32 path of the address + * * @param {Buffer} index Address index to be shown on ledger * * @return {Promise} Promise resolved when user validates that the address is correct. @@ -272,12 +289,12 @@ class Ledger { * Maximum data that can be transferred to ledger is 255 bytes * * p1 = 0 - * p2 = remaining calls + * p2 = chunk index (starting at 0) * Eg: 3 rounds (data has 612 bytes) * p1 p2 data - * 0 2 255 bytes * 0 1 255 bytes - * 0 0 102 bytes + * 0 2 255 bytes + * 0 3 102 bytes * * @param {Object} data Data to send to Ledger (change_output_info + sighash_all) * @@ -304,11 +321,11 @@ class Ledger { * We request the signature for each input * * p1 = 1 - * p2 = * - * data = priv key index (4 bytes) + * p2 = 0 + * data = bip32 path of the priv key (21 bytes) [1 + 4*n bytes, with n == path length] * ## done, no more signatures needed * p1 = 2 - * p2 = * + * p2 = 0 * data = none * * @param {Object} indexes Index of the key we want the data signed with From 258ff07c1bf833ea3876845bb6c179a02004e4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Tue, 3 Aug 2021 09:42:34 -0300 Subject: [PATCH 09/25] fix: ledger SIGN_TX change info --- public/ledger.js | 2 +- src/utils/ledger.js | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/public/ledger.js b/public/ledger.js index 9b9caa28..11944cc4 100644 --- a/public/ledger.js +++ b/public/ledger.js @@ -269,7 +269,7 @@ class Ledger { * * We send the bip32 path of the address * - * @param {Buffer} index Address index to be shown on ledger + * @param {number} index Address index to be shown on ledger * * @return {Promise} Promise resolved when user validates that the address is correct. */ diff --git a/src/utils/ledger.js b/src/utils/ledger.js index 569f0b4d..a052cbe2 100644 --- a/src/utils/ledger.js +++ b/src/utils/ledger.js @@ -66,7 +66,7 @@ if (IPC_RENDERER) { /** * Show address from index (in the path) on ledger * - * @param {Buffer} index Path index of addres as 4 bytes number + * @param {number} index Path index of address * * @memberof Ledger * @inner @@ -89,10 +89,13 @@ if (IPC_RENDERER) { // first assemble data to be sent const arr = []; if (changeIndex > -1) { - // encode the bit indicating existence of change and change index on first byte - arr.push(hathorLib.transaction.intToBytes(0x80 | changeIndex, 1)) - // Change key index of the address - arr.push(formatPathData(changeKeyIndex)); + const changeBuffer = formatPathData(changeKeyIndex) + // encode the bit indicating existence of change and change path length on first byte + arr.push(hathorLib.transaction.intToBytes(0x80 | changeBuffer[0], 1)) + // change output index on the second + arr.push(hathorLib.transaction.intToBytes(changeIndex, 1)) + // Change key path of the address + arr.push(changeBuffer.slice(1)); } else { // no change output arr.push(hathorLib.transaction.intToBytes(0, 1)); From 8d04b8048300a0dbebf512913090f60edd5472f6 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 3 Aug 2021 18:10:54 -0300 Subject: [PATCH 10/25] fix: search address on addresses list is working now (#214) --- QA.md | 2 ++ src/screens/AddressList.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/QA.md b/QA.md index 84976fdc..794350b8 100644 --- a/QA.md +++ b/QA.md @@ -34,6 +34,8 @@ You can connect your wallet to the testnet (https://node1.foxtrot.testnet.hathor 1. **Wallet screen** 1. Copy the address and send 10.00 HTR from another wallet to this address. 1. Check that the transaction appears in the list, the balance was updated and the address also changed. + 1. Click on 'See all addresses' and see the list. The address used to send the transaction should have 'Number of transactions' equal to 1. + 1. Copy this address and paste to search, validate it appears only one line on the table with the searched address. 1. Click on 'Generate new address' and validate it has changed. 1. Click on the 'QR Code', and copy the address to validate it really copied. 1. Read the QRCode and validate it's the same as the written text. diff --git a/src/screens/AddressList.js b/src/screens/AddressList.js index a84689af..fc1b1ea9 100644 --- a/src/screens/AddressList.js +++ b/src/screens/AddressList.js @@ -89,7 +89,7 @@ class AddressList extends React.Component { if (text) { if (hathorLib.transaction.isAddressValid(text)) { for (const addr of this.state.addresses) { - if (addr.address === text) { + if (addr === text) { this.setState({ filtered: true, filteredAddresses: [addr], totalPages: 1, page: 1 }); return } From 022c8fda3f56b71cb5aee842f2855c96f26b2c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Tue, 3 Aug 2021 18:23:26 -0300 Subject: [PATCH 11/25] fix: bip32 format path for ledger --- src/utils/ledger.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/ledger.js b/src/utils/ledger.js index a052cbe2..33ed34ac 100644 --- a/src/utils/ledger.js +++ b/src/utils/ledger.js @@ -17,7 +17,7 @@ import hathorLib from '@hathor/wallet-lib'; export class LedgerError extends Error {} const formatPathData = (index) => { - pathArr = [ + const pathArr = [ 44 + 0x80000000, // 44' 280 + 0x80000000, // 280' 0 + 0x80000000, // 0' @@ -26,7 +26,7 @@ const formatPathData = (index) => { if (index !== undefined) { pathArr.push(index); } - const buffer = Buffer.alloc(1+4*pathArr.length); + const buffer = Buffer.alloc(1+(4*pathArr.length)); buffer[0] = pathArr.length; pathArr.forEach((element, index) => { buffer.writeUInt32BE(element, 1 + 4 * index); From 04722e714146929109df1f6290d11a4dcc27df09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 4 Aug 2021 13:08:26 -0300 Subject: [PATCH 12/25] chore: better names and deactivating hw wallet --- src/components/SendTokensOne.js | 2 +- src/constants.js | 2 +- src/screens/SendTokens.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/SendTokensOne.js b/src/components/SendTokensOne.js index 2e423d9c..44af1d3a 100644 --- a/src/components/SendTokensOne.js +++ b/src/components/SendTokensOne.js @@ -138,7 +138,7 @@ class SendTokensOne extends React.Component { * 2. If amount of selected inputs is larger than outputs amount, we create a change output * 3. If inputs were selected, check if they are valid and add address key to input */ - handleInitialData = (data) => { + validateInputsAndOutputs = (data) => { const noInputs = this.noInputs.current.checked; const walletData = hathorLib.wallet.getWalletData(); const history = 'historyTransactions' in walletData ? walletData['historyTransactions'] : {}; diff --git a/src/constants.js b/src/constants.js index 3363de43..a6fbe19c 100644 --- a/src/constants.js +++ b/src/constants.js @@ -129,4 +129,4 @@ export const IPC_RENDERER = ipcRenderer; /** * Flag to hide/show elements after ledger integration is done */ -export const LEDGER_ENABLED = true; +export const LEDGER_ENABLED = false; diff --git a/src/screens/SendTokens.js b/src/screens/SendTokens.js index c85591be..7299dce9 100644 --- a/src/screens/SendTokens.js +++ b/src/screens/SendTokens.js @@ -137,7 +137,7 @@ class SendTokens extends React.Component { const dataOne = instance.getData(); if (!dataOne) return; if (hathorLib.wallet.isHardwareWallet()) { - dataOne = instance.handleInitialData(dataOne); + dataOne = instance.validateInputsAndOutputs(dataOne); } data['inputs'] = [...data['inputs'], ...dataOne['inputs']]; data['outputs'] = [...data['outputs'], ...dataOne['outputs']]; From 0e8e0e874916dca0746b90abb2ec73c8ea20002e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Thu, 5 Aug 2021 01:34:03 -0300 Subject: [PATCH 13/25] chore: change location of token init --- src/screens/SendTokens.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screens/SendTokens.js b/src/screens/SendTokens.js index 7299dce9..649b8973 100644 --- a/src/screens/SendTokens.js +++ b/src/screens/SendTokens.js @@ -138,6 +138,8 @@ class SendTokens extends React.Component { if (!dataOne) return; if (hathorLib.wallet.isHardwareWallet()) { dataOne = instance.validateInputsAndOutputs(dataOne); + // currently we only support HTR + data['tokens'] = []; } data['inputs'] = [...data['inputs'], ...dataOne['inputs']]; data['outputs'] = [...data['outputs'], ...dataOne['outputs']]; @@ -237,8 +239,6 @@ class SendTokens extends React.Component { this.data = data; $('#pinModal').modal('show'); } else { - // currently we only support HTR - data.tokens = []; this.executeSendLedger(data); } } catch(e) { From fe6827df34aa5f0c5829d7dab6cf9222b6bdfa76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Thu, 12 Aug 2021 13:33:43 -0300 Subject: [PATCH 14/25] chore: update wallet-lib version --- package-lock.json | 590 ++++++---------------------------------------- package.json | 2 +- 2 files changed, 71 insertions(+), 521 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10ae2179..5c78e775 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1135,17 +1135,76 @@ } }, "@hathor/wallet-lib": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-0.19.0.tgz", - "integrity": "sha512-HAs/OmXQMJ/SbBhKBCss1r/2t2h5Wu5i2RQHvR1hgfbgckZ0qH0dvegN7klwKWHL/iexocMXGv6PFcfjMr2JIQ==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-0.21.0.tgz", + "integrity": "sha512-bAkt/QJ5j7buFSskypeovo2v8aQDZHRQCUGaRuXU/pEDBhgMkC+YPjEHhdi3Oli8B0x1hJFRqnEddE2XZRc2eA==", "requires": { "axios": "0.18.1", - "bitcore-mnemonic": "1.7.0", + "bitcore-lib": "8.25.10", + "bitcore-mnemonic": "8.25.10", "crypto-js": "3.3.0", "isomorphic-ws": "4.0.1", "lodash": "4.17.15", "long": "4.0.0", "ws": "7.2.3" + }, + "dependencies": { + "bitcore-lib": { + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.10.tgz", + "integrity": "sha512-MyHpSg7aFRHe359RA/gdkaQAal3NswYZTLEuu0tGX1RGWXAYN9i/24fsjPqVKj+z0ua+gzAT7aQs0KiKXWCgKA==", + "requires": { + "bech32": "1.1.3", + "bn.js": "4.11.8", + "bs58": "4.0.1", + "buffer-compare": "1.1.1", + "elliptic": "6.5.4", + "inherits": "2.0.1", + "lodash": "4.17.21" + }, + "dependencies": { + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + } + } + }, + "bitcore-mnemonic": { + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.10.tgz", + "integrity": "sha512-FeXxO37BLV5JRvxPmVFB91zRHalavV8H4TdQGt1/hz0AkoPymIV68OkuB+TptpjeYgatcgKPoPvPhglJkTzFQQ==", + "requires": { + "bitcore-lib": "8.25.10", + "unorm": "1.6.0" + } + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "4.12.0", + "brorand": "1.1.0", + "hash.js": "1.1.7", + "hmac-drbg": "1.0.1", + "inherits": "2.0.4", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + } + } + } } }, "@jest/console": { @@ -3135,6 +3194,11 @@ "tweetnacl": "0.14.5" } }, + "bech32": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.3.tgz", + "integrity": "sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg==" + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -3153,35 +3217,6 @@ "file-uri-to-path": "1.0.0" } }, - "bitcore-lib": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-0.16.0.tgz", - "integrity": "sha512-CEtcrPAH2gwgaMN+OPMJc18TBEak1+TtzMyafrqrIbK9PIa3kat195qBJhC0liJSHRiRr6IE2eLcXeIFFs+U8w==", - "requires": { - "bn.js": "4.11.8", - "bs58": "4.0.1", - "buffer-compare": "1.1.1", - "elliptic": "6.4.0", - "inherits": "2.0.1", - "lodash": "4.17.11" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } - } - }, - "bitcore-mnemonic": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-1.7.0.tgz", - "integrity": "sha512-1JV1okgz9Vv+Y4fG2m3ToR+BGdKA6tSoqjepIxA95BZjW6YaeopVW4iOe/dY9dnkZH4+LA2AJ4YbDE6H3ih3Yw==", - "requires": { - "bitcore-lib": "0.16.0", - "unorm": "1.6.0" - } - }, "bl": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", @@ -3832,7 +3867,6 @@ "anymatch": "2.0.0", "async-each": "1.0.3", "braces": "2.3.2", - "fsevents": "1.2.11", "glob-parent": "3.1.0", "inherits": "2.0.4", "is-binary-path": "1.0.1", @@ -7103,7 +7137,6 @@ "requires": { "anymatch": "3.1.1", "braces": "3.0.2", - "fsevents": "2.1.2", "glob-parent": "5.1.0", "is-binary-path": "2.1.0", "is-glob": "4.0.1", @@ -7122,8 +7155,7 @@ "fsevents": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "optional": true + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==" }, "glob-parent": { "version": "5.1.0", @@ -7270,477 +7302,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", - "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", - "optional": true, - "requires": { - "bindings": "1.5.0", - "nan": "2.14.0", - "node-pre-gyp": "0.14.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.3", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "2.9.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.3" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": "2.1.2" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.1.1" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "2.9.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.4.0", - "bundled": true, - "optional": true, - "requires": { - "debug": "3.2.6", - "iconv-lite": "0.4.24", - "sax": "1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.4.0", - "nopt": "4.0.1", - "npm-packlist": "1.4.7", - "npmlog": "4.1.2", - "rc": "1.2.8", - "rimraf": "2.7.1", - "semver": "5.7.1", - "tar": "4.4.13" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.7", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "3.0.3", - "npm-bundled": "1.1.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.5", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "isarray": "1.0.0", - "process-nextick-args": "2.0.1", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "7.1.6" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "1.1.3", - "fs-minipass": "1.2.7", - "minipass": "2.9.0", - "minizlib": "1.3.3", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.1.1" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true - } - } - }, "fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", @@ -9611,7 +9172,6 @@ "@jest/types": "24.9.0", "anymatch": "2.0.0", "fb-watchman": "2.0.1", - "fsevents": "1.2.11", "graceful-fs": "4.2.3", "invariant": "2.2.4", "jest-serializer": "24.9.0", @@ -11783,7 +11343,6 @@ "requires": { "anymatch": "3.1.1", "braces": "3.0.2", - "fsevents": "2.1.2", "glob-parent": "5.1.0", "is-binary-path": "2.1.0", "is-glob": "4.0.1", @@ -11847,9 +11406,7 @@ "fsevents": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "dev": true, - "optional": true + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==" }, "get-stream": { "version": "3.0.0", @@ -14656,7 +14213,6 @@ "eslint-plugin-react-hooks": "1.7.0", "file-loader": "4.3.0", "fs-extra": "8.1.0", - "fsevents": "2.1.2", "html-webpack-plugin": "4.0.0-beta.11", "identity-obj-proxy": "3.0.0", "jest": "24.9.0", @@ -14708,12 +14264,6 @@ "resolve": "1.15.0" } }, - "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "optional": true - }, "resolve": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz", diff --git a/package.json b/package.json index e21b92fd..67d57d53 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "version": "0.19.1", "private": true, "dependencies": { - "@hathor/wallet-lib": "^0.19.0", + "@hathor/wallet-lib": "^0.21.0", "@ledgerhq/hw-transport-node-hid": "^4.73.4", "@sentry/electron": "^0.17.0", "babel-polyfill": "^6.26.0", From 0345f5de32bb1c6be81241c4d8650f80da00bb41 Mon Sep 17 00:00:00 2001 From: Luis Helder Date: Mon, 23 Aug 2021 12:34:40 -0300 Subject: [PATCH 15/25] Create CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..016c848e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @pedroferreira1 From c54ee2259807ef2942dfb1b0b2875ac5ae9bc196 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 27 Aug 2021 11:54:52 -0300 Subject: [PATCH 16/25] refactor: adapt wallet desktop code for new facade signatures (#220) * refactor: load wallet and real time update working with new facade signatures * refactor: send tokens working with new facade signatures (all token actions as well) * refactor: address list getting data from generator * refactor: token history pagination is now paginated also in the facade method * refactor: get address new return * fix: redirect after creating token fixed * chore: remove debug log * refactor: sendPromise dont need to be a component attribut, it can be a local variable * refactor: using object destructuring to get address from object * fix: typo helpersUtils * feat: old lib methods used by ledger integration expect input to have key tx_id and new ones expect txId * chore: bump lib version to 0.22.0 --- package-lock.json | 6 +- package.json | 2 +- src/actions/index.js | 22 +++- src/components/ModalSendTx.js | 4 +- src/components/SendTokensOne.js | 7 +- src/components/SendTxHandler.js | 30 ++++-- src/components/TokenHistory.js | 22 +++- src/components/WalletAddress.js | 2 +- src/components/tokens/TokenAction.js | 13 ++- src/components/tokens/TokenDelegate.js | 8 +- src/components/tokens/TokenDestroy.js | 8 +- src/components/tokens/TokenMelt.js | 8 +- src/components/tokens/TokenMint.js | 8 +- src/reducers/index.js | 129 +++++++++------------- src/screens/AddressList.js | 31 ++++-- src/screens/CreateToken.js | 33 +++--- src/screens/SendTokens.js | 12 +-- src/utils/wallet.js | 144 +++++++++++++++++++++++-- 18 files changed, 324 insertions(+), 165 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c78e775..16104b12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1135,9 +1135,9 @@ } }, "@hathor/wallet-lib": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-0.21.0.tgz", - "integrity": "sha512-bAkt/QJ5j7buFSskypeovo2v8aQDZHRQCUGaRuXU/pEDBhgMkC+YPjEHhdi3Oli8B0x1hJFRqnEddE2XZRc2eA==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-0.22.0.tgz", + "integrity": "sha512-kKOTNHnCr36o66ehGgCijEcUP2yvPop6rGyQKbbcB3iQ8yi0WuVMShQvUGqC725yJ3Dd18pXv4dOgwz3k4V6pA==", "requires": { "axios": "0.18.1", "bitcore-lib": "8.25.10", diff --git a/package.json b/package.json index 67d57d53..7c3598b1 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "version": "0.19.1", "private": true, "dependencies": { - "@hathor/wallet-lib": "^0.21.0", + "@hathor/wallet-lib": "^0.22.0", "@ledgerhq/hw-transport-node-hid": "^4.73.4", "@sentry/electron": "^0.17.0", "babel-polyfill": "^6.26.0", diff --git a/src/actions/index.js b/src/actions/index.js index 202fa2d2..8df90eab 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -92,8 +92,10 @@ export const updateRequestErrorStatusCode = data => ({ type: "update_request_err /** * Set height + * height {number} new network height + * htrUpdatedBalance {Object} balance of HTR */ -export const updateHeight = data => ({ type: "update_height", payload: data }); +export const updateHeight = (height, htrUpdatedBalance) => ({ type: "update_height", payload: { height, htrUpdatedBalance } }); /** * wallet {HathorWallet} wallet object @@ -106,16 +108,26 @@ export const setWallet = (wallet) => ({ type: "set_wallet", payload: wallet }); export const resetWallet = () => ({ type: "reset_wallet" }); /** - * history {Object} history of this wallet (including txs from all tokens) + * tokensHistory {Object} history for each token + * tokensBalance {Object} balance for each token + * tokens {Array} array of token uids the the wallet has */ -export const loadWalletSuccess = (history) => ({ type: "load_wallet_success", payload: { history } }); +export const loadWalletSuccess = (tokensHistory, tokensBalance, tokens) => ({ type: "load_wallet_success", payload: { tokensHistory, tokensBalance, tokens } }); /** * tx {Object} the new transaction + * updatedBalanceMap {Object} balance updated of each token in this tx */ -export const newTx = (tx) => ({ type: "new_tx", payload: { tx } }); +export const newTx = (tx, updatedBalanceMap) => ({ type: "new_tx", payload: { tx, updatedBalanceMap } }); /** * tx {Object} the new transaction + * updatedBalanceMap {Object} balance updated of each token in this tx */ -export const updateTx = (tx) => ({ type: "update_tx", payload: { tx } }); +export const updateTx = (tx, updatedBalanceMap) => ({ type: "update_tx", payload: { tx, updatedBalanceMap } }); + +/** + * token {String} token of the updated history + * newHistory {Array} array with the new fetched history + */ +export const updateTokenHistory = (token, newHistory) => ({ type: "update_token_history", payload: { token, newHistory } }); diff --git a/src/components/ModalSendTx.js b/src/components/ModalSendTx.js index 397313a0..f8ef2c5c 100644 --- a/src/components/ModalSendTx.js +++ b/src/components/ModalSendTx.js @@ -79,7 +79,7 @@ class ModalSendTx extends React.Component { * * @param {Object} e Event emitted when button is clicked */ - handlePin = (e) => { + handlePin = async (e) => { e.preventDefault(); if (this.refs.formPin.checkValidity() === false) { this.refs.formPin.classList.add('was-validated'); @@ -89,7 +89,7 @@ class ModalSendTx extends React.Component { if (hathorLib.wallet.isPinCorrect(pin)) { $('#pinModal').data('bs.modal')._config.backdrop = 'static'; $('#pinModal').data('bs.modal')._config.keyboard = false; - this.sendTransaction = this.props.prepareSendTransaction(pin); + this.sendTransaction = await this.props.prepareSendTransaction(pin); if (this.sendTransaction) { // Show send tx handler component and start sending this.setState({ step: 1, loading: true }); diff --git a/src/components/SendTokensOne.js b/src/components/SendTokensOne.js index 44af1d3a..96b2b052 100644 --- a/src/components/SendTokensOne.js +++ b/src/components/SendTokensOne.js @@ -124,7 +124,7 @@ class SendTokensOne extends React.Component { const index = input.current.index.current.value; if (txId && index) { - data['inputs'].push({'tx_id': txId, 'index': index, 'token': this.state.selected.uid }); + data['inputs'].push({'txId': txId, 'index': parseInt(index, 10), 'token': this.state.selected.uid }); } } } @@ -142,6 +142,11 @@ class SendTokensOne extends React.Component { const noInputs = this.noInputs.current.checked; const walletData = hathorLib.wallet.getWalletData(); const history = 'historyTransactions' in walletData ? walletData['historyTransactions'] : {}; + // This method is used by hardware wallet because it still uses old methods from lib for speeding the integration process + // the new methods expect input object with txId key but the old one expect tx_id + for (let input of data.inputs) { + input.tx_id = input.txId; + } const result = hathorLib.wallet.prepareSendTokensData(data, this.state.selected, noInputs, history, this.state.selectedTokens); if (result.success === false) { this.props.updateState({ errorMessage: result.message, loading: false }); diff --git a/src/components/SendTxHandler.js b/src/components/SendTxHandler.js index 820c67da..6fd634c7 100644 --- a/src/components/SendTxHandler.js +++ b/src/components/SendTxHandler.js @@ -7,7 +7,7 @@ import React from 'react'; import { t } from 'ttag'; -import hathorLib from '@hathor/wallet-lib'; +import { SendTransaction, ErrorMessages as errorMessagesEnum } from '@hathor/wallet-lib'; import PropTypes from 'prop-types'; @@ -40,8 +40,21 @@ class SendTxHandler extends React.Component { componentDidMount = () => { // Start listening for events this.addSendTxEventHandlers(); + // Promise of the sendTransaction object that resolves when the tx is sent + let sendPromise; // Start sendTransaction object (submit job) - this.props.sendTransaction.start(); + if (this.props.sendTransaction.transaction) { + // Token action transactions already have the full tx prepared + // just need to mine and propagate + sendPromise = this.props.sendTransaction.runFromMining(); + } else { + sendPromise = this.props.sendTransaction.run(); + } + sendPromise.then((tx) => { + this.sendSuccess(tx); + }, (err) => { + this.sendError(err.message); + }); } /** @@ -51,9 +64,7 @@ class SendTxHandler extends React.Component { this.props.sendTransaction.on('job-submitted', this.updateEstimation); this.props.sendTransaction.on('estimation-updated', this.updateEstimation); this.props.sendTransaction.on('job-done', this.jobDone); - this.props.sendTransaction.on('send-success', this.sendSuccess); this.props.sendTransaction.on('send-error', this.sendError); - this.props.sendTransaction.on('unexpected-error', this.sendError); } /** @@ -74,7 +85,14 @@ class SendTxHandler extends React.Component { * @param {String} message Error message */ sendError = (message) => { - this.setState({ errorMessage: `Error: ${message}`}); + const errorMap = { + [errorMessagesEnum.ErrorMessages.UNEXPECTED_PUSH_TX_ERROR]: t`There was an unexpected error when pushing the transaction to the network.`, + [errorMessagesEnum.ErrorMessages.NO_UTXOS_AVAILABLE]: t`There are no utxos available to fill the transaction.`, + [errorMessagesEnum.ErrorMessages.INVALID_INPUT]: t`The selected inputs are invalid.`, + } + + const errorMessage = message in errorMap ? errorMap[message] : message; + this.setState({ errorMessage: `Error: ${errorMessage}`}); if (this.props.onSendError) { this.props.onSendError(message); } @@ -124,7 +142,7 @@ class SendTxHandler extends React.Component { * onSendError: optional method to be executed when an error happens while sending the tx */ SendTxHandler.propTypes = { - sendTransaction: PropTypes.instanceOf(hathorLib.SendTransaction).isRequired, + sendTransaction: PropTypes.instanceOf(SendTransaction).isRequired, onSendSuccess: PropTypes.func, onSendError: PropTypes.func, }; diff --git a/src/components/TokenHistory.js b/src/components/TokenHistory.js index b8bf5e79..9f012b59 100644 --- a/src/components/TokenHistory.js +++ b/src/components/TokenHistory.js @@ -10,6 +10,7 @@ import { t } from 'ttag'; import { Link } from 'react-router-dom' import { CopyToClipboard } from 'react-copy-to-clipboard'; import HathorAlert from './HathorAlert'; +import wallet from '../utils/wallet'; import TokenPagination from './TokenPagination'; import hathorLib from '@hathor/wallet-lib'; import { connect } from "react-redux"; @@ -21,6 +22,7 @@ const mapStateToProps = (state, props) => { } return { tokensHistory: history, + wallet: state.wallet }; }; @@ -39,6 +41,7 @@ class TokenHistory extends React.Component { * reference {string} ID of the reference transaction used when clicking on pagination button * direction {string} 'previous' or 'next', dependending on which pagination button the user has clicked * transactions {Array} List of transactions to be shown in the screen + * shouldFetch {Boolean} If should fetch more history (when the fetch returns 0 elements, should be set to false) */ state = { hasAfter: false, @@ -48,6 +51,7 @@ class TokenHistory extends React.Component { reference: null, direction: null, transactions: [], + shouldFetch: true, }; componentDidMount = () => { @@ -60,14 +64,30 @@ class TokenHistory extends React.Component { } } + /** + * Fetch more history + */ + fetchMoreHistory = async () => { + if (this.state.shouldFetch) { + const newHistory = await wallet.fetchMoreHistory(this.props.wallet, this.props.selectedToken, this.props.tokensHistory); + if (newHistory.length === 0) { + // Last page already fetched, no need to fetch anymore + this.setState({ shouldFetch: false }); + } + } + } + /** * Called when user clicks 'Next' pagination button * * @param {Object} e Event emitted when button is clicked */ - nextClicked = (e) => { + nextClicked = async (e) => { e.preventDefault(); this.setState({ reference: this.state.lastHash, direction: 'next' }, () => { + // Every time the user clicks on the next button we must try to fetch more history + // usually we will have at least five more pages already fetched + this.fetchMoreHistory(); this.handleHistoryUpdate(); }); } diff --git a/src/components/WalletAddress.js b/src/components/WalletAddress.js index 6b6ec603..086a50a3 100644 --- a/src/components/WalletAddress.js +++ b/src/components/WalletAddress.js @@ -66,7 +66,7 @@ class WalletAddress extends React.Component { */ generateNewAddress = (e) => { e.preventDefault(); - const address = this.props.wallet.getNextAddress(); + const { address } = this.props.wallet.getNextAddress(); if (address === this.props.lastSharedAddress) { this.refs.alertError.show(3000); diff --git a/src/components/tokens/TokenAction.js b/src/components/tokens/TokenAction.js index 0bef8d07..7bf3218d 100644 --- a/src/components/tokens/TokenAction.js +++ b/src/components/tokens/TokenAction.js @@ -115,13 +115,12 @@ class TokenAction extends React.Component { * * @param {String} pin PIN written by the user */ - onPrepareSendTransaction = (pin) => { - const ret = this.props.prepareSendTransaction(pin); - - if (ret.success) { - return ret.sendTransaction; - } else { - this.onSendError(ret.message); + onPrepareSendTransaction = async (pin) => { + try { + const sendTransaction = await this.props.prepareSendTransaction(pin); + return sendTransaction; + } catch (e) { + this.onSendError(e.message); } } diff --git a/src/components/tokens/TokenDelegate.js b/src/components/tokens/TokenDelegate.js index 08c49ca9..192f68e1 100644 --- a/src/components/tokens/TokenDelegate.js +++ b/src/components/tokens/TokenDelegate.js @@ -9,6 +9,7 @@ import React from 'react'; import { t } from 'ttag'; import TokenAction from './TokenAction'; import { connect } from "react-redux"; +import hathorLib from '@hathor/wallet-lib'; const mapStateToProps = (state) => { return { @@ -41,18 +42,19 @@ class TokenDelegate extends React.Component { * SendTransaction object that emit events while the tx is being sent and promise resolves when the sending is done * In case of error, an object with {success: false, message} */ - prepareSendTransaction = (pin) => { + prepareSendTransaction = async (pin) => { const type = this.props.action === 'delegate-mint' ? t`Mint` : t`Melt`; - return this.props.wallet.delegateAuthority( + + const transaction = await this.props.wallet.prepareDelegateAuthorityData( this.props.token.uid, type.toLowerCase(), this.delegateAddress.current.value, { createAnother: this.delegateCreateAnother.current.checked, - startMiningTx: false, pinCode: pin, } ); + return new hathorLib.SendTransaction({ transaction, pin }); } /** diff --git a/src/components/tokens/TokenDestroy.js b/src/components/tokens/TokenDestroy.js index a89b4089..c48657c6 100644 --- a/src/components/tokens/TokenDestroy.js +++ b/src/components/tokens/TokenDestroy.js @@ -41,14 +41,16 @@ class TokenDestroy extends React.Component { * SendTransaction object that emit events while the tx is being sent and promise resolves when the sending is done * In case of error, an object with {success: false, message} */ - prepareSendTransaction = (pin) => { + prepareSendTransaction = async (pin) => { const type = this.props.action === 'destroy-mint' ? 'mint' : 'melt'; - return this.props.wallet.destroyAuthority( + + const transaction = await this.props.wallet.prepareDestroyAuthorityData( this.props.token.uid, type, this.state.destroyQuantity, - { startMiningTx: false, pinCode: pin }, + { pinCode: pin }, ); + return new hathorLib.SendTransaction({ transaction, pin }); } /** diff --git a/src/components/tokens/TokenMelt.js b/src/components/tokens/TokenMelt.js index 5f0015ba..7e3676df 100644 --- a/src/components/tokens/TokenMelt.js +++ b/src/components/tokens/TokenMelt.js @@ -44,18 +44,18 @@ class TokenMelt extends React.Component { * SendTransaction object that emit events while the tx is being sent and promise resolves when the sending is done * In case of error, an object with {success: false, message} */ - prepareSendTransaction = (pin) => { + prepareSendTransaction = async (pin) => { const sanitizedValue = (this.amount.current.value || "").replace(/,/g, ''); const amountValue = wallet.decimalToInteger(sanitizedValue); - return this.props.wallet.meltTokens( + const transaction = await this.props.wallet.prepareMeltTokensData( this.props.token.uid, amountValue, { createAnotherMelt: this.createAnother.current.checked, - startMiningTx: false, - pinCode: pin, + pinCode: pin } ); + return new hathorLib.SendTransaction({ transaction, pin }); } /** diff --git a/src/components/tokens/TokenMint.js b/src/components/tokens/TokenMint.js index 122f2045..2f048c93 100644 --- a/src/components/tokens/TokenMint.js +++ b/src/components/tokens/TokenMint.js @@ -54,19 +54,19 @@ class TokenMint extends React.Component { * SendTransaction object that emit events while the tx is being sent and promise resolves when the sending is done * In case of error, an object with {success: false, message} */ - prepareSendTransaction = (pin) => { + prepareSendTransaction = async (pin) => { const amountValue = wallet.decimalToInteger(this.state.amount); const address = this.chooseAddress.current.checked ? null : this.address.current.value; - return this.props.wallet.mintTokens( + const transaction = await this.props.wallet.prepareMintTokensData( this.props.token.uid, amountValue, - address, { + address, createAnotherMint: this.createAnother.current.checked, - startMiningTx: false, pinCode: pin } ); + return new hathorLib.SendTransaction({ transaction, pin }); } /** diff --git a/src/reducers/index.js b/src/reducers/index.js index 9df0fbf4..dc005fb0 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -11,7 +11,6 @@ import { VERSION } from '../constants'; const initialState = { tokensHistory: {}, tokensBalance: {}, - addressesTxs: {}, // Address to be used and is shown in the screen lastSharedAddress: null, // Index of the address to be used @@ -90,7 +89,7 @@ const rootReducer = (state = initialState, action) => { case 'update_request_error_status_code': return Object.assign({}, state, {requestErrorStatusCode: action.payload}); case 'update_height': - return Object.assign({}, state, {height: action.payload.height}); + return onUpdateHeight(state, action); case 'set_wallet': return onSetWallet(state, action); case 'reset_wallet': @@ -101,6 +100,8 @@ const rootReducer = (state = initialState, action) => { return onNewTx(state, action); case 'update_tx': return onUpdateTx(state, action); + case 'update_token_history': + return onUpdateTokenHistory(state, action); default: return state; } @@ -130,7 +131,7 @@ const onResetWallet = (state, action) => { }; }; -const getTxHistoryFromTx = (tx, tokenUid, tokenTxBalance) => { +const getTxHistoryFromWSTx = (tx, tokenUid, tokenTxBalance) => { return { tx_id: tx.tx_id, timestamp: tx.timestamp, @@ -165,58 +166,15 @@ const isAllAuthority = (tx) => { return true; } - /** - * Got wallet history. Update history and balance for each token. + * Got wallet history. Update wallet data on redux */ const onLoadWalletSuccess = (state, action) => { // Update the version of the wallet that the data was loaded hathorLib.storage.setItem('wallet:version', VERSION); - const { history } = action.payload; - const tokensHistory = {}; - const newAddressesTxs = {}; - const allTokens = new Set(); - // iterate through all txs received and map all tokens this wallet has, with - // its history and balance - for (const tx of Object.values(history)) { - // we first get all tokens present in this tx (that belong to the user) and - // the corresponding balances - const balances = state.wallet.getTxBalance(tx, { includeAuthorities: true }); - for (const [tokenUid, tokenTxBalance] of Object.entries(balances)) { - allTokens.add(tokenUid); - let tokenHistory = tokensHistory[tokenUid]; - if (tokenHistory === undefined) { - tokenHistory = []; - tokensHistory[tokenUid] = tokenHistory; - } - // add this tx to the history of the corresponding token - tokenHistory.push(getTxHistoryFromTx(tx, tokenUid, tokenTxBalance)); - } - - const txAddresses = state.wallet.getTxAddresses(tx); - for (const addr of txAddresses) { - if (addr in newAddressesTxs) { - newAddressesTxs[addr] += 1; - } else { - newAddressesTxs[addr] = 1; - } - } - - } - - const tokensBalance = {}; - for (const tokenUid of Object.keys(tokensHistory)) { - const totalBalance = state.wallet.getBalance(tokenUid); - // update token total balance - tokensBalance[tokenUid] = totalBalance; - } - - // in the end, sort (in place) all tx lists in descending order by timestamp - for (const txList of Object.values(tokensHistory)) { - txList.sort((elem1, elem2) => elem2.timestamp - elem1.timestamp); - } - - const address = state.wallet.getCurrentAddress(); + const { tokensHistory, tokensBalance, tokens } = action.payload; + const allTokens = new Set(tokens); + const address = state.wallet.getCurrentAddress().address; const addressIndex = state.wallet.getAddressIndex(address); return { @@ -226,7 +184,6 @@ const onLoadWalletSuccess = (state, action) => { loadingAddresses: false, lastSharedAddress: address, lastSharedIndex: addressIndex, - addressesTxs: newAddressesTxs, allTokens, }; }; @@ -246,7 +203,7 @@ const addTxToSortedList = (tokenUid, tx, txTokenBalance, currentHistory) => { // If is_voided changed, we update the tx in the history // otherwise we just return the currentHistory without change if (tx.is_voided !== currentHistory[i].isVoided) { - const txHistory = getTxHistoryFromTx(tx, tokenUid, txTokenBalance); + const txHistory = getTxHistoryFromWSTx(tx, tokenUid, txTokenBalance); // return new object so redux triggers update const newHistory = [...currentHistory]; newHistory[i] = txHistory; @@ -264,7 +221,7 @@ const addTxToSortedList = (tokenUid, tx, txTokenBalance, currentHistory) => { index = i + 1; } } - const txHistory = getTxHistoryFromTx(tx, tokenUid, txTokenBalance); + const txHistory = getTxHistoryFromWSTx(tx, tokenUid, txTokenBalance); // return new object so redux triggers update const newHistory = [...currentHistory]; newHistory.splice(index, 0, txHistory); @@ -276,11 +233,9 @@ const addTxToSortedList = (tokenUid, tx, txTokenBalance, currentHistory) => { * because it might have been voided */ const onUpdateTx = (state, action) => { - const { tx } = action.payload; - let changed = false; + const { tx, updatedBalanceMap } = action.payload; const updatedHistoryMap = {}; - const updatedBalanceMap = {}; const balances = state.wallet.getTxBalance(tx, { includeAuthorities: true }); for (const [tokenUid, tokenTxBalance] of Object.entries(balances)) { @@ -293,12 +248,8 @@ const onUpdateTx = (state, action) => { return el.tx_id === tx.tx_id; }); - // We update the balance and the history - const totalBalance = state.wallet.getBalance(tokenUid); - updatedBalanceMap[tokenUid] = totalBalance; - const newHistory = [...currentHistory]; - newHistory[txIndex] = getTxHistoryFromTx(tx, tokenUid, tokenTxBalance) + newHistory[txIndex] = getTxHistoryFromWSTx(tx, tokenUid, tokenTxBalance) updatedHistoryMap[tokenUid] = newHistory; } @@ -315,11 +266,10 @@ const onUpdateTx = (state, action) => { * Updates the history and balance when a new tx arrives */ const onNewTx = (state, action) => { - const { tx } = action.payload; + const { tx, updatedBalanceMap } = action.payload; const allTokens = state.allTokens; const updatedHistoryMap = {}; - const updatedBalanceMap = {}; const balances = state.wallet.getTxBalance(tx, { includeAuthorities: true }); // we now loop through all tokens present in the new tx to get the new history and balance @@ -329,33 +279,18 @@ const onNewTx = (state, action) => { const currentHistory = state.tokensHistory[tokenUid] || []; const newTokenHistory = addTxToSortedList(tokenUid, tx, tokenTxBalance, currentHistory); updatedHistoryMap[tokenUid] = newTokenHistory; - // totalBalance should not be confused with tokenTxBalance. The latter is the balance of the new - // tx, while the former is the total balance of the token, considering all tx history - const totalBalance = state.wallet.getBalance(tokenUid); - updatedBalanceMap[tokenUid] = totalBalance; } const newTokensHistory = Object.assign({}, state.tokensHistory, updatedHistoryMap); const newTokensBalance = Object.assign({}, state.tokensBalance, updatedBalanceMap); - const address = state.wallet.getCurrentAddress(); + const address = state.wallet.getCurrentAddress().address; const addressIndex = state.wallet.getAddressIndex(address); - const newAddressesTxs = Object.assign({}, state.addressesTxs); - const txAddresses = state.wallet.getTxAddresses(tx); - for (const addr of txAddresses) { - if (addr in newAddressesTxs) { - newAddressesTxs[addr] += 1; - } else { - newAddressesTxs[addr] = 1; - } - } - return Object.assign({}, state, { tokensHistory: newTokensHistory, tokensBalance: newTokensBalance, lastSharedAddress: address, lastSharedIndex: addressIndex, - addressesTxs: newAddressesTxs, allTokens, }); }; @@ -376,4 +311,40 @@ const onCleanData = (state, action) => { }); }; +/** + * Update token history after fetching more data in pagination + */ +const onUpdateTokenHistory = (state, action) => { + const { token, newHistory } = action.payload; + const currentHistory = state.tokensHistory[token] || []; + + const updatedHistoryMap = {}; + updatedHistoryMap[token] = [...currentHistory, ...newHistory]; + const newTokensHistory = Object.assign({}, state.tokensHistory, updatedHistoryMap); + return { + ...state, + tokensHistory: newTokensHistory, + }; +}; + +/** + * Update height value on redux + * If value is different from last value we also update HTR balance + */ +const onUpdateHeight = (state, action) => { + if (action.payload.height !== state.height) { + const tokensBalance = {}; + const { uid } = hathorLib.constants.HATHOR_TOKEN_CONFIG; + tokensBalance[uid] = action.payload.htrUpdatedBalance; + const newTokensBalance = Object.assign({}, state.tokensBalance, tokensBalance); + return { + ...state, + tokensBalance: newTokensBalance, + height: action.payload.height, + }; + } + + return state; +}; + export default rootReducer; diff --git a/src/screens/AddressList.js b/src/screens/AddressList.js index fc1b1ea9..6a544902 100644 --- a/src/screens/AddressList.js +++ b/src/screens/AddressList.js @@ -18,8 +18,7 @@ import hathorLib from '@hathor/wallet-lib'; const mapStateToProps = (state) => { return { - wallet: state.wallet, - addressesTxs: state.addressesTxs, + wallet: state.wallet }; }; @@ -44,8 +43,20 @@ class AddressList extends React.Component { filtered: false, } - componentDidMount = () => { - const addresses = this.props.wallet.getAllAddresses(); + componentDidMount = async () => { + const addresses = []; + const iterator = this.props.wallet.getAllAddresses(); + for (;;) { + const addressObj = await iterator.next(); + const { value, done } = addressObj; + + if (done) { + break; + } + + addresses.push(value); + } + this.setState({ addresses, filteredAddresses: addresses, totalPages: this.getTotalPages(addresses) }); } @@ -89,7 +100,7 @@ class AddressList extends React.Component { if (text) { if (hathorLib.transaction.isAddressValid(text)) { for (const addr of this.state.addresses) { - if (addr === text) { + if (addr.address === text) { this.setState({ filtered: true, filteredAddresses: [addr], totalPages: 1, page: 1 }); return } @@ -144,12 +155,12 @@ class AddressList extends React.Component { const renderData = () => { const startIndex = (this.state.page - 1) * WALLET_HISTORY_COUNT; const endIndex = startIndex + WALLET_HISTORY_COUNT; - return this.state.filteredAddresses.slice(startIndex, endIndex).map((address) => { + return this.state.filteredAddresses.slice(startIndex, endIndex).map((addressObj) => { return ( - - this.goToAddressSearch(e, address)}>{address} - {this.props.wallet.getAddressIndex(address)} - {address in this.props.addressesTxs ? this.props.addressesTxs[address] : 0} + + this.goToAddressSearch(e, addressObj.address)}>{addressObj.address} + {addressObj.index} + {addressObj.transactions} ) }); diff --git a/src/screens/CreateToken.js b/src/screens/CreateToken.js index 5470d8bc..b3a10a66 100644 --- a/src/screens/CreateToken.js +++ b/src/screens/CreateToken.js @@ -102,7 +102,7 @@ class CreateToken extends React.Component { * * @return {hathorLib.SendTransaction} SendTransaction object */ - prepareSendTransaction = (pin) => { + prepareSendTransaction = async (pin) => { // Get the address to send the created tokens let address = ''; if (this.refs.autoselectAddress.checked) { @@ -111,18 +111,17 @@ class CreateToken extends React.Component { address = this.refs.address.value; } - const ret = this.props.wallet.createNewToken( - this.refs.shortName.value, - this.refs.symbol.value, - wallet.decimalToInteger(this.state.amount), - address, - { startMiningTx: false, pinCode: pin } - ) - - if (ret.success) { - return ret.sendTransaction; - } else { - this.setState({ errorMessage: ret.message }); + let transaction; + try { + transaction = await this.props.wallet.prepareCreateNewToken( + this.refs.shortName.value, + this.refs.symbol.value, + wallet.decimalToInteger(this.state.amount), + { address, pinCode: pin } + ); + return new hathorLib.SendTransaction({ transaction, pin }); + } catch (e) { + this.setState({ errorMessage: e.message }); } } @@ -132,14 +131,16 @@ class CreateToken extends React.Component { * @param {Object} tx Create token transaction data */ onTokenCreateSuccess = (tx) => { + const name = this.refs.shortName.value; + const symbol = this.refs.symbol.value; const token = { uid: tx.hash, - name: this.refs.shortName.value, - symbol: this.refs.symbol.value + name, + symbol }; // Update redux with added token - tokens.saveTokenRedux(token.uid); + tokens.addToken(token.uid, name, symbol); // Must update the shared address, in case we have used one for the change wallet.updateSharedAddress(); this.showAlert(token); diff --git a/src/screens/SendTokens.js b/src/screens/SendTokens.js index 649b8973..3fc478f1 100644 --- a/src/screens/SendTokens.js +++ b/src/screens/SendTokens.js @@ -162,7 +162,8 @@ class SendTokens extends React.Component { try { // Prepare data and submit job to tx mining API this.data = hathorLib.transaction.prepareData(data, null, {getSignature: false, completeTx: false}); - this.sendTransaction = new hathorLib.SendTransaction({data: this.data}); + const transaction = hathorLib.helpersUtils.createTxFromData(this.data); + this.sendTransaction = new hathorLib.SendTransaction({ transaction }); this.setState({ ledgerStep: 1, ledgerModalTitle: t`Sending transaction` }); } catch(e) { this.handleSendError(e); @@ -253,13 +254,8 @@ class SendTokens extends React.Component { * * @return {SendTransaction} SendTransaction object, in case of success, null otherwise */ - prepareSendTransaction = (pin) => { - const ret = this.props.wallet.sendManyOutputsTransaction(this.data.outputs, this.data.inputs, null, { startMiningTx: false, pinCode: pin }); - if (ret.success) { - return ret.sendTransaction; - } else { - this.setState({ errorMessage: ret.message, ledgerStep: 0 }); - } + prepareSendTransaction = async (pin) => { + return new hathorLib.SendTransaction({ outputs: this.data.outputs, inputs: this.data.inputs, pin }); } /** diff --git a/src/utils/wallet.js b/src/utils/wallet.js index 921d3eb2..3eda9c1c 100644 --- a/src/utils/wallet.js +++ b/src/utils/wallet.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { SENTRY_DSN, DEBUG_LOCAL_DATA_KEYS } from '../constants'; +import { SENTRY_DSN, DEBUG_LOCAL_DATA_KEYS, WALLET_HISTORY_COUNT } from '../constants'; import STORE from '../storageInstance'; import store from '../store/index'; import { @@ -21,7 +21,8 @@ import { sharedAddressUpdate, reloadData, cleanData, - changeServer + changeServer, + updateTokenHistory, } from '../actions/index'; import { helpers, @@ -100,6 +101,116 @@ const wallet = { return this.startWallet(words, passphrase, pin, password, routerHistory); }, + /** + * Map token history object to the expected object in the wallet redux data + * + * tx {Object} history data element + * tokenUid {String} token uid + */ + mapTokenHistory(tx, tokenUid) { + return { + tx_id: tx.txId, + timestamp: tx.timestamp, + tokenUid, + balance: tx.balance, + // in wallet service this comes as 0/1 and in the full node comes with true/false + is_voided: Boolean(tx.voided), + version: tx.version, + } + }, + + /** + * Get all tokens that this wallet has any transaction and fetch balance/history for each of them + * We could do a lazy history load only when the user selects to see the token + * but this would change the behaviour of the wallet and was not the goal of this moment + * We should do this in the future anwyay + * + * wallet {HathorWallet} wallet object + */ + async fetchWalletData(wallet) { + // First we get the tokens in the wallet + const tokens = await wallet.getTokens(); + + const tokensHistory = {}; + const tokensBalance = {}; + // Then for each token we get the balance and history + for (const token of tokens) { + /* eslint-disable no-await-in-loop */ + const balance = await wallet.getBalance(token); + const tokenBalance = balance[0].balance; + tokensBalance[token] = { available: tokenBalance.unlocked, locked: tokenBalance.locked }; + // We fetch history count of 5 pages and then we fetch a new page each 'Next' button clicked + const history = await wallet.getTxHistory({ token_id: token, count: 5 * WALLET_HISTORY_COUNT }); + tokensHistory[token] = history.map((element) => this.mapTokenHistory(element, token)); + /* eslint-enable no-await-in-loop */ + } + + // Then we get from the addresses iterator all addresses + return { tokensHistory, tokensBalance, tokens }; + }, + + /** + * Fetch paginated history for specific token + * + * wallet {HathorWallet} wallet object + * token {string} Token uid + * history {Array} current token history array + */ + async fetchMoreHistory(wallet, token, history) { + const newHistory = await wallet.getTxHistory({ token_id: token, skip: history.length, count: WALLET_HISTORY_COUNT }); + const newHistoryObjects = newHistory.map((element) => this.mapTokenHistory(element, token)); + + if (newHistoryObjects.length) { + store.dispatch(updateTokenHistory(token, newHistoryObjects)); + } + + return newHistoryObjects; + }, + + /** + * After a new transaction arrives in the websocket we must + * fetch the new balance for each token on it and use + * this new data to update redux info + * + * wallet {HathorWallet} wallet object + * tx {Object} full transaction object from the websocket + */ + async fetchNewTxTokenBalance(wallet, tx) { + const updatedBalanceMap = {}; + const balances = wallet.getTxBalance(tx); + // we now loop through all tokens present in the new tx to get the new balance + for (const [tokenUid, tokenTxBalance] of Object.entries(balances)) { + updatedBalanceMap[tokenUid] = await this.fetchTokenBalance(wallet, tokenUid); + } + return updatedBalanceMap; + }, + + /** + * Method that fetches the balance of a token + * and pre process for the expected format + * + * wallet {HathorWallet} wallet object + * uid {String} Token uid to fetch balance + */ + async fetchTokenBalance(wallet, uid) { + const balance = await wallet.getBalance(uid); + const tokenBalance = balance[0].balance; + return { available: tokenBalance.unlocked, locked: tokenBalance.locked }; + }, + + /** + * Fetch HTR balance + * + * wallet {HathorWallet} wallet object + */ + async fetchNewHTRBalance(wallet) { + if (wallet.isReady()) { + // Need to update tokensBalance if wallet is ready + const { uid } = hathorConstants.HATHOR_TOKEN_CONFIG; + return await this.fetchTokenBalance(wallet, uid); + } + }, + /** * Start a new HD wallet with new private key * Encrypt this private key and save data in localStorage @@ -163,14 +274,14 @@ const wallet = { const serverInfo = await wallet.start({ pinCode: pin, password }); // TODO should we save server info? //store.dispatch(setServerInfo(serverInfo)); - wallet.on('state', (state) => { + wallet.on('state', async (state) => { if (state === HathorWallet.ERROR) { // ERROR // TODO Should we show an error screen and try to restart the wallet? - } else if (state === HathorWallet.READY) { + } else if (wallet.isReady()) { // READY - const historyTransactions = wallet.getTxHistory(); - store.dispatch(loadWalletSuccess(historyTransactions)); + const { tokensHistory, tokensBalance, tokens } = await this.fetchWalletData(wallet); + store.dispatch(loadWalletSuccess(tokensHistory, tokensBalance, tokens)); } }); @@ -188,19 +299,30 @@ const wallet = { routerHistory.push(`/transaction/${tx.tx_id}/`); } } - store.dispatch(newTx(tx)); + // Fetch new balance for each token in the tx and update redux + this.fetchNewTxTokenBalance(wallet, tx).then((updatedBalanceMap) => { + store.dispatch(newTx(tx, updatedBalanceMap)); + }); }); wallet.on('update-tx', (tx) => { - store.dispatch(updateTx(tx)); + this.fetchNewTxTokenBalance(wallet, tx).then((updatedBalanceMap) => { + store.dispatch(updateTx(tx, updatedBalanceMap)); + }); }); - this.setConnectionEvents(connection); + this.setConnectionEvents(connection, wallet); }, - setConnectionEvents(connection) { + setConnectionEvents(connection, wallet) { connection.on('best-block-update', (height) => { - store.dispatch(updateHeight({ height })); + // HTR balance might have updated because of new height + // and some block HTR might have unlocked + this.fetchNewHTRBalance(wallet).then((htrUpdatedBalance) => { + if (htrUpdatedBalance) { + store.dispatch(updateHeight(height, htrUpdatedBalance)); + } + }); }); connection.on('state', (state) => { From 1cf95776f968d9a134d1b08fefcc137e1ccc1c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Tue, 31 Aug 2021 11:39:52 -0300 Subject: [PATCH 17/25] chore: update ledger QA steps (#223) --- QA.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/QA.md b/QA.md index 794350b8..7a44e43d 100644 --- a/QA.md +++ b/QA.md @@ -168,9 +168,9 @@ You can connect your wallet to the testnet (https://node1.foxtrot.testnet.hathor 1. Choose 'Hardware Wallet'. 1. Wallet should go through both steps and show 'Step 1/2' and 'Step 2/2'. 1. On your Ledger, check it is requesting your authorization. - 1. Deny the authorization request on Ledger (left button). It should show an error on the wallet. + 1. Deny the authorization request on Ledger (scroll with left or right and click both buttons on "Reject" step). It should show an error on the wallet. 1. Click 'Try again'. It goes through both steps and asks for authorization again. - 1. Grant authorization request (right button). It will proceed to 'Loading transactions' screen. + 1. Grant authorization request (click both buttons on the "Approve" step). It will proceed to 'Loading transactions' screen. 1. **Ledger wallet screen** 1. On the main wallet screen, confirm 'Address to receive tokens' is truncated (eg: 'HGZmqThwa6...'). There should be a 'Show full address' button next to it. @@ -178,7 +178,7 @@ You can connect your wallet to the testnet (https://node1.foxtrot.testnet.hathor 1. Click on 'Show full address'. A modal will display asking you to compare the address on the wallet and on Ledger. 1. Check that the modal cannot be closed, not even by clicking outside it. 1. Confirm that both addresses (on wallet and Ledger) are the same. Copy this address on the computed. - 1. Click both buttons on Ledger. Modal should close. + 1. On the Ledger click both buttons on the "Approve" step. Modal should close. 1. Send 10.00 HTR from another wallet to the copied address. 1. Check that the transaction appears in the list, the balance was updated and the address also changed. 1. Click on 'Generate new address' and validate it has changed. @@ -192,10 +192,10 @@ You can connect your wallet to the testnet (https://node1.foxtrot.testnet.hathor 1. Click on 'Add another token' button. A modal is displayed saying the action is not supported. 1. Try sending a transaction to an invalid address. An error message should show up on the wallet. Nothing should be displayed on Ledger. 1. Send tokens to both copied addresses in the same transaction (2 outputs). A modal will be displayed asking to confirm operation on Ledger. - 1. On Ledger, it should show 'Output 1/2' on the first line and the address + amount on the second line. After clicking both buttons, it shows 'Output 2/2' and the other output (address + value). - 1. Clicking both buttons one more time will display the confirmation screen. Deny the transaction (left button). The modal will hide and an error message should appear on the wallet. - 1. Click to send the transaction again. This time, approve it on the confirmation screen (right button). Ledger screen will show 'Processing...' for a while and the desktop wallet will display the modal while solving proof of work. - 1. After transaction is completed, it closes the modal and goes back to main wallet screen. The first transaction on the list must have amount 0.00. + 1. On Ledger, it should show 'Output 1/2' on the first step the address on the second and the amount on the third step (steps being from left to right), then an "Approve" and "Reject" steps. After clicking both buttons on the "Approve", it shows 'Output 2/2' and the other output (address and ammount). + 1. Clicking both buttons on the "Approve" step one more time will display the confirmation screen. Reject signing the transaction. The modal will hide and an error message should appear on the wallet. + 1. Click to send the transaction again. This time, approve it on the signing screen. Ledger screen will show 'Processing' for a while and the desktop wallet will display the modal while solving proof of work. + 1. After transaction is completed, it closes the modal and goes back to main wallet screen. The first transaction on the list must have value 0.00. 1. **Ledger misc** 1. Go to the settings screen. From e7dc14580c381dd9c60dc8dbf01990675ae90a7e Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 9 Sep 2021 12:30:35 -0300 Subject: [PATCH 18/25] fix: package lock rollback for node v8.9.0 format (#224) * fix: package lock rollback for node v8.9.0 format * chore: add two different scripts to run electron-dev --- package-lock.json | 96 ++++++++++++++++++----------------------------- package.json | 3 +- 2 files changed, 38 insertions(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16104b12..9e84f716 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1147,64 +1147,6 @@ "lodash": "4.17.15", "long": "4.0.0", "ws": "7.2.3" - }, - "dependencies": { - "bitcore-lib": { - "version": "8.25.10", - "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.10.tgz", - "integrity": "sha512-MyHpSg7aFRHe359RA/gdkaQAal3NswYZTLEuu0tGX1RGWXAYN9i/24fsjPqVKj+z0ua+gzAT7aQs0KiKXWCgKA==", - "requires": { - "bech32": "1.1.3", - "bn.js": "4.11.8", - "bs58": "4.0.1", - "buffer-compare": "1.1.1", - "elliptic": "6.5.4", - "inherits": "2.0.1", - "lodash": "4.17.21" - }, - "dependencies": { - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - } - } - }, - "bitcore-mnemonic": { - "version": "8.25.10", - "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.10.tgz", - "integrity": "sha512-FeXxO37BLV5JRvxPmVFB91zRHalavV8H4TdQGt1/hz0AkoPymIV68OkuB+TptpjeYgatcgKPoPvPhglJkTzFQQ==", - "requires": { - "bitcore-lib": "8.25.10", - "unorm": "1.6.0" - } - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "4.12.0", - "brorand": "1.1.0", - "hash.js": "1.1.7", - "hmac-drbg": "1.0.1", - "inherits": "2.0.4", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - } - } - } } }, "@jest/console": { @@ -3217,6 +3159,35 @@ "file-uri-to-path": "1.0.0" } }, + "bitcore-lib": { + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.10.tgz", + "integrity": "sha512-MyHpSg7aFRHe359RA/gdkaQAal3NswYZTLEuu0tGX1RGWXAYN9i/24fsjPqVKj+z0ua+gzAT7aQs0KiKXWCgKA==", + "requires": { + "bech32": "1.1.3", + "bn.js": "4.11.8", + "bs58": "4.0.1", + "buffer-compare": "1.1.1", + "inherits": "2.0.1", + "lodash": "4.17.21" + }, + "dependencies": { + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + } + } + }, + "bitcore-mnemonic": { + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.10.tgz", + "integrity": "sha512-FeXxO37BLV5JRvxPmVFB91zRHalavV8H4TdQGt1/hz0AkoPymIV68OkuB+TptpjeYgatcgKPoPvPhglJkTzFQQ==", + "requires": { + "bitcore-lib": "8.25.10", + "unorm": "1.6.0" + } + }, "bl": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", @@ -7137,6 +7108,7 @@ "requires": { "anymatch": "3.1.1", "braces": "3.0.2", + "fsevents": "2.1.2", "glob-parent": "5.1.0", "is-binary-path": "2.1.0", "is-glob": "4.0.1", @@ -7155,7 +7127,8 @@ "fsevents": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==" + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "optional": true }, "glob-parent": { "version": "5.1.0", @@ -11343,6 +11316,7 @@ "requires": { "anymatch": "3.1.1", "braces": "3.0.2", + "fsevents": "2.1.2", "glob-parent": "5.1.0", "is-binary-path": "2.1.0", "is-glob": "4.0.1", @@ -11406,7 +11380,9 @@ "fsevents": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==" + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true }, "get-stream": { "version": "3.0.0", diff --git a/package.json b/package.json index 7c3598b1..27a86d79 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,8 @@ "electron-pack-linux": "electron-builder --linux -c.extraMetadata.main=build/electron.js", "electron-pack-win": "electron-builder --win -c.extraMetadata.main=build/electron.js", "watch-electron": "ELECTRON_START_URL=http://localhost:3000 NODE_ENV=dev nodemon --watch ./public/**/* --watch . --exec 'npm run electron'", - "electron-dev": "npx concurrently 'npx cross-env BROWSER=none npm run start' 'npx wait-on http://localhost:3000/ && npx cross-env ELECTRON_START_URL=http://localhost:3000 NODE_ENV=dev electron --inspect=5858 .'", + "electron-dev": "ELECTRON_START_URL=http://localhost:3000 NODE_ENV=dev electron --inspect=5858 .", + "electron-dev-concurrently": "npx concurrently 'npx cross-env BROWSER=none npm run start' 'npx wait-on http://localhost:3000/ && npx cross-env ELECTRON_START_URL=http://localhost:3000 NODE_ENV=dev electron --inspect=5858 .'", "locale-update-pot": "ttag extract -o ./locale/texts.pot ./src/", "postinstall": "npx cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=true npm run electron-deps" }, From 5b3e03e1316f635b7366e797b710e0466b4eda8e Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 17 Sep 2021 18:06:10 -0300 Subject: [PATCH 19/25] feat: add support for creating NFT (#222) * feat: add support for creating NFT * style: add css style for create nft form * fix: translation variable should be calculated before the string * refactor: using createRef instead of ref because it's deprecated on React * feat: add flag to easily enable/disable NFT creation * refactor: add space between // comment * refactor: using variable already created instead of expression to get nftFee again * feat: limit size of NFT data field * feat: improving max size message for nft data * refactor: using constant var to write down the max nft data size * chore: improve text on create NFT screen * feat: improve nft data helper text --- src/App.js | 2 + src/constants.js | 15 ++ src/index.scss | 7 +- src/screens/CreateNFT.js | 340 ++++++++++++++++++++++++++++++++++++ src/screens/CustomTokens.js | 13 ++ src/utils/tokens.js | 10 ++ 6 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 src/screens/CreateNFT.js diff --git a/src/App.js b/src/App.js index 0887b160..e8487ac8 100644 --- a/src/App.js +++ b/src/App.js @@ -10,6 +10,7 @@ import { Switch, Route, Redirect } from 'react-router-dom'; import Wallet from './screens/Wallet'; import SendTokens from './screens/SendTokens'; import CreateToken from './screens/CreateToken'; +import CreateNFT from './screens/CreateNFT'; import Navigation from './components/Navigation'; import WaitVersion from './components/WaitVersion'; import TransactionDetail from './screens/TransactionDetail'; @@ -78,6 +79,7 @@ class Root extends React.Component { return ( + diff --git a/src/constants.js b/src/constants.js index a6fbe19c..45fa9452 100644 --- a/src/constants.js +++ b/src/constants.js @@ -105,6 +105,11 @@ export const TESTNET_EXPLORER_BASE_URL = "https://explorer.testnet.hathor.networ */ export const TOKEN_DEPOSIT_RFC_URL = "https://gitlab.com/HathorNetwork/rfcs/blob/master/text/0011-token-deposit.md"; +/** + * URL of NFT standard + */ +export const NFT_STANDARD_RFC_URL = "https://github.com/HathorNetwork/rfcs/blob/master/text/0032-nft-standard.md"; + export const HATHOR_WEBSITE_URL = "https://hathor.network/"; /** @@ -130,3 +135,13 @@ export const IPC_RENDERER = ipcRenderer; * Flag to hide/show elements after ledger integration is done */ export const LEDGER_ENABLED = false; + +/** + * Flag to hide/show create NFT button + */ +export const NFT_ENABLED = false; + +/** + * Maximum size of NFT data length + */ +export const NFT_DATA_MAX_SIZE = 150; diff --git a/src/index.scss b/src/index.scss index 1f5b7af4..d2e0e426 100644 --- a/src/index.scss +++ b/src/index.scss @@ -190,6 +190,10 @@ div.new-address-wrapper canvas { margin-bottom: 1rem; } +#formCreateNFT { + padding-bottom: 2rem; +} + .tx-input-wrapper textarea { margin: 1rem 0; } @@ -622,7 +626,8 @@ svg.svg-wrapper { width: 13rem; } -#formCreateToken .address-checkbox { +#formCreateToken .address-checkbox, #formCreateNFT .address-checkbox { + padding-left: 1rem; padding-top: 2rem; } diff --git a/src/screens/CreateNFT.js b/src/screens/CreateNFT.js new file mode 100644 index 00000000..56858408 --- /dev/null +++ b/src/screens/CreateNFT.js @@ -0,0 +1,340 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import $ from 'jquery'; +import { t } from 'ttag' +import { str2jsx } from '../utils/i18n'; + +import wallet from '../utils/wallet'; +import tokens from '../utils/tokens'; +import SpanFmt from '../components/SpanFmt'; +import ModalSendTx from '../components/ModalSendTx'; +import ModalAlert from '../components/ModalAlert'; +import { connect } from "react-redux"; +import BackButton from '../components/BackButton'; +import hathorLib from '@hathor/wallet-lib'; +import helpers from '../utils/helpers'; +import { NFT_STANDARD_RFC_URL, NFT_DATA_MAX_SIZE } from '../constants'; +import InputNumber from '../components/InputNumber'; + + +const mapStateToProps = (state) => { + const HTR_UID = hathorLib.constants.HATHOR_TOKEN_CONFIG.uid; + const htrBalance = HTR_UID in state.tokensBalance ? state.tokensBalance[HTR_UID].available : 0; + return { + htrBalance, + wallet: state.wallet, + }; +}; + + +/** + * Create an NFT + * + * @memberof Screens + */ +class CreateNFT extends React.Component { + constructor(props) { + super(props); + + this.addressDivRef = React.createRef(); + this.formCreateNFTRef = React.createRef(); + this.addressRef = React.createRef(); + this.autoselectAddressRef = React.createRef(); + this.nameRef = React.createRef(); + this.symbolRef = React.createRef(); + this.nftDataRef = React.createRef(); + this.createMintAuthorityRef = React.createRef(); + this.createMeltAuthorityRef = React.createRef(); + + /** + * errorMessage {string} Message to show when error happens on the form + * name {string} Name of the created token + * configurationString {string} Configuration string of the created token + * amount {number} Amount of tokens to create + */ + this.state = { + errorMessage: '', + name: '', + configurationString: '', + amount: null, + }; + } + + /** + * Validates if the create NFT form is valid + */ + formValid = () => { + const isValid = this.formCreateNFTRef.current.checkValidity(); + if (isValid) { + if (this.addressRef.current.value === '' && !this.autoselectAddressRef.current.checked) { + this.setState({ errorMessage: t`Must choose an address or auto select` }); + return false; + } + + // Validating maximum amount + if (this.state.amount > hathorLib.constants.MAX_OUTPUT_VALUE) { + this.setState({ errorMessage: t`Maximum NFT units to mint is ${hathorLib.constants.MAX_OUTPUT_VALUE}` }); + return false; + } + return true; + } else { + this.formCreateNFTRef.current.classList.add('was-validated') + } + } + + /** + * Opens PIN modal if form is valid + */ + onClickCreate = () => { + if (!this.formValid()) { + return; + } + + this.setState({ errorMessage: '' }); + $('#pinModal').modal('show'); + } + + /** + * Prepare create NFT transaction data after PIN is validated + * + * @param {String} pin PIN written by the user + * + * @return {hathorLib.SendTransaction} SendTransaction object + */ + prepareSendTransaction = async (pin) => { + // Get the address to send the created tokens + let address = ''; + if (this.autoselectAddressRef.current.checked) { + address = hathorLib.wallet.getAddressToUse(); + } else { + address = this.addressRef.current.value; + } + + const name = this.nameRef.current.value; + const symbol = this.symbolRef.current.value; + const nftData = this.nftDataRef.current.value; + const createMint = this.createMintAuthorityRef.current.checked; + const createMelt = this.createMeltAuthorityRef.current.checked; + + let transaction; + try { + transaction = await this.props.wallet.prepareCreateNewToken( + name, + symbol, + parseInt(this.state.amount), + { + nftData, + address, + pinCode: pin, + createMint, + createMelt, + } + ); + return new hathorLib.SendTransaction({ transaction, pin }); + } catch (e) { + this.setState({ errorMessage: e.message }); + } + } + + /** + * Method executed if token is created with success + * + * @param {Object} tx Create token transaction data + */ + onTokenCreateSuccess = (tx) => { + const name = this.nameRef.current.value; + const symbol = this.symbolRef.current.value; + const token = { + uid: tx.hash, + name, + symbol + }; + + // Update redux with added token + tokens.addToken(token.uid, name, symbol); + // Must update the shared address, in case we have used one for the change + wallet.updateSharedAddress(); + this.showAlert(token); + } + + + /** + * Method called after creating a token, then show an alert with explanation of the token + * + * @param {Object} token Object with {uid, name, symbol} + */ + showAlert = (token) => { + this.setState({ name: token.name, configurationString: hathorLib.tokens.getConfigurationString(token.uid, token.name, token.symbol) }, () => { + $('#alertModal').modal('show'); + }); + } + + /** + * Method called after clicking the button in the alert modal, then redirects to the wallet screen + */ + alertButtonClick = () => { + $('#alertModal').on('hidden.bs.modal', (e) => { + this.props.history.push('/wallet/'); + }); + $('#alertModal').modal('hide'); + } + + /** + * Shows/hides address field depending on the checkbox click + * + * @param {Object} e Event for the address checkbox input change + */ + handleCheckboxAddress = (e) => { + const value = e.target.checked; + if (value) { + $(this.addressDivRef.current).hide(400); + } else { + $(this.addressDivRef.current).show(400); + } + } + + /** + * Handles amount input change + */ + onAmountChange = (e) => { + this.setState({ amount: e.target.value }); + } + + /** + * Method called when user clicked to see the NFT standard RFC + * + * @param {Object} e Event for the click + */ + goToRFC = (e) => { + e.preventDefault(); + helpers.openExternalURL(NFT_STANDARD_RFC_URL); + } + + /** + * Method called when user clicked to see the NFT guide + * + * @param {Object} e Event for the click + */ + goToNFTGuide = (e) => { + e.preventDefault(); + // TODO create this guide in the website (this will be later used as a medium post) + // helpers.openExternalURL(NFT_GUIDE_URL); + } + + render = () => { + const getAlertBody = () => { + return ( +
+

{t`Your NFT has been successfully created!`}

+

{t`You can share the following configuration string with other people to let them register your brand new NFT in their wallets.`}

+

{t`Remember to **make a backup** of this configuration string.`}

+

{this.state.configurationString}

+
+ ) + } + + const htrDeposit = hathorLib.tokens.getDepositPercentage() * 100; + const depositAmount = hathorLib.tokens.getDepositAmount(this.state.amount); + const nftFee = hathorLib.helpers.prettyValue(tokens.getNFTFee()); + + return ( +
+ +

Create NFT

+

{t`Here you will create a new NFT. After the creation, you will be able to send the units of this NFT to other addresses.`}

+

{t`NFTs share the address space with all other tokens, including HTR. This means that you can send and receive tokens using any valid address.`}

+

{t`Remember to make a backup of your new token's configuration string. You will need to send it to other people to allow them to use your NFT.`}

+

+ {str2jsx( + t`When creating and minting NFTs, a |bold:deposit of ${htrDeposit}%| in HTR is required and an additional |bold:fee of ${nftFee} HTR|. If these tokens are later melted, this HTR deposit will be returned (depending on the amount melted) and the fee will never be returned. Read more about the NFT standard |link:here|.`, + { + bold: (x, i) => {x}, + link: (x, i) => {x}, + } + )} +

+

+ {str2jsx( + t`The most common usage for an NFT is to represent a digital asset. Please see |link:this guide| that explains how to create an NFT data (including suggestions on how to upload files to the IPFS network) in the Hathor standard in order to be able to show your asset in our explorer.`, + { + link: (x, i) => {x}, + } + )} +

+
+
+
+
+ + + String that uniquely identify your NFT. For example, the IPFS link to your metadata file, a URI to your asset or any string. Max size: {NFT_DATA_MAX_SIZE} characters. +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+

HTR available: {hathorLib.helpers.prettyValue(this.props.htrBalance)} HTR

+

Deposit: {hathorLib.helpers.prettyValue(depositAmount)} HTR

+

Fee: {nftFee} HTR

+

Total: {hathorLib.helpers.prettyValue(tokens.getNFTFee() + depositAmount)} HTR

+ +
+

{this.state.errorMessage}

+ + +
+ ); + } +} + +export default connect(mapStateToProps)(CreateNFT); diff --git a/src/screens/CustomTokens.js b/src/screens/CustomTokens.js index 9dc131f1..372d5efa 100644 --- a/src/screens/CustomTokens.js +++ b/src/screens/CustomTokens.js @@ -14,6 +14,7 @@ import HathorAlert from '../components/HathorAlert'; import $ from 'jquery'; import BackButton from '../components/BackButton'; import ModalAlertNotSupported from '../components/ModalAlertNotSupported'; +import { NFT_ENABLED } from '../constants'; import hathorLib from '@hathor/wallet-lib'; @@ -49,6 +50,17 @@ class CustomTokens extends React.Component { } } + /** + * Triggered when user clicks on the Create NFT button + */ + createNFTClicked = () => { + if (hathorLib.wallet.isHardwareWallet()) { + $('#notSupported').modal('show'); + } else { + this.props.history.push('/create_nft/'); + } + } + render() { return (
@@ -59,6 +71,7 @@ class CustomTokens extends React.Component {

{t`If you want to use a custom token that already exists, you need to register this token in your Hathor Wallet. For this, you will need the custom token's Configuration String, which you can get from the creators of the token.`}

+ { NFT_ENABLED && }
diff --git a/src/utils/tokens.js b/src/utils/tokens.js index 8b1dd118..1749b1d7 100644 --- a/src/utils/tokens.js +++ b/src/utils/tokens.js @@ -104,6 +104,16 @@ const tokens = { return 0; } }, + + /** + * Returns the fee in HTR for creating an NFT + * + * @memberof Tokens + * @inner + */ + getNFTFee() { + return 1; + }, } export default tokens; From 806ca8aa88b0f578c5777909a9631666003ec486 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 22 Sep 2021 16:12:49 -0300 Subject: [PATCH 20/25] style: add vertical scroll for token bar body (#227) --- src/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.scss b/src/index.scss index d2e0e426..1e1eaa4e 100644 --- a/src/index.scss +++ b/src/index.scss @@ -572,6 +572,7 @@ svg.svg-wrapper { .token-bar .body { width: 100%; overflow-y: auto; + flex: 1 1; } .token-bar .token-wrapper { From 30ad8d0840c542831df0cf2e6e51c8c491d0e848 Mon Sep 17 00:00:00 2001 From: Marcelo Salhab Brogliato Date: Wed, 22 Sep 2021 18:44:48 -0700 Subject: [PATCH 21/25] feat(ledger): Enable Ledger support (#229) --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 45fa9452..e1ce11ef 100644 --- a/src/constants.js +++ b/src/constants.js @@ -134,7 +134,7 @@ export const IPC_RENDERER = ipcRenderer; /** * Flag to hide/show elements after ledger integration is done */ -export const LEDGER_ENABLED = false; +export const LEDGER_ENABLED = true; /** * Flag to hide/show create NFT button From 2d2731402c81447d1507548eebc6b3a7fbad495e Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 23 Sep 2021 11:29:06 -0300 Subject: [PATCH 22/25] feat: explain about developer mode and add link to ledger guide on medium (#230) * feat: explain about developer mode and add link to ledger guide on medium * chore: update pot file --- locale/texts.pot | 344 ++++++++++++++++++++++++-------------- src/constants.js | 5 + src/screens/WalletType.js | 18 +- 3 files changed, 239 insertions(+), 128 deletions(-) diff --git a/locale/texts.pot b/locale/texts.pot index ce81e36c..e1c52369 100644 --- a/locale/texts.pot +++ b/locale/texts.pot @@ -7,26 +7,27 @@ msgstr "" msgid "I want to reset my wallet" msgstr "" -#: src/screens/AddressList.js:138 +#: src/screens/AddressList.js:149 msgid "Search address" msgstr "" -#: src/screens/AddressList.js:162 +#: src/screens/AddressList.js:173 msgid "Addresses" msgstr "" #: src/components/OutputsWrapper.js:49 #: src/components/tokens/TokenMint.js:126 -#: src/screens/AddressList.js:169 -#: src/screens/CreateToken.js:277 +#: src/screens/AddressList.js:180 +#: src/screens/CreateNFT.js:304 +#: src/screens/CreateToken.js:278 msgid "Address" msgstr "" -#: src/screens/AddressList.js:170 +#: src/screens/AddressList.js:181 msgid "Index" msgstr "" -#: src/screens/AddressList.js:171 +#: src/screens/AddressList.js:182 msgid "Number of transactions" msgstr "" @@ -115,95 +116,176 @@ msgstr "" msgid "Set a passphrase" msgstr "" +#: src/screens/CreateNFT.js:76 #: src/screens/CreateToken.js:69 msgid "Must choose an address or auto select" msgstr "" -#: src/screens/CreateToken.js:77 +#: src/screens/CreateNFT.js:82 #, javascript-format -msgid "Maximum value to mint token is ${ max_output_value_str }" +msgid "Maximum NFT units to mint is ${ hathorLib.constants.MAX_OUTPUT_VALUE }" msgstr "" -#: src/screens/CreateToken.js:219 -msgid "Your token has been successfully created!" +#: src/screens/CreateNFT.js:234 +msgid "Your NFT has been successfully created!" msgstr "" -#: src/screens/CreateToken.js:220 +#: src/screens/CreateNFT.js:235 msgid "" "You can share the following configuration string with other people to let " -"them use your brand new token." +"them register your brand new NFT in their wallets." msgstr "" -#: src/screens/CreateToken.js:221 +#: src/screens/CreateNFT.js:236 +#: src/screens/CreateToken.js:222 msgid "Remember to **make a backup** of this configuration string." msgstr "" -#: src/screens/CreateToken.js:233 +#: src/screens/CreateNFT.js:250 msgid "" -"Here you will create a new customized token. After the creation, you will " -"be able to send this new token to other addresses." +"Here you will create a new NFT. After the creation, you will be able to " +"send the units of this NFT to other addresses." msgstr "" -#: src/screens/CreateToken.js:234 +#: src/screens/CreateNFT.js:251 msgid "" -"Custom tokens share the address space with all other tokens, including HTR. " -"This means that you can send and receive tokens using any valid address." +"NFTs share the address space with all other tokens, including HTR. This " +"means that you can send and receive tokens using any valid address." msgstr "" -#: src/screens/CreateToken.js:235 +#: src/screens/CreateNFT.js:252 msgid "" "Remember to make a backup of your new token's configuration string. You " -"will need to send it to other people to allow them to use your new token." +"will need to send it to other people to allow them to use your NFT." msgstr "" -#: src/screens/CreateToken.js:238 +#: src/screens/CreateNFT.js:255 #, javascript-format msgid "" -"When creating and minting tokens, a |bold:deposit of ${ htrDeposit }%| in " -"HTR is required. If these tokens are later melted, this HTR deposit will be " -"returned. Read more about it |link:here|." +"When creating and minting NFTs, a |bold:deposit of ${ htrDeposit }%| in HTR " +"is required and an additional |bold:fee of ${ nftFee } HTR|. If these " +"tokens are later melted, this HTR deposit will be returned (depending on " +"the amount melted) and the fee will never be returned. Read more about the " +"NFT standard |link:here|." +msgstr "" + +#: src/screens/CreateNFT.js:264 +msgid "" +"The most common usage for an NFT is to represent a digital asset. Please " +"see |link:this guide| that explains how to create an NFT data (including " +"suggestions on how to upload files to the IPFS network) in the Hathor " +"standard in order to be able to show your asset in our explorer." +msgstr "" + +#: src/screens/CreateNFT.js:274 +msgid "NFT Data" +msgstr "" + +#: src/screens/CreateNFT.js:275 +msgid "ipfs://..." msgstr "" #: src/components/ModalEditToken.js:87 -#: src/screens/CreateToken.js:249 +#: src/screens/CreateNFT.js:281 +#: src/screens/CreateToken.js:250 msgid "Short name" msgstr "" -#: src/screens/CreateToken.js:250 +#: src/screens/CreateNFT.js:282 +#: src/screens/CreateToken.js:251 msgid "MyCoin" msgstr "" #: src/components/ModalEditToken.js:90 -#: src/screens/CreateToken.js:253 +#: src/screens/CreateNFT.js:285 +#: src/screens/CreateToken.js:254 msgid "Symbol" msgstr "" -#: src/screens/CreateToken.js:254 +#: src/screens/CreateNFT.js:286 +#: src/screens/CreateToken.js:255 msgid "MYC (2-5 characters)" msgstr "" -#: src/screens/CreateToken.js:259 +#: src/screens/CreateNFT.js:291 +#: src/screens/CreateToken.js:260 msgid "Amount" msgstr "" -#: src/screens/CreateToken.js:271 +#: src/screens/CreateNFT.js:298 +#: src/screens/CreateToken.js:272 msgid "Select address automatically" msgstr "" -#: src/screens/CreateToken.js:276 +#: src/screens/CreateNFT.js:303 +#: src/screens/CreateToken.js:277 msgid "Destination address" msgstr "" -#: src/screens/CreateToken.js:285 +#: src/screens/CreateNFT.js:312 +msgid "Create a mint authority" +msgstr "" + +#: src/screens/CreateNFT.js:320 +msgid "Create a melt authority" +msgstr "" + +#: src/screens/CreateNFT.js:334 +#, javascript-format +msgid "NFT ${ _this.state.name } created" +msgstr "" + +#: src/screens/CreateToken.js:77 +#, javascript-format +msgid "Maximum value to mint token is ${ max_output_value_str }" +msgstr "" + +#: src/screens/CreateToken.js:220 +msgid "Your token has been successfully created!" +msgstr "" + +#: src/screens/CreateToken.js:221 +msgid "" +"You can share the following configuration string with other people to let " +"them use your brand new token." +msgstr "" + +#: src/screens/CreateToken.js:234 +msgid "" +"Here you will create a new customized token. After the creation, you will " +"be able to send this new token to other addresses." +msgstr "" + +#: src/screens/CreateToken.js:235 +msgid "" +"Custom tokens share the address space with all other tokens, including HTR. " +"This means that you can send and receive tokens using any valid address." +msgstr "" + +#: src/screens/CreateToken.js:236 +msgid "" +"Remember to make a backup of your new token's configuration string. You " +"will need to send it to other people to allow them to use your new token." +msgstr "" + +#: src/screens/CreateToken.js:239 +#, javascript-format +msgid "" +"When creating and minting tokens, a |bold:deposit of ${ htrDeposit }%| in " +"HTR is required. If these tokens are later melted, this HTR deposit will be " +"returned. Read more about it |link:here|." +msgstr "" + +#: src/screens/CreateToken.js:286 #, javascript-format msgid "Token ${ _this.state.name } created" msgstr "" -#: src/screens/CustomTokens.js:56 +#: src/screens/CustomTokens.js:68 msgid "Custom Tokens" msgstr "" -#: src/screens/CustomTokens.js:57 +#: src/screens/CustomTokens.js:69 msgid "" "You can create your own digital token with customized specifications on " "Hathor Network with only a few clicks. They will fully work under the same " @@ -212,13 +294,13 @@ msgid "" "price of the native HTR token, and they can serve multiple purposes." msgstr "" -#: src/screens/CustomTokens.js:58 +#: src/screens/CustomTokens.js:70 msgid "" "Every custom token has a unique **Configuration String** which must be " "shared with all other people that will use the custom token." msgstr "" -#: src/screens/CustomTokens.js:59 +#: src/screens/CustomTokens.js:71 msgid "" "If you want to use a custom token that already exists, you need to register " "this token in your Hathor Wallet. For this, you will need the custom " @@ -226,15 +308,19 @@ msgid "" "token." msgstr "" -#: src/screens/CustomTokens.js:61 +#: src/screens/CustomTokens.js:73 msgid "Create a new token" msgstr "" -#: src/screens/CustomTokens.js:62 +#: src/screens/CustomTokens.js:74 +msgid "Create an NFT" +msgstr "" + +#: src/screens/CustomTokens.js:75 msgid "Register a token" msgstr "" -#: src/screens/CustomTokens.js:65 +#: src/screens/CustomTokens.js:78 msgid "Token registered with success!" msgstr "" @@ -353,31 +439,31 @@ msgstr "" msgid "You tried to access a page that does not exist in Hathor Wallet" msgstr "" -#: src/screens/SendTokens.js:161 -#: src/screens/SendTokens.js:393 +#: src/screens/SendTokens.js:167 +#: src/screens/SendTokens.js:394 msgid "Sending transaction" msgstr "" -#: src/screens/SendTokens.js:299 +#: src/screens/SendTokens.js:300 msgid "All your tokens were already added" msgstr "" -#: src/screens/SendTokens.js:361 +#: src/screens/SendTokens.js:362 msgid "Add another token" msgstr "" -#: src/screens/SendTokens.js:362 -#: src/screens/SendTokens.js:391 +#: src/screens/SendTokens.js:363 +#: src/screens/SendTokens.js:392 msgid "Send Tokens" msgstr "" -#: src/screens/SendTokens.js:374 +#: src/screens/SendTokens.js:375 msgid "" "Please go to you Ledger and validate each output of your transaction. Press " "both buttons in case the output is correct." msgstr "" -#: src/screens/SendTokens.js:375 +#: src/screens/SendTokens.js:376 msgid "In the end, a final screen will ask you to confirm sending the transaction." msgstr "" @@ -631,7 +717,7 @@ msgid "Please update you API version and try again" msgstr "" #: src/screens/VersionError.js:56 -#: src/screens/WalletType.js:192 +#: src/screens/WalletType.js:195 msgid "Try again" msgstr "" @@ -680,46 +766,49 @@ msgstr "" msgid "Unregister token" msgstr "" -#: src/screens/WalletType.js:174 +#: src/screens/WalletType.js:173 msgid "Hathor Wallet supports two types of wallet: software and hardware." msgstr "" #: src/screens/WalletType.js:175 msgid "" -"**Hardware wallets** are dedicated external devices that store your private " -"information. We currently support the Ledger hardware wallet. **Software " -"wallets**, on the other hand, store the information on your computer." +"|bold:Hardware wallets| are dedicated external devices that store your " +"private information. We currently support the Ledger hardware wallet and " +"the ledger app must be installed in developer mode for now. For a tutorial " +"about how to use the ledger app with Hathor Wallet check out |fn:this page|." msgstr "" -#: src/screens/WalletType.js:177 -msgid "For more information on different types of wallet, check out |fn:this page|." +#: src/screens/WalletType.js:182 +msgid "" +"**Software wallets**, on the other hand, store the information on your " +"computer." msgstr "" -#: src/screens/WalletType.js:181 +#: src/screens/WalletType.js:184 msgid "Hardware wallet" msgstr "" -#: src/screens/WalletType.js:182 +#: src/screens/WalletType.js:185 msgid "Software wallet" msgstr "" -#: src/screens/WalletType.js:199 +#: src/screens/WalletType.js:202 msgid "You need to authorize the operation on your Ledger." msgstr "" -#: src/screens/WalletType.js:201 +#: src/screens/WalletType.js:204 msgid "Please connect your Ledger device to the computer and open the Hathor app." msgstr "" -#: src/screens/WalletType.js:207 +#: src/screens/WalletType.js:210 msgid "Step 2/2" msgstr "" -#: src/screens/WalletType.js:209 +#: src/screens/WalletType.js:212 msgid "Step 1/2" msgstr "" -#: src/screens/WalletType.js:216 +#: src/screens/WalletType.js:219 msgid "Connecting to Ledger" msgstr "" @@ -851,7 +940,7 @@ msgstr "" #: src/components/ModalEditToken.js:95 #: src/components/ModalSendTx.js:181 #: src/components/ModalUnregisteredTokenInfo.js:113 -#: src/components/tokens/TokenAction.js:134 +#: src/components/tokens/TokenAction.js:133 msgid "Cancel" msgstr "" @@ -890,7 +979,7 @@ msgstr "" #: src/components/ModalAddressQRCode.js:93 #: src/components/TokenGeneralInfo.js:145 -#: src/components/WalletAddress.js:149 +#: src/components/WalletAddress.js:151 msgid "Copy to clipboard" msgstr "" @@ -1115,7 +1204,7 @@ msgid "Public Explorer" msgstr "" #: src/components/OutputsWrapper.js:54 -#: src/components/SendTokensOne.js:186 +#: src/components/SendTokensOne.js:210 msgid "This feature is disabled for hardware wallet" msgstr "" @@ -1187,27 +1276,27 @@ msgstr "" msgid "Retry request" msgstr "" -#: src/components/SendTokensOne.js:186 +#: src/components/SendTokensOne.js:210 msgid "Select token" msgstr "" -#: src/components/SendTokensOne.js:198 +#: src/components/SendTokensOne.js:222 msgid "Balance available: " msgstr "" -#: src/components/SendTokensOne.js:204 +#: src/components/SendTokensOne.js:228 msgid "Token:" msgstr "" -#: src/components/SendTokensOne.js:207 +#: src/components/SendTokensOne.js:231 msgid "Remove" msgstr "" -#: src/components/SendTokensOne.js:216 +#: src/components/SendTokensOne.js:240 msgid "Choose inputs automatically" msgstr "" -#: src/components/SendTokensOne.js:220 +#: src/components/SendTokensOne.js:244 msgid "Inputs" msgstr "" @@ -1223,6 +1312,18 @@ msgstr "" msgid "Your transaction was sent successfully!" msgstr "" +#: src/components/SendTxHandler.js:89 +msgid "There was an unexpected error when pushing the transaction to the network." +msgstr "" + +#: src/components/SendTxHandler.js:90 +msgid "There are no utxos available to fill the transaction." +msgstr "" + +#: src/components/SendTxHandler.js:91 +msgid "The selected inputs are invalid." +msgstr "" + #: src/components/ServerStatus.js:34 msgid "Online" msgstr "" @@ -1304,17 +1405,17 @@ msgstr "" msgid "Your balance available:" msgstr "" -#: src/components/TokenBar.js:135 +#: src/components/TokenBar.js:139 msgid "Tokens" msgstr "" -#: src/components/TokenBar.js:163 -#: src/components/TokenBar.js:164 +#: src/components/TokenBar.js:167 +#: src/components/TokenBar.js:168 msgid "Lock wallet" msgstr "" -#: src/components/TokenBar.js:167 -#: src/components/TokenBar.js:168 +#: src/components/TokenBar.js:171 +#: src/components/TokenBar.js:172 msgid "Settings" msgstr "" @@ -1367,47 +1468,47 @@ msgstr "" msgid "Download QRCode" msgstr "" -#: src/components/TokenHistory.js:185 +#: src/components/TokenHistory.js:205 msgid "Date" msgstr "" -#: src/components/TokenHistory.js:186 +#: src/components/TokenHistory.js:206 msgid "ID" msgstr "" -#: src/components/TokenHistory.js:187 +#: src/components/TokenHistory.js:207 msgid "Type" msgstr "" -#: src/components/TokenHistory.js:189 +#: src/components/TokenHistory.js:209 msgid "Value" msgstr "" -#: src/components/TokenHistory.js:209 +#: src/components/TokenHistory.js:229 msgid "Voided" msgstr "" -#: src/components/TokenHistory.js:221 +#: src/components/TokenHistory.js:241 msgid "Token creation" msgstr "" -#: src/components/TokenHistory.js:223 +#: src/components/TokenHistory.js:243 msgid "Received" msgstr "" -#: src/components/TokenHistory.js:228 +#: src/components/TokenHistory.js:248 msgid "Token deposit" msgstr "" -#: src/components/TokenHistory.js:230 +#: src/components/TokenHistory.js:250 msgid "Sent" msgstr "" -#: src/components/TokenHistory.js:260 +#: src/components/TokenHistory.js:280 msgid "You are receiving transactions in real time." msgstr "" -#: src/components/TokenHistory.js:265 +#: src/components/TokenHistory.js:285 #, javascript-format msgid "Page ${ page }" msgstr "" @@ -1613,7 +1714,7 @@ msgid "Copy raw tx to clipboard" msgstr "" #: src/components/TxData.js:666 -#: src/components/WalletAddress.js:178 +#: src/components/WalletAddress.js:180 msgid "Copied to clipboard!" msgstr "" @@ -1648,27 +1749,28 @@ msgstr "" msgid "Get qrcode" msgstr "" -#: src/components/WalletAddress.js:138 +#: src/components/WalletAddress.js:139 +#. hide all addresses for hardware wallet msgid "See all addresses" msgstr "" -#: src/components/WalletAddress.js:159 +#: src/components/WalletAddress.js:161 msgid "Show full address" msgstr "" -#: src/components/WalletAddress.js:168 +#: src/components/WalletAddress.js:170 msgid "Validate that the address below is the same presented on the Ledger screen." msgstr "" -#: src/components/WalletAddress.js:169 +#: src/components/WalletAddress.js:171 msgid "Press both buttons on your Ledger in case the address is valid." msgstr "" -#: src/components/WalletAddress.js:179 +#: src/components/WalletAddress.js:181 msgid "You must use an old address before generating new ones" msgstr "" -#: src/components/WalletAddress.js:181 +#: src/components/WalletAddress.js:183 msgid "Validate address on Ledger" msgstr "" @@ -1676,84 +1778,84 @@ msgstr "" msgid "Transaction history" msgstr "" -#: src/components/tokens/TokenDelegate.js:45 -#: src/components/tokens/TokenDelegate.js:62 -#: src/components/tokens/TokenDelegate.js:90 -#: src/components/tokens/TokenDestroy.js:58 -#: src/components/tokens/TokenDestroy.js:99 +#: src/components/tokens/TokenDelegate.js:46 +#: src/components/tokens/TokenDelegate.js:64 +#: src/components/tokens/TokenDelegate.js:92 +#: src/components/tokens/TokenDestroy.js:60 +#: src/components/tokens/TokenDestroy.js:101 msgid "Mint" msgstr "" -#: src/components/tokens/TokenDelegate.js:45 -#: src/components/tokens/TokenDelegate.js:62 -#: src/components/tokens/TokenDelegate.js:90 -#: src/components/tokens/TokenDestroy.js:58 -#: src/components/tokens/TokenDestroy.js:99 +#: src/components/tokens/TokenDelegate.js:46 +#: src/components/tokens/TokenDelegate.js:64 +#: src/components/tokens/TokenDelegate.js:92 +#: src/components/tokens/TokenDestroy.js:60 +#: src/components/tokens/TokenDestroy.js:101 msgid "Melt" msgstr "" -#: src/components/tokens/TokenDelegate.js:63 +#: src/components/tokens/TokenDelegate.js:65 #, javascript-format msgid "${ type } output delegated!" msgstr "" -#: src/components/tokens/TokenDelegate.js:68 -#: src/components/tokens/TokenDestroy.js:71 -#: src/components/tokens/TokenDestroy.js:80 +#: src/components/tokens/TokenDelegate.js:70 +#: src/components/tokens/TokenDestroy.js:73 +#: src/components/tokens/TokenDestroy.js:82 msgid "mint" msgstr "" -#: src/components/tokens/TokenDelegate.js:68 -#: src/components/tokens/TokenDestroy.js:71 -#: src/components/tokens/TokenDestroy.js:80 +#: src/components/tokens/TokenDelegate.js:70 +#: src/components/tokens/TokenDestroy.js:73 +#: src/components/tokens/TokenDestroy.js:82 msgid "melt" msgstr "" -#: src/components/tokens/TokenDelegate.js:73 +#: src/components/tokens/TokenDelegate.js:75 #, javascript-format msgid "Address of the new ${ type } authority" msgstr "" -#: src/components/tokens/TokenDelegate.js:81 +#: src/components/tokens/TokenDelegate.js:83 msgid "Create another ${ type } output for you?" msgstr "" -#: src/components/tokens/TokenDelegate.js:83 +#: src/components/tokens/TokenDelegate.js:85 #: src/components/tokens/TokenMelt.js:99 #: src/components/tokens/TokenMint.js:153 msgid "Leave it checked unless you know what you are doing" msgstr "" -#: src/components/tokens/TokenDelegate.js:96 +#: src/components/tokens/TokenDelegate.js:98 msgid "Delegate" msgstr "" -#: src/components/tokens/TokenDelegate.js:99 +#: src/components/tokens/TokenDelegate.js:101 msgid "Delegating authority" msgstr "" -#: src/components/tokens/TokenDestroy.js:59 +#: src/components/tokens/TokenDestroy.js:61 #, javascript-format msgid "${ label } outputs destroyed!" msgstr "" -#: src/components/tokens/TokenDestroy.js:73 +#: src/components/tokens/TokenDestroy.js:75 #, javascript-format msgid "You only have ${ authoritiesLength } ${ type } ${ plural } to destroy." msgstr "" -#: src/components/tokens/TokenDestroy.js:84 +#: src/components/tokens/TokenDestroy.js:86 #, javascript-format msgid "" "Are you sure you want to destroy **${ quantity } ${ type }** authority ${ " "plural }?" msgstr "" -#: src/components/tokens/TokenDestroy.js:106 +#: src/components/tokens/TokenDestroy.js:108 msgid "Destroy" msgstr "" -#: src/components/tokens/TokenDestroy.js:110 +#: src/components/tokens/TokenDestroy.js:112 msgid "Destroying authorities" msgstr "" diff --git a/src/constants.js b/src/constants.js index e1ce11ef..5adce7e1 100644 --- a/src/constants.js +++ b/src/constants.js @@ -145,3 +145,8 @@ export const NFT_ENABLED = false; * Maximum size of NFT data length */ export const NFT_DATA_MAX_SIZE = 150; + +/** + * URL for the ledger guide + */ +export const LEDGER_GUIDE_URL = 'https://medium.com/hathor-network/hathor-is-available-on-ledger-wallet-31cb1613b060'; diff --git a/src/screens/WalletType.js b/src/screens/WalletType.js index 49ec12df..99a7b9ac 100644 --- a/src/screens/WalletType.js +++ b/src/screens/WalletType.js @@ -12,7 +12,7 @@ import logo from '../assets/images/hathor-logo.png'; import wallet from '../utils/wallet'; import ledger from '../utils/ledger'; import helpers from '../utils/helpers'; -import { IPC_RENDERER, HATHOR_WEBSITE_URL } from '../constants'; +import { LEDGER_GUIDE_URL, IPC_RENDERER, HATHOR_WEBSITE_URL } from '../constants'; import SpanFmt from '../components/SpanFmt'; import InitialImages from '../components/InitialImages'; import { str2jsx } from '../utils/i18n'; @@ -156,13 +156,13 @@ class WalletType extends React.Component { } /** - * Method called to open external website page. + * Method called to open ledger guide * * @param {Object} e Event for the click */ - openWebsiteLink = (e) => { + openLedgerGuide = (e) => { e.preventDefault(); - const url = new URL('wallet-types/', HATHOR_WEBSITE_URL); + const url = new URL(LEDGER_GUIDE_URL); helpers.openExternalURL(url.href); } @@ -171,11 +171,15 @@ class WalletType extends React.Component { return (

{t`Hathor Wallet supports two types of wallet: software and hardware.`}

-

{t`**Hardware wallets** are dedicated external devices that store your private information. We currently support the Ledger hardware wallet. **Software wallets**, on the other hand, store the information on your computer.`}

- {str2jsx(t`For more information on different types of wallet, check out |fn:this page|.`, - {fn: (x, i) => {x}})} + {str2jsx(t`|bold:Hardware wallets| are dedicated external devices that store your private information. We currently support the Ledger hardware wallet and the ledger app must be installed in developer mode for now. For a tutorial about how to use the ledger app with Hathor Wallet check out |fn:this page|.`, + { + bold: (x, i) => {x}, + fn: (x, i) => {x} + } + )}

+

{t`**Software wallets**, on the other hand, store the information on your computer.`}

From a788f6f5dcedeee4bb027c10153c6de9e9cd6140 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 23 Sep 2021 11:37:11 -0300 Subject: [PATCH 23/25] chore: bump wallet version to v0.20.0 (#231) --- package-lock.json | 2 +- package.json | 2 +- public/electron.js | 2 +- src/constants.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e84f716..3602b662 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hathor-wallet", - "version": "0.19.1", + "version": "0.20.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 27a86d79..964b92eb 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "productName": "Hathor Wallet", "description": "Light wallet for Hathor Network", "author": "Hathor Labs (https://hathor.network/)", - "version": "0.19.1", + "version": "0.20.0", "private": true, "dependencies": { "@hathor/wallet-lib": "^0.22.0", diff --git a/public/electron.js b/public/electron.js index 87ebe38e..15502723 100644 --- a/public/electron.js +++ b/public/electron.js @@ -34,7 +34,7 @@ if (process.platform === 'darwin') { } const appName = 'Hathor Wallet'; -const walletVersion = '0.19.1'; +const walletVersion = '0.20.0'; const debugMode = ( process.argv.indexOf('--unsafe-mode') >= 0 && diff --git a/src/constants.js b/src/constants.js index 5adce7e1..403c534e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -20,7 +20,7 @@ export const WALLET_HISTORY_COUNT = 10; /** * Wallet version */ -export const VERSION = '0.19.1'; +export const VERSION = '0.20.0'; /** * Before this version the data in localStorage from the wallet is not compatible From 75b3bd1225097a9c54f7e91bd3a7e51b8d6c4cc3 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 23 Sep 2021 13:30:59 -0300 Subject: [PATCH 24/25] fix: change server for hardware wallet does not have pin (#232) --- src/screens/Server.js | 4 +++- src/utils/wallet.js | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/screens/Server.js b/src/screens/Server.js index e7506f39..2df14008 100644 --- a/src/screens/Server.js +++ b/src/screens/Server.js @@ -131,7 +131,9 @@ class Server extends React.Component { * reloads data and redirects to wallet screen */ executeServerChange = () => { - const promise = wallet.changeServer(this.props.wallet, this.refs.pin.value, this.props.history); + // We don't have PIN on hardware wallet + const pin = hathorLib.wallet.isSoftwareWallet() ? this.refs.pin.value : null; + const promise = wallet.changeServer(this.props.wallet, pin, this.props.history); promise.then(() => { this.props.history.push('/wallet/'); }, () => { diff --git a/src/utils/wallet.js b/src/utils/wallet.js index 3eda9c1c..4a7e933c 100644 --- a/src/utils/wallet.js +++ b/src/utils/wallet.js @@ -344,7 +344,11 @@ const wallet = { async changeServer(wallet, pin, routerHistory) { wallet.stop({ cleanStorage: false }); - await this.startWallet(null, '', pin, '', routerHistory, true); + if (oldWalletUtil.isSoftwareWallet()) { + await this.startWallet(null, '', pin, '', routerHistory, true); + } else { + await this.startWallet(null, '', null, '', routerHistory, false, wallet.xpub); + } }, /* From f6d3973708252d40c6362297f03a80ba3496bd38 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 23 Sep 2021 13:43:08 -0300 Subject: [PATCH 25/25] feat: set ledger guide URL from website link (#234) --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 403c534e..f9ff2712 100644 --- a/src/constants.js +++ b/src/constants.js @@ -149,4 +149,4 @@ export const NFT_DATA_MAX_SIZE = 150; /** * URL for the ledger guide */ -export const LEDGER_GUIDE_URL = 'https://medium.com/hathor-network/hathor-is-available-on-ledger-wallet-31cb1613b060'; +export const LEDGER_GUIDE_URL = 'https://hathor.network/ledger-guide';