diff --git a/app/actions/ada/daedalus-transfer-actions.js b/app/actions/ada/daedalus-transfer-actions.js index 22cc3e5521..0c1eb8738e 100644 --- a/app/actions/ada/daedalus-transfer-actions.js +++ b/app/actions/ada/daedalus-transfer-actions.js @@ -4,7 +4,9 @@ import Action from '../lib/Action'; export default class DaedalusTranferActions { startTransferFunds: Action = new Action(); startTransferPaperFunds: Action = new Action(); - setupTransferFunds: Action = new Action(); + startTransferMasterKey: Action = new Action(); + setupTransferFundsWithMnemonic: Action = new Action(); + setupTransferFundsWithMasterKey: Action = new Action(); backToUninitialized: Action = new Action(); transferFunds: Action = new Action(); cancelTransferFunds: Action = new Action(); diff --git a/app/actions/ada/trezor-connect-actions.js b/app/actions/ada/hw-connect-actions.js similarity index 56% rename from app/actions/ada/trezor-connect-actions.js rename to app/actions/ada/hw-connect-actions.js index 0a3568e82e..8ddddc514e 100644 --- a/app/actions/ada/trezor-connect-actions.js +++ b/app/actions/ada/hw-connect-actions.js @@ -1,12 +1,13 @@ // @flow import Action from '../lib/Action'; -// ======= TREZOR CONNECT ACTIONS ======= +// ======= HARDWARE WALLET CONNECT ACTIONS ======= -export default class TrezorConnectActions { +export default class HWConnectActions { + init: Action = new Action(); cancel: Action = new Action(); submitAbout: Action = new Action(); - goBacktToAbout: Action = new Action(); + goBackToAbout: Action = new Action(); submitConnect: Action = new Action(); submitSave: Action = new Action(); } diff --git a/app/actions/ada/index.js b/app/actions/ada/index.js index a5972ec459..fa817f0bf0 100644 --- a/app/actions/ada/index.js +++ b/app/actions/ada/index.js @@ -4,9 +4,10 @@ import TransactionsActions from './transactions-actions'; import WalletSettingsActions from './wallet-settings-actions'; import AddressesActions from './addresses-actions'; import DaedalusTransferActions from './daedalus-transfer-actions'; -import TrezorConnectActions from './trezor-connect-actions'; +import HWConnectActions from './hw-connect-actions'; import TrezorSendActions from './trezor-send-actions'; import AdaRedemptionActions from './ada-redemption-actions'; +import LedgerSendActions from './ledger-send-actions'; export type AdaActionsMap = { adaRedemption: AdaRedemptionActions, @@ -15,8 +16,10 @@ export type AdaActionsMap = { walletSettings: WalletSettingsActions, addresses: AddressesActions, daedalusTransfer: DaedalusTransferActions, - trezorConnect: TrezorConnectActions, + trezorConnect: HWConnectActions, trezorSend: TrezorSendActions, + ledgerConnect: HWConnectActions, + ledgerSend: LedgerSendActions, }; const adaActionsMap: AdaActionsMap = { @@ -26,8 +29,10 @@ const adaActionsMap: AdaActionsMap = { walletSettings: new WalletSettingsActions(), addresses: new AddressesActions(), daedalusTransfer: new DaedalusTransferActions(), - trezorConnect: new TrezorConnectActions(), + trezorConnect: new HWConnectActions(), trezorSend: new TrezorSendActions(), + ledgerConnect: new HWConnectActions(), + ledgerSend: new LedgerSendActions(), }; export default adaActionsMap; diff --git a/app/actions/ada/ledger-send-actions.js b/app/actions/ada/ledger-send-actions.js new file mode 100644 index 0000000000..079166003a --- /dev/null +++ b/app/actions/ada/ledger-send-actions.js @@ -0,0 +1,15 @@ +// @flow +import Action from '../lib/Action'; + +export type SendUsingLedgerParams = { + receiver: string, + amount: string +}; + +// ======= Sending ADA using Ledger ACTIONS ======= + +export default class LedgerSendActions { + init: Action = new Action(); + cancel: Action = new Action(); + sendUsingLedger: Action = new Action(); +} diff --git a/app/actions/wallet-backup-actions.js b/app/actions/wallet-backup-actions.js index 0a9c651edf..877011252a 100644 --- a/app/actions/wallet-backup-actions.js +++ b/app/actions/wallet-backup-actions.js @@ -15,4 +15,5 @@ export default class WalletBackupActions { restartWalletBackup: Action = new Action(); cancelWalletBackup: Action = new Action(); finishWalletBackup: Action = new Action(); + removeOneMnemonicWord: Action = new Action(); } diff --git a/app/api/ada/daedalusTransfer.js b/app/api/ada/daedalusTransfer.js index 615d361e1b..7222871961 100644 --- a/app/api/ada/daedalusTransfer.js +++ b/app/api/ada/daedalusTransfer.js @@ -20,9 +20,6 @@ import { NoInputsError, GenerateTransferTxError } from './errors'; -import { - getCryptoDaedalusWalletFromMnemonics -} from './lib/cardanoCrypto/cryptoWallet'; import { getAllUTXOsForAddresses } from './adaTransactions/adaNewTransactions'; @@ -38,14 +35,11 @@ import { getReceiverAddress } from './adaAddress'; * @param fullUtxo the full utxo of the Cardano blockchain */ export function getAddressesWithFunds(payload: { - secretWords: string, + checker: CryptoAddressChecker, fullUtxo: Array }): Array { try { - const { secretWords, fullUtxo } = payload; - const checker: CryptoAddressChecker = getResultOrFail( - RandomAddressChecker.newCheckerFromMnemonics(secretWords) - ); + const { checker, fullUtxo } = payload; const addressesWithFunds: Array = getResultOrFail( RandomAddressChecker.checkAddresses(checker, fullUtxo) ); @@ -58,12 +52,12 @@ export function getAddressesWithFunds(payload: { /** Generate transaction including all addresses with no change */ export async function generateTransferTx(payload: { - secretWords: string, + wallet: CryptoDaedalusWallet, addressesWithFunds: Array }): Promise { try { - const { secretWords, addressesWithFunds } = payload; + const { wallet, addressesWithFunds } = payload; // fetch data to make transaction const senders = addressesWithFunds.map(a => a.address); @@ -78,8 +72,7 @@ export async function generateTransferTx(payload: { // pick which address to send transfer to const output = await getReceiverAddress(); - // get wallet and make transaction - const wallet = getCryptoDaedalusWalletFromMnemonics(secretWords); + // make transaction const tx: MoveResponse = getResultOrFail(Wallet.move(wallet, inputs, output)); // return summary of transaction diff --git a/app/api/ada/hardwareWallet/README.md b/app/api/ada/hardwareWallet/README.md new file mode 100644 index 0000000000..c9b691c303 --- /dev/null +++ b/app/api/ada/hardwareWallet/README.md @@ -0,0 +1,130 @@ +# newTransaction.js +This module is responsible for managing hardware wallet device sign transaction, and it's two step process. +- Prepare the payload data format to be consume by respective hawrdware wallets(Trezor/Ledger). +- Propagate the hardware wallet signed transaction to the blockchain. + +## Prepare the payload data format to be consume by respective hawrdware wallets(Trezor/Ledger). +For that let's first understand Cardano's format(UnsignedTransactionExt). +``` +const txExt = { + "inputs": [ + { + "ptr": { + "index": 0, + "id": "2610d9fe9af9ac321d631b231edc8433105a2facf2b1b7048d3365458ba0c060" + }, + "value": { + "address": "Ae2tdPwUPEZHEU3j2jSAhuvwrCPvjSBHPjk4xTHEaiVGkrhneAGB6qzZMoD", + "value": "80000" + }, + "addressing": { + "account": 0, + "change": 0, + "index": 1 + } + }, + { + "ptr": { + "index": 1, + "id": "e70f354a69f0e67ac2114ac57e27bd4f15bfa8c66e00603bd8e975d90400bf63" + }, + "value": { + "address": "Ae2tdPwUPEZKVNJCH2CjBFNukyQBSEuq9xiU8hxdA4FxeT9ajY8L3dpevuR", + "value": "1484495" + }, + "addressing": { + "account": 0, + "change": 1, + "index": 23 + } + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZHAQPk7EHHmKXBWWS33QbEWb576v52yHMpPEt7HgjR42coDay", + "value": 20004 + }, + { + "address": "Ae2tdPwUPEZAa8d3kFZEJeSZvHk2EnWsnJ4mCowMD8NX8aStpuryFxunt9m", + "value": 1368631, + "isChange": true, + "fullAddress": { + "cadAmount": { + "getCCoin": 0 + }, + "cadId": "Ae2tdPwUPEZAa8d3kFZEJeSZvHk2EnWsnJ4mCowMD8NX8aStpuryFxunt9m", + "cadIsUsed": false, + "account": 0, + "change": 1, + "index": 24 + } + } + ] +} +``` + +- [Trezor.cardanoSignTransaction(trezorParams)](https://github.com/trezor/connect/blob/develop/docs/methods/cardanoSignTransaction.md) +``` + const trezorParams = { + "network": 2, + "transactions": [ + "839f8200d81858248258206f9cf4bbad8fd0ac7487419ff7f1ed7ecfe90892f753325aaa74fbee73c227a601ff9f8282d818582183581cccb5b2e6aa52faa4a18e55c7712ee69ddc1bfafebf4c9e9fcb154a1ba0001a59f92a181a000138808282d818582183581c5f616829cd5536ba6b5931bc2aeca6016e6d8dd0a054a2e57650d617a0001a43d762111a001a4022ffa0", + "839f8200d81858248258206f9cf4bbad8fd0ac7487419ff7f1ed7ecfe90892f753325aaa74fbee73c227a6008200d81858248258202610d9fe9af9ac321d631b231edc8433105a2facf2b1b7048d3365458ba0c06001ff9f8282d818582183581caae0559f67add7d768b91ef3e55f1ce0e400037a11da3cacd1c101b7a0001a6aefab2c19ea608282d818582183581ce35422b874147b84ab476cf799198f29f1f3dacfac6ec3738f92caffa0001aa6d6ed8c1a0016a6cfffa0" + ], + "inputs": [ + { + "path": "m/44'/1815'/0'/0/1", + "prev_hash": "2610d9fe9af9ac321d631b231edc8433105a2facf2b1b7048d3365458ba0c060", + "prev_index": 0, + "type": 0 + }, + { + "path": "m/44'/1815'/0'/1/23", + "prev_hash": "e70f354a69f0e67ac2114ac57e27bd4f15bfa8c66e00603bd8e975d90400bf63", + "prev_index": 1, + "type": 0 + } + ], + "outputs": [ + { + "amount": "20004", + "address": "Ae2tdPwUPEZHAQPk7EHHmKXBWWS33QbEWb576v52yHMpPEt7HgjR42coDay" + }, + { + "amount": "1368631", + "path": "m/44'/1815'/0'/1/24" + } + ] + } +``` + +- [Ledger.signTransaction(ledgerParams)](https://github.com/vacuumlabs/ledgerjs/blob/1a85a888f7c2a0494b2dda0451e85916b43e7101/packages/hw-app-ada/src/Ada.js#L291) +``` + const ledgerParams = { + "inputs" : [ + { + txDataHex: trezorParams.transactions[0], + outputIndex: trezorParams.inputs[0].prev_index, + path: utils.str_to_path(trezorParams.inputs[0].path) + }, + { + txDataHex: trezorParams.transactions[1], + outputIndex: trezorParams.inputs[1].prev_index, + path: utils.str_to_path(trezorParams.inputs[1].path) + } + ], + "outputs": [ + { + amountStr: trezorParams.outputs[0].amount, + address58: trezorParams.outputs[0].address + }, + { + amountStr: trezorParams.outputs[1].amount, + path: utils.str_to_path(trezorParams.outputs[1].path) + } + ] + } +``` + +## Propagate the hardware wallet signed transaction to the blockchain +TODO \ No newline at end of file diff --git a/app/api/ada/hardwareWallet/createTrezorWallet.js b/app/api/ada/hardwareWallet/createWallet.js similarity index 62% rename from app/api/ada/hardwareWallet/createTrezorWallet.js rename to app/api/ada/hardwareWallet/createWallet.js index 89e6135e95..c2b9b1b8d4 100644 --- a/app/api/ada/hardwareWallet/createTrezorWallet.js +++ b/app/api/ada/hardwareWallet/createWallet.js @@ -21,25 +21,26 @@ import { createHardwareWalletAccount } from '../adaAccount'; * Caches the fetched address results locally (into lovefieldDatabase) * @returns a new AdaWallet */ -export async function createTrezorWallet({ +export async function createWallet({ walletInitData }: AdaHardwareWalletParams): Promise { try { - Logger.debug('createTrezorWallet::createTrezorWallet called'); + Logger.debug('hardwareWallet::createWallet called'); // create ada wallet object for hardware wallet - const [adaWallet] = createAdaHardwareWallet({ walletInitData }); + const [hardwareWallet] = createAdaHardwareWallet({ walletInitData }); + // create crypto account object for hardware wallet - // eslint-disable-next-line max-len - const cryptoAccount = createHardwareWalletAccount(walletInitData.cwHardwareInfo.publicMasterKey); + const { publicMasterKey } = walletInitData.cwHardwareInfo; + const cryptoAccount = createHardwareWalletAccount(publicMasterKey); // Restore transactions and Save wallet + cryptoAccount to localstorage - await restoreTransactionsAndSave(cryptoAccount, adaWallet); + await restoreTransactionsAndSave(cryptoAccount, hardwareWallet); - Logger.debug('createTrezorWallet::createTrezorWallet success'); - return adaWallet; + Logger.debug('hardwareWallet::createWallet success'); + return hardwareWallet; } catch (error) { - Logger.error(`createTrezorWallet::createTrezorWallet error: ${stringifyError(error)}`); + Logger.error(`hardwareWallet::createWallet error: ${stringifyError(error)}`); throw error; } } diff --git a/app/api/ada/hardwareWallet/newTransaction.js b/app/api/ada/hardwareWallet/newTransaction.js new file mode 100644 index 0000000000..d45bab724b --- /dev/null +++ b/app/api/ada/hardwareWallet/newTransaction.js @@ -0,0 +1,467 @@ +// @flow +import _ from 'lodash'; + +import { + Logger, + stringifyError, + stringifyData +} from '../../../utils/logging'; +import { + saveAdaAddress, + removeAdaAddress, +} from '../adaAddress'; +import type { + AdaAddress, +} from '../adaTypes'; +import { + sendTx, + getTxsBodiesForUTXOs +} from '../lib/yoroi-backend-api'; +import { + SendTransactionError, + InvalidWitnessError, + GetTxsBodiesForUTXOsError +} from '../errors'; +import type { + BroadcastTrezorSignedTxResponse, + PrepareAndBroadcastLedgerSignedTxResponse +} from '../../common'; +import type { + TrezorInput, + TrezorOutput, + TrezorSignTxPayload, + LedgerSignTxPayload, + LedgerUnsignedOutput, + LedgerUnsignedUtxo, +} from '../../../domain/HWSignTx'; +import type { + BIP32Path, + InputTypeUTxO, + OutputTypeAddress, + OutputTypeChange, + SignTransactionResponse as LedgerSignTxResponse, +} from '@cardano-foundation/ledgerjs-hw-app-cardano'; +import { makeCardanoBIP44Path } from 'yoroi-extension-ledger-bridge'; +import bs58 from 'bs58'; + +import type { ConfigType } from '../../../../config/config-types'; +import Config from '../../../config'; + +// Probably this needs to be moved somewhere else +import { getSingleCryptoAccount } from '../adaLocalStorage'; +import cbor from 'cbor'; +import { HdWallet } from 'rust-cardano-crypto'; +import { CborIndefiniteLengthArray } from '../lib/utils'; +import { blake2b } from 'cardano-crypto.js'; + +declare var CONFIG: ConfigType; + +export type UnsignedLedgerTx = {inputs: Array, outputs: Array, attributes: {}}; + +// ==================== TREZOR ==================== // +/** Generate a payload for Trezor SignTx */ +export async function createTrezorSignTxPayload( + txExt: UnsignedTransactionExt +): Promise { + + // Inputs + const trezorInputs = _transformToTrezorInputs(txExt.inputs); + + // Outputs + const trezorOutputs = _generateTrezorOutputs(txExt.outputs); + + // Transactions + const txsBodiesMap = await txsBodiesForInputs(txExt.inputs); + const txsBodies = txExt.inputs.map((x: TxInput) => txsBodiesMap[x.ptr.id]); + + return { + inputs: trezorInputs, + outputs: trezorOutputs, + transactions: txsBodies, + protocol_magic: CONFIG.network.protocolMagic, + }; +} + +/** List of Body hashes for a list of utxos by batching backend requests */ +export async function txsBodiesForInputs( + inputs: Array +): Promise<{[key: string]:string}> { + if (!inputs) return {}; + try { + + // Map inputs to UNIQUE tx hashes (there might be multiple inputs from the same tx) + const txsHashes = [...new Set(inputs.map(x => x.ptr.id))]; + + // split up all addresses into chunks of equal size + const groupsOfTxsHashes = _.chunk(txsHashes, CONFIG.app.txsBodiesRequestSize); + + // convert chunks into list of Promises that call the backend-service + const promises = groupsOfTxsHashes + .map(groupOfTxsHashes => getTxsBodiesForUTXOs(groupOfTxsHashes)); + + // Sum up all the utxo + return Promise.all(promises) + .then(groupsOfTxBodies => { + const bodies = groupsOfTxBodies + .reduce((acc, groupOfTxBodies) => Object.assign(acc, groupOfTxBodies), {}); + if (txsHashes.length !== Object.keys(bodies).length) { + throw new GetTxsBodiesForUTXOsError(); + } + return bodies; + }); + } catch (getTxBodiesError) { + Logger.error('newTransaction::txsBodiesForInputs error: ' + + stringifyError(getTxBodiesError)); + throw new GetTxsBodiesForUTXOsError(); + } +} + +/** Send a transaction and save the new change address */ +export async function broadcastTrezorSignedTx( + signedTxHex: string, + changeAdaAddr: AdaAddress, +): Promise { + Logger.debug('newTransaction::broadcastTrezorSignedTx: called'); + const signedTx: string = Buffer.from(signedTxHex, 'hex').toString('base64'); + + // We assume a change address is used. Currently, there is no way to perfectly match the tx. + // tentatively assume that the transaction will succeed, + // so we save the change address to the wallet + await saveAdaAddress(changeAdaAddr, 'Internal'); + + try { + const body = { signedTx }; + const backendResponse = await sendTx(body); + Logger.debug('newTransaction::broadcastTrezorSignedTx: success'); + + return backendResponse; + } catch (sendTxError) { + Logger.error('newTransaction::broadcastTrezorSignedTx error: ' + stringifyError(sendTxError)); + // On failure, we have to remove the change address we eagerly added + // Note: we don't await on this + removeAdaAddress(changeAdaAddr); + if (sendTxError instanceof InvalidWitnessError) { + throw new InvalidWitnessError(); + } + throw new SendTransactionError(); + } +} + +function _derivePathAsString(chain: number, addressIndex: number): string { + // Assumes this is only for Cardano and Web Yoroi (only one account). + return `${Config.wallets.BIP44_CARDANO_FIRST_ACCOUNT_SUB_PATH}/${chain}/${addressIndex}`; +} + +function _transformToTrezorInputs(inputs: Array): Array { + return inputs.map((input: TxInput) => ({ + path: _derivePathAsString(input.addressing.change, input.addressing.index), + prev_hash: input.ptr.id, + prev_index: input.ptr.index, + type: 0 + })); +} + +function _generateTrezorOutputs(outputs: Array): Array { + return outputs.map(x => ({ + amount: x.value.toString(), + ..._outputAddressOrPath(x) + })); +} + +function _outputAddressOrPath( + txOutput: TxOutput +): { path: string } | { address: string } { + if (txOutput.isChange) { + const fullAddress: ?AdaAddress = txOutput.fullAddress; + if (fullAddress) { + return { path: _derivePathAsString(fullAddress.change, fullAddress.index) }; + } + Logger.debug(`newTransaction::_outputAddressOrPath:[WEIRD] Trezor got a change output without a full 'Ada Address': ${stringifyData(txOutput)}`); + } + + return { address: txOutput.address }; +} + +// ==================== LEDGER ==================== // +/** Generate a payload for Ledger SignTx */ +export async function createLedgerSignTxPayload( + txExt: UnsignedTransactionExt +): Promise { + + // Transactions Hash + const txDataHexMap = await txsBodiesForInputs(txExt.inputs); + + // Inputs + const ledgerInputs: Array = + _transformToLedgerInputs(txExt.inputs, txDataHexMap); + + // Outputs + const ledgerOutputs: Array = + _transformToLedgerOutputs(txExt.outputs); + + return { + inputs: ledgerInputs, + outputs: ledgerOutputs, + }; +} + +function _derivePathAsBIP32Path( + chain: number, + addressIndex: number +): BIP32Path { + // Assumes this is only for Cardano and Web Yoroi (only one account). + return makeCardanoBIP44Path(0, chain, addressIndex); +} + +function _transformToLedgerInputs( + inputs: Array, + txDataHexMap: {[key: string]:string} +): Array { + return inputs.map((input: TxInput) => ({ + txDataHex: txDataHexMap[input.ptr.id], + outputIndex: input.ptr.index, + path: _derivePathAsBIP32Path(input.addressing.change, input.addressing.index), + })); +} + +function _transformToLedgerOutputs( + txOutputs: Array +): Array { + return txOutputs.map(txOutput => ({ + amountStr: txOutput.value.toString(), + ..._ledgerOutputAddress58OrPath(txOutput) + })); +} + +function _ledgerOutputAddress58OrPath( + txOutput: TxOutput +): { address58: string } | { path: BIP32Path } { + if (txOutput.isChange) { + const fullAddress: ?AdaAddress = txOutput.fullAddress; + if (fullAddress) { + return { path: _derivePathAsBIP32Path(fullAddress.change, fullAddress.index) }; + } + Logger.debug(`newTransaction::_ledgerOutputAddressOrPath:[WEIRD] Ledger got a change output without a full 'Ada Address': ${stringifyData(txOutput)}`); + } + + return { address58: txOutput.address }; +} + +/** Send a transaction and save the new change address */ +export async function prepareAndBroadcastLedgerSignedTx( + ledgerSignTxResp: LedgerSignTxResponse, + changeAdaAddr: AdaAddress, + unsignedTx: any, + txExt: UnsignedTransactionExt +): Promise { + try { + Logger.debug('newTransaction::prepareAndBroadcastLedgerSignedTx: called'); + + // Since Ledger only provide witness signature + // need to make full broadcastable signed Tx + Logger.debug(`newTransaction::prepareAndBroadcastLedgerSignedTx unsignedTx: ${stringifyData(unsignedTx)}`); + const signedTxHex: string = await prepareLedgerSignedTxBody( + ledgerSignTxResp, + unsignedTx, + txExt + ); + + Logger.debug('newTransaction::prepareAndBroadcastLedgerSignedTx: called'); + const signedTx: string = Buffer.from(signedTxHex, 'hex').toString('base64'); + + // We assume a change address is used. Currently, there is no way to perfectly match the tx. + // tentatively assume that the transaction will succeed, + // so we save the change address to the wallet + await saveAdaAddress(changeAdaAddr, 'Internal'); + const body = { signedTx }; + const backendResponse = await sendTx(body); + Logger.debug('newTransaction::prepareAndBroadcastLedgerSignedTx: success'); + + return backendResponse; + } catch (sendTxError) { + Logger.error('newTransaction::prepareAndBroadcastLedgerSignedTx error: ' + stringifyError(sendTxError)); + + // On failure, we have to remove the change address we eagerly added + // Note: we don't await on this + removeAdaAddress(changeAdaAddr); + if (sendTxError instanceof InvalidWitnessError) { + throw new InvalidWitnessError(); + } else { + throw new SendTransactionError(); + } + } +} + +function prepareLedgerSignedTxBody( + ledgerSignTxResp: LedgerSignTxResponse, + unsignedTx: any, + txExt: UnsignedTransactionExt +): string { + // TODO: add type to unsignedTx + + const txAux = TxAux( + txExt.inputs + .map(input => { + const transformedInput = InputToLedgerUnsignedFormat(input); + Logger.debug(`newTransaction::transformedInput: ${stringifyData(transformedInput)}`); + return TxInputFromUtxo(transformedInput); + }), + txExt.outputs + .map(output => { + const transformedOutput = OutputToLedgerUnsignedFormat(output); + Logger.debug(`newTransaction::transformedOutput: ${stringifyData(transformedOutput)}`); + return LedgerTxOutput(transformedOutput); + }), + {} // attributes + ); + + const txWitnesses = ledgerSignTxResp.witnesses.map((witness) => prepareWitness(witness)); + Logger.debug(`newTransaction::prepareLedgerSignedTxBody txWitnesses: ${stringifyData(txWitnesses)}`); + + const txBody = prepareBody(txAux, txWitnesses); + Logger.debug(`newTransaction::prepareLedgerSignedTxBody txBody: ${stringifyData(txBody)}`); + + return txBody; +} + +function InputToLedgerUnsignedFormat(txInput: TxInput): LedgerUnsignedUtxo { + return { + txHash: txInput.ptr.id, + address: txInput.value.address, + coins: Number(txInput.value.value), + outputIndex: txInput.ptr.index + }; +} + +function OutputToLedgerUnsignedFormat(output: TxOutput): LedgerUnsignedOutput { + // TODO: when does this actually happen + const isChange = !!output.isChange; + return { + address: output.address, + coins: Number(output.value), + isChange + }; +} +/* Stuff from VacuumLabs */ + +function TxInputFromUtxo(utxo: LedgerUnsignedUtxo) { + // default input type + const type = 0; + const coins = utxo.coins; + const txHash = utxo.txHash; + const outputIndex = utxo.outputIndex; + + function encodeCBOR(encoder) { + return encoder.pushAny([ + type, + new cbor.Tagged(24, cbor.encode([Buffer.from(txHash, 'hex'), outputIndex])), + ]); + } + + return { + coins, + txHash, + outputIndex, + utxo, + encodeCBOR, + }; +} + +function AddressCborWrapper(address: string) { + function encodeCBOR(encoder) { + return encoder.push(bs58.decode(address)); + } + + return { + address, + encodeCBOR, + }; +} +function LedgerTxOutput(output: LedgerUnsignedOutput) { + function encodeCBOR(encoder) { + return encoder.pushAny([AddressCborWrapper(output.address), output.coins]); + } + + return { + address: output.address, + coins: output.coins, + isChange: output.isChange, + encodeCBOR, + }; +} + +function prepareWitness(witness) { + const cryptoAccount = getSingleCryptoAccount(); + const masterXPubAccount0 = cryptoAccount.root_cached_key; + + const deriveXpub = function XPubFactory(absDerivationPath: Array) { + const masterXPubAccount0Buffer = Buffer.from(masterXPubAccount0, 'hex'); + const changeXpub = HdWallet.derivePublic(masterXPubAccount0Buffer, [absDerivationPath[3]]); + const addressXpub = HdWallet.derivePublic(changeXpub, [absDerivationPath[4]]); + return addressXpub; + }; + + const extendedPublicKey = deriveXpub(witness.path); + Logger.debug(`newTransaction::prepareWitness extendedPublicKey: ${stringifyData(extendedPublicKey)}`); + return TxWitness(extendedPublicKey, Buffer.from(witness.witnessSignatureHex, 'hex')); +} + +function TxWitness(extendedPublicKey, signature) { + // default - PkWitness + const type = 0; + + function encodeCBOR(encoder) { + return encoder.pushAny( + [type, new cbor.Tagged(24, cbor.encode([extendedPublicKey, signature]))] + ); + } + + return { + extendedPublicKey, + signature, + encodeCBOR, + }; +} + +function prepareBody(txAux, txWitnesses) { + return cbor.encode(SignedTransactionStructured(txAux, txWitnesses)).toString('hex'); +} + +function TxAux(inputs: Array, outputs: Array, attributes: any) { + function getId() { + return blake2b(cbor.encode(TxAux(inputs, outputs, attributes)), 32).toString('hex'); + } + + function encodeCBOR(encoder) { + return encoder.pushAny([ + new CborIndefiniteLengthArray(inputs), + new CborIndefiniteLengthArray(outputs), + attributes, + ]); + } + + return { + getId, + inputs, + outputs, + attributes, + encodeCBOR, + }; +} + +function SignedTransactionStructured(txAux, witnesses) { + function getId() { + return txAux.getId(); + } + + function encodeCBOR(encoder) { + return encoder.pushAny([txAux, witnesses]); + } + + return { + getId, + witnesses, + txAux, + encodeCBOR, + }; +} diff --git a/app/api/ada/hardwareWallet/trezorNewTransactions.js b/app/api/ada/hardwareWallet/trezorNewTransactions.js deleted file mode 100644 index 339f316e08..0000000000 --- a/app/api/ada/hardwareWallet/trezorNewTransactions.js +++ /dev/null @@ -1,152 +0,0 @@ -// @flow -import _ from 'lodash'; - -import { - Logger, - stringifyError, - stringifyData -} from '../../../utils/logging'; -import { - saveAdaAddress, - removeAdaAddress, -} from '../adaAddress'; -import type { - AdaAddress, -} from '../adaTypes'; -import { - sendTx, - getTxsBodiesForUTXOs -} from '../lib/yoroi-backend-api'; -import { - SendTransactionError, - InvalidWitnessError, - GetTxsBodiesForUTXOsError -} from '../errors'; -import type { SendTrezorSignedTxResponse } from '../../common'; -import type { - TrezorInput, - TrezorOutput, - TrezorSignTxPayload -} from '../../../domain/TrezorSignTx'; - -import type { ConfigType } from '../../../../config/config-types'; -import Config from '../../../config'; - -declare var CONFIG: ConfigType; - -// TODO: [TREZOR] add trezor payload format. Maybe as a README in the same folder? - -/** Generate a payload for Trezor SignTx */ -export async function createTrezorSignTxPayload( - txExt: UnsignedTransactionExt -): Promise { - - // Inputs - const trezorInputs = _transformToTrezorInputs(txExt.inputs); - - // Outputs - const trezorOutputs = _generateTrezorOutputs(txExt.outputs); - - // Transactions - const txsBodies = await txsBodiesForInputs(txExt.inputs); - - return { - inputs: trezorInputs, - outputs: trezorOutputs, - transactions: txsBodies, - protocol_magic: CONFIG.network.protocolMagic, - }; -} - -/** List of Body hashes for a list of utxos by batching backend requests */ -export async function txsBodiesForInputs( - inputs: Array -): Promise> { - if (!inputs) return []; - try { - - // Map inputs to UNIQUE tx hashes (there might be multiple inputs from the same tx) - const txsHashes = [...new Set(inputs.map(x => x.ptr.id))]; - - // split up all addresses into chunks of equal size - const groupsOfTxsHashes = _.chunk(txsHashes, CONFIG.app.txsBodiesRequestSize); - - // convert chunks into list of Promises that call the backend-service - const promises = groupsOfTxsHashes - .map(groupOfTxsHashes => getTxsBodiesForUTXOs(groupOfTxsHashes)); - - // Sum up all the utxo - return Promise.all(promises) - .then(groupsOfTxBodies => ( - groupsOfTxBodies.reduce((acc, groupOfTxBodies) => acc.concat(groupOfTxBodies), []) - )); - } catch (getTxBodiesError) { - Logger.error('trezorNewTransactions::txsBodiesForInputs error: ' + - stringifyError(getTxBodiesError)); - throw new GetTxsBodiesForUTXOsError(); - } -} - -/** Send a transaction and save the new change address */ -export async function newTrezorTransaction( - signedTxHex: string, - changeAdaAddr: AdaAddress, -): Promise { - Logger.debug('trezorNewTransactions::newTrezorTransaction error: called'); - const signedTx: string = Buffer.from(signedTxHex, 'hex').toString('base64'); - - // We assume a change address is used. Currently, there is no way to perfectly match the tx. - // tentatively assume that the transaction will succeed, - // so we save the change address to the wallet - await saveAdaAddress(changeAdaAddr, 'Internal'); - - try { - const body = { signedTx }; - const backendResponse = await sendTx(body); - Logger.debug('trezorNewTransactions::newTrezorTransaction error: success'); - - return backendResponse; - } catch (sendTxError) { - Logger.error('trezorNewTransactions::newTrezorTransaction error: ' + stringifyError(sendTxError)); - // On failure, we have to remove the change address we eagerly added - // Note: we don't await on this - removeAdaAddress(changeAdaAddr); - if (sendTxError instanceof InvalidWitnessError) { - throw new InvalidWitnessError(); - } - throw new SendTransactionError(); - } -} - -function _derivePath(change: number, index: number): string { - // Assumes this is only for Cardano and Web Yoroi (only one account). - return `${Config.trezor.DEFAULT_CARDANO_PATH}/${change}/${index}`; -} - -function _transformToTrezorInputs(inputs: Array): Array { - return inputs.map((input: TxInput) => ({ - path: _derivePath(input.addressing.change, input.addressing.index), - prev_hash: input.ptr.id, - prev_index: input.ptr.index, - type: 0 - })); -} - -function _generateTrezorOutputs(outputs: Array): Array { - return outputs.map(x => ({ - amount: x.value.toString(), - ..._outputAddressOrPath(x) - })); -} - -function _outputAddressOrPath(out: TxOutput) { - if (out.isChange) { - const fullAddress: ?AdaAddress = out.fullAddress; - if (fullAddress) { - return { path: _derivePath(fullAddress.change, fullAddress.index) }; - } - Logger.debug('trezorNewTransactions::_outputAddressOrPath: ' + - `[WEIRD] Trezor got a change output without a full 'Ada Address': ${stringifyData(out)}`); - } - return { address: out.address }; -} diff --git a/app/api/ada/index.js b/app/api/ada/index.js index 117804434c..9a20f3978a 100644 --- a/app/api/ada/index.js +++ b/app/api/ada/index.js @@ -36,8 +36,8 @@ import { restoreAdaWallet } from './restoreAdaWallet'; import { - createTrezorWallet -} from './hardwareWallet/createTrezorWallet'; + createWallet, +} from './hardwareWallet/createWallet'; import { getAdaTxsHistoryByWallet, getAdaTxLastUpdatedDate, @@ -48,11 +48,16 @@ import { getAdaTransactionFee, newAdaTransaction } from './adaTransactions/adaNewTransactions'; -import type { TrezorSignTxPayload } from '../../domain/TrezorSignTx'; +import type { + TrezorSignTxPayload, + LedgerSignTxPayload, +} from '../../domain/HWSignTx'; import { createTrezorSignTxPayload, - newTrezorTransaction, -} from './hardwareWallet/trezorNewTransactions'; + broadcastTrezorSignedTx, + createLedgerSignTxPayload, + prepareAndBroadcastLedgerSignedTx, +} from './hardwareWallet/newTransaction'; import { GenericApiError, IncorrectWalletPasswordError, @@ -88,10 +93,14 @@ import type { RestoreWalletRequest, RestoreWalletResponse, UpdateWalletResponse, - CreateTrezorWalletRequest, - CreateTrezorWalletResponse, - SendTrezorSignedTxResponse, + CreateHardwareWalletRequest, + CreateHardwareWalletResponse, + BroadcastTrezorSignedTxResponse, + PrepareAndBroadcastLedgerSignedTxResponse, } from '../common'; +import type { + SignTransactionResponse as LedgerSignTxResponse +} from '@cardano-foundation/ledgerjs-hw-app-cardano'; import { InvalidWitnessError, RedeemAdaError, RedemptionKeyAlreadyUsedError } from './errors'; import { WrongPassphraseError } from './lib/cardanoCrypto/cryptoErrors'; import { getSingleCryptoAccount, getAdaWallet, getLastBlockNumber } from './adaLocalStorage'; @@ -114,18 +123,33 @@ export type CreateTransactionRequest = { amount: string, password: string }; -export type SendTrezorSignedTxRequest = { - signedTxHex: string, - changeAdaAddr: AdaAddress -}; export type CreateTrezorSignTxDataRequest = { receiver: string, amount: string }; export type CreateTrezorSignTxDataResponse = { - trezorSignTxPayload: TrezorSignTxPayload, + trezorSignTxPayload: TrezorSignTxPayload, // https://github.com/trezor/connect/blob/develop/docs/methods/cardanoSignTransaction.md changeAddress: AdaAddress }; +export type BroadcastTrezorSignedTxRequest = { + signedTxHex: string, + changeAdaAddr: AdaAddress +}; +export type CreateLedgerSignTxDataRequest = { + receiver: string, + amount: string +}; +export type CreateLedgerSignTxDataResponse = { + ledgerSignTxPayload: LedgerSignTxPayload, + changeAddress: AdaAddress, + txExt: UnsignedTransactionExt +}; +export type PrepareAndBroadcastLedgerSignedTxRequest = { + ledgerSignTxResp: LedgerSignTxResponse, + changeAdaAddr: AdaAddress, + unsignedTx: any, + txExt: UnsignedTransactionExt, +}; export type UpdateWalletRequest = { walletId: string, name: string, @@ -346,9 +370,10 @@ export default class AdaApi { const { changeAdaAddress, txExt }: AdaFeeEstimateResponse = await getAdaTransactionFee(receiver, amount); - const trezorSignTxPayload: TrezorSignTxPayload = await createTrezorSignTxPayload(txExt); + const trezorSignTxPayload: TrezorSignTxPayload = await createTrezorSignTxPayload(txExt); Logger.debug('AdaApi::createTrezorSignTxData success: ' + stringifyData(trezorSignTxPayload)); + return { trezorSignTxPayload, changeAddress: changeAdaAddress @@ -361,18 +386,18 @@ export default class AdaApi { } } - async sendTrezorSignedTx( - request: SendTrezorSignedTxRequest - ): Promise { - Logger.debug('AdaApi::sendTrezorSignedTx called'); + async broadcastTrezorSignedTx( + request: BroadcastTrezorSignedTxRequest + ): Promise { + Logger.debug('AdaApi::broadcastTrezorSignedTx called'); const { signedTxHex, changeAdaAddr } = request; try { - const response = await newTrezorTransaction(signedTxHex, changeAdaAddr); - Logger.debug('AdaApi::sendTrezorSignedTx success: ' + stringifyData(response)); + const response = await broadcastTrezorSignedTx(signedTxHex, changeAdaAddr); + Logger.debug('AdaApi::broadcastTrezorSignedTx success: ' + stringifyData(response)); return response; } catch (error) { - Logger.error('AdaApi::sendTrezorSignedTx error: ' + stringifyError(error)); + Logger.error('AdaApi::broadcastTrezorSignedTx error: ' + stringifyError(error)); if (error instanceof InvalidWitnessError) { throw new InvalidWitnessError(); @@ -383,6 +408,66 @@ export default class AdaApi { } } + async createLedgerSignTxData( + request: CreateLedgerSignTxDataRequest + ): Promise { + try { + Logger.debug('AdaApi::createLedgerSignTxData called'); + const { receiver, amount } = request; + + const { changeAdaAddress, txExt }: AdaFeeEstimateResponse + = await getAdaTransactionFee(receiver, amount); + + const ledgerSignTxPayload: LedgerSignTxPayload = await createLedgerSignTxPayload(txExt); + + Logger.debug('AdaApi::createLedgerSignTxData success: ' + stringifyData(ledgerSignTxPayload)); + return { + ledgerSignTxPayload, + changeAddress: changeAdaAddress, + txExt + }; + } catch (error) { + Logger.error('AdaApi::createLedgerSignTxData error: ' + stringifyError(error)); + + if (error instanceof LocalizableError) { + // we found it as a LocalizableError, so could throw it as it is. + throw error; + } else { + // We don't know what the problem was so throw a generic error + throw new GenericApiError(); + } + } + } + + async prepareAndBroadcastLedgerSignedTx( + request: PrepareAndBroadcastLedgerSignedTxRequest + ): Promise { + try { + Logger.debug('AdaApi::prepareAndBroadcastLedgerSignedTx called'); + + const { ledgerSignTxResp, changeAdaAddr, unsignedTx, txExt } = request; + const response = await prepareAndBroadcastLedgerSignedTx( + ledgerSignTxResp, + changeAdaAddr, + unsignedTx, + txExt + ); + Logger.debug('AdaApi::prepareAndBroadcastLedgerSignedTx success: ' + stringifyData(response)); + + return response; + } catch (error) { + Logger.error('AdaApi::prepareAndBroadcastLedgerSignedTx error: ' + stringifyError(error)); + + if (error instanceof LocalizableError) { + // we found it as a LocalizableError, so could throw it as it is. + throw error; + } else { + // We don't know what the problem was so throw a generic error + throw new GenericApiError(); + } + } + } + async calculateTransactionFee( request: TransactionFeeRequest ): Promise { @@ -557,12 +642,12 @@ export default class AdaApi { } } - async createTrezorWallet( - request: CreateTrezorWalletRequest - ): Promise { + async createHardwareWallet( + request: CreateHardwareWalletRequest + ): Promise { try { - Logger.debug('AdaApi::connectTrezor called'); - const { walletName, publicMasterKey, deviceFeatures } = request; + Logger.debug('AdaApi::createHardwareWallet called'); + const { walletName, publicMasterKey, hwFeatures } = request; const assurance = 'CWANormal'; const unit = 0; @@ -573,23 +658,23 @@ export default class AdaApi { cwUnit: unit }, cwHardwareInfo: { - vendor: deviceFeatures.vendor, - model: deviceFeatures.model, - deviceId: deviceFeatures.device_id, - label: deviceFeatures.label, - majorVersion: deviceFeatures.major_version, - minorVersion: deviceFeatures.minor_version, - patchVersion: deviceFeatures.patch_version, - language: deviceFeatures.language, publicMasterKey, + vendor: hwFeatures.vendor, + model: hwFeatures.model, + deviceId: hwFeatures.deviceId, + label: hwFeatures.label, + majorVersion: hwFeatures.majorVersion, + minorVersion: hwFeatures.minorVersion, + patchVersion: hwFeatures.patchVersion, + language: hwFeatures.language, }, }; - const wallet: AdaWallet = await createTrezorWallet({ walletInitData }); + const wallet: AdaWallet = await createWallet({ walletInitData }); - Logger.debug('AdaApi::connectTrezor success'); + Logger.debug('AdaApi::createHardwareWallet success'); return _createWalletFromServerData(wallet); } catch (error) { - Logger.error('AdaApi::connectTrezor error: ' + stringifyError(error)); + Logger.error('AdaApi::createHardwareWallet error: ' + stringifyError(error)); if (error instanceof LocalizableError) { // we found it as a LocalizableError, so could throw it as it is. diff --git a/app/api/ada/lib/cardanoCrypto/cryptoWallet.js b/app/api/ada/lib/cardanoCrypto/cryptoWallet.js index 337a899ba9..dac251643d 100644 --- a/app/api/ada/lib/cardanoCrypto/cryptoWallet.js +++ b/app/api/ada/lib/cardanoCrypto/cryptoWallet.js @@ -110,3 +110,16 @@ export function getCryptoDaedalusWalletFromMnemonics( wallet.config.protocol_magic = protocolMagic; return wallet; } + +/** Generate a Daedalus /wallet/ to create transactions. Do not save this. Regenerate every time. + * Note: key encoded as hex-string + */ +export function getCryptoDaedalusWalletFromMasterKey( + masterKey: string, +): CryptoDaedalusWallet { + const encodedKey = Buffer.from(masterKey, 'hex'); + + const wallet: CryptoDaedalusWallet = getResultOrFail(Wallet.fromDaedalusMasterKey(encodedKey)); + wallet.config.protocol_magic = protocolMagic; + return wallet; +} diff --git a/app/api/ada/lib/utils.js b/app/api/ada/lib/utils.js index 6a2a3c42ec..6517a6a6e4 100644 --- a/app/api/ada/lib/utils.js +++ b/app/api/ada/lib/utils.js @@ -79,7 +79,7 @@ const _getTxCondition = (state: string): AdaTransactionCondition => { return 'CPtxWontApply'; }; -class CborIndefiniteLengthArray { +export class CborIndefiniteLengthArray { elements: Array; constructor(elements: Array) { this.elements = elements; diff --git a/app/api/ada/lib/yoroi-backend-api.js b/app/api/ada/lib/yoroi-backend-api.js index f113408479..d10b0ff30b 100644 --- a/app/api/ada/lib/yoroi-backend-api.js +++ b/app/api/ada/lib/yoroi-backend-api.js @@ -57,7 +57,7 @@ export type UtxoSumForAddressesResponse = { export const getTxsBodiesForUTXOs = ( txsHashes: Array -): Promise> => ( +): Promise<{[key: string]:string}> => ( axios( `${backendUrl}/api/txs/txBodies`, { @@ -66,7 +66,7 @@ export const getTxsBodiesForUTXOs = ( txsHashes } } - ).then(response => Object.values(response.data)) + ).then(response => response.data) .catch((error) => { Logger.error('yoroi-backend-api::getTxsBodiesForUTXOs error: ' + stringifyError(error)); throw new GetTxsBodiesForUTXOsApiError(); diff --git a/app/api/common.js b/app/api/common.js index 24fd4e852b..14ad69e46b 100644 --- a/app/api/common.js +++ b/app/api/common.js @@ -4,12 +4,12 @@ import BigNumber from 'bignumber.js'; import { defineMessages } from 'react-intl'; -import type { Features } from 'trezor-connect'; import LocalizableError from '../i18n/LocalizableError'; import WalletTransaction from '../domain/WalletTransaction'; import WalletAddress from '../domain/WalletAddress'; import Wallet from '../domain/Wallet'; +import { HWFeatures } from '../types/HWConnectStoreTypes'; import type { SignedResponse } from './ada/lib/yoroi-backend-api'; import type { TransactionExportRow, @@ -140,12 +140,12 @@ export type RestoreWalletRequest = { }; export type RestoreWalletResponse = Wallet; -export type CreateTrezorWalletRequest = { - publicMasterKey: string, +export type CreateHardwareWalletRequest = { walletName: string, - deviceFeatures: Features + publicMasterKey: string, + hwFeatures: HWFeatures, }; -export type CreateTrezorWalletResponse = Wallet; +export type CreateHardwareWalletResponse = Wallet; export type UpdateWalletPasswordRequest = { walletId: string, @@ -158,7 +158,9 @@ export type UpdateWalletResponse = Wallet; export type CreateTransactionResponse = SignedResponse; -export type SendTrezorSignedTxResponse = SignedResponse; +export type BroadcastTrezorSignedTxResponse = SignedResponse; + +export type PrepareAndBroadcastLedgerSignedTxResponse = SignedResponse; export type GetWalletsResponse = Array; diff --git a/app/assets/images/trezor/connect/about-prerequisite-header-icon.inline.svg b/app/assets/images/hardware-wallet/about-prerequisite-header-icon.inline.svg similarity index 100% rename from app/assets/images/trezor/connect/about-prerequisite-header-icon.inline.svg rename to app/assets/images/hardware-wallet/about-prerequisite-header-icon.inline.svg diff --git a/app/assets/images/hardware-wallet/ledger/about.inline.svg b/app/assets/images/hardware-wallet/ledger/about.inline.svg new file mode 100644 index 0000000000..597f5f3f74 --- /dev/null +++ b/app/assets/images/hardware-wallet/ledger/about.inline.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/hardware-wallet/ledger/connect-error.inline.svg b/app/assets/images/hardware-wallet/ledger/connect-error.inline.svg new file mode 100644 index 0000000000..8747134e23 --- /dev/null +++ b/app/assets/images/hardware-wallet/ledger/connect-error.inline.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/hardware-wallet/ledger/connect-load.gif b/app/assets/images/hardware-wallet/ledger/connect-load.gif new file mode 100644 index 0000000000..c48602c64c Binary files /dev/null and b/app/assets/images/hardware-wallet/ledger/connect-load.gif differ diff --git a/app/assets/images/hardware-wallet/ledger/save-error.inline.svg b/app/assets/images/hardware-wallet/ledger/save-error.inline.svg new file mode 100644 index 0000000000..718835a6a9 --- /dev/null +++ b/app/assets/images/hardware-wallet/ledger/save-error.inline.svg @@ -0,0 +1,54 @@ + + + + save-error.inline + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/hardware-wallet/ledger/save-load.inline.svg b/app/assets/images/hardware-wallet/ledger/save-load.inline.svg new file mode 100644 index 0000000000..70bce40350 --- /dev/null +++ b/app/assets/images/hardware-wallet/ledger/save-load.inline.svg @@ -0,0 +1,56 @@ + + + + save-start.inline + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/trezor/connect/about-trezor.inline.svg b/app/assets/images/hardware-wallet/trezor/about.inline.svg similarity index 99% rename from app/assets/images/trezor/connect/about-trezor.inline.svg rename to app/assets/images/hardware-wallet/trezor/about.inline.svg index ddea1cba1a..b4d1899bb6 100644 --- a/app/assets/images/trezor/connect/about-trezor.inline.svg +++ b/app/assets/images/hardware-wallet/trezor/about.inline.svg @@ -1,8 +1,5 @@ - - pic copy - Created with Sketch. diff --git a/app/assets/images/trezor/connect/connect-error.inline.svg b/app/assets/images/hardware-wallet/trezor/connect-error.inline.svg similarity index 100% rename from app/assets/images/trezor/connect/connect-error.inline.svg rename to app/assets/images/hardware-wallet/trezor/connect-error.inline.svg diff --git a/app/assets/images/trezor/connect/connect-load.gif b/app/assets/images/hardware-wallet/trezor/connect-load.gif similarity index 100% rename from app/assets/images/trezor/connect/connect-load.gif rename to app/assets/images/hardware-wallet/trezor/connect-load.gif diff --git a/app/assets/images/trezor/connect/save-error.inline.svg b/app/assets/images/hardware-wallet/trezor/save-error.inline.svg similarity index 100% rename from app/assets/images/trezor/connect/save-error.inline.svg rename to app/assets/images/hardware-wallet/trezor/save-error.inline.svg diff --git a/app/assets/images/trezor/connect/save-load.inline.svg b/app/assets/images/hardware-wallet/trezor/save-load.inline.svg similarity index 100% rename from app/assets/images/trezor/connect/save-load.inline.svg rename to app/assets/images/hardware-wallet/trezor/save-load.inline.svg diff --git a/app/assets/images/top-bar/with-ledger-nano-s-logo-white.inline.svg b/app/assets/images/top-bar/with-ledger-nano-s-logo-white.inline.svg new file mode 100644 index 0000000000..3db09a0df8 --- /dev/null +++ b/app/assets/images/top-bar/with-ledger-nano-s-logo-white.inline.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/top-bar/with-trezor-logo-white.inline.svg b/app/assets/images/top-bar/with-trezor-logo-white.inline.svg deleted file mode 100644 index 18178d0f6e..0000000000 --- a/app/assets/images/top-bar/with-trezor-logo-white.inline.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/top-bar/with-trezor-t-logo-white.inline.svg b/app/assets/images/top-bar/with-trezor-t-logo-white.inline.svg new file mode 100644 index 0000000000..9604620fea --- /dev/null +++ b/app/assets/images/top-bar/with-trezor-t-logo-white.inline.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/trezor/connect/connect-start.gif b/app/assets/images/trezor/connect/connect-start.gif deleted file mode 100644 index 93e8c96528..0000000000 Binary files a/app/assets/images/trezor/connect/connect-start.gif and /dev/null differ diff --git a/app/assets/images/trezor/connect/save-start.inline.svg b/app/assets/images/trezor/connect/save-start.inline.svg deleted file mode 100644 index 18fda8e491..0000000000 --- a/app/assets/images/trezor/connect/save-start.inline.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - save.inline - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/components/settings/categories/DisplaySettings.js b/app/components/settings/categories/DisplaySettings.js index bee007e7de..2a90ac4e30 100644 --- a/app/components/settings/categories/DisplaySettings.js +++ b/app/components/settings/categories/DisplaySettings.js @@ -76,7 +76,7 @@ export default class DisplaySettings extends Component { getThemeVars, exportTheme, hasCustomTheme, - onExternalLinkClick + onExternalLinkClick } = this.props; const { intl } = this.context; diff --git a/app/components/topbar/TopBarCategory.js b/app/components/topbar/TopBarCategory.js index bc85f9457d..a979bb44a3 100644 --- a/app/components/topbar/TopBarCategory.js +++ b/app/components/topbar/TopBarCategory.js @@ -22,9 +22,10 @@ export default class TopBarCategory extends Component { className ]); + const isWithHW = (className === 'with-trezor-t' || className === 'with-ledger-nano-s'); const iconStyles = classNames([ className === 'wallets' ? styles.walletsIcon : null, - className === 'with-trezor-t' ? styles.withTrezorTIcon : null, + isWithHW ? styles.withHardwareWalletIcon : null, styles.icon ]); diff --git a/app/components/topbar/TopBarCategory.scss b/app/components/topbar/TopBarCategory.scss index 0a945cf0eb..4ac70351c9 100644 --- a/app/components/topbar/TopBarCategory.scss +++ b/app/components/topbar/TopBarCategory.scss @@ -15,27 +15,27 @@ } .active { - .icon:not(.withTrezorTIcon) path { + .icon:not(.withHardwareWalletIcon) path { stroke: var(--theme-button-primary-background-color); fill: var(--theme-button-primary-background-color); } - .icon:not(.walletsIcon):not(.withTrezorTIcon) path { + .icon:not(.walletsIcon):not(.withHardwareWalletIcon) path { fill: var(--theme-button-primary-background-color); } - .icon:not(.walletsIcon):not(.withTrezorTIcon) polygon { + .icon:not(.walletsIcon):not(.withHardwareWalletIcon) polygon { fill: var(--theme-button-primary-background-color); } - .withTrezorTIcon { + .withHardwareWalletIcon { & > svg { // plus-logo & > g > g:nth-child(1) { stroke: none; fill: var(--theme-button-primary-background-color); } - // trezor-t-logo + // hardware wallet (trezor/ledger) & > g > g:nth-child(2) { stroke: var(--theme-button-primary-background-color); } @@ -68,7 +68,7 @@ } } -.withTrezorTIcon { +.withHardwareWalletIcon { flex-shrink: 0; & > svg { width: 64px; @@ -78,7 +78,7 @@ stroke: none; fill: var(--theme-icon-topbar-color); } - // trezor-t-logo + // hardware wallet (trezor/ledger) & > g > g:nth-child(2) { stroke: var(--theme-icon-topbar-color); } @@ -96,6 +96,10 @@ &.with-trezor-t { left: 64px; + } + + &.with-ledger-nano-s { + left: 64px; } &.daedalus-transfer { diff --git a/app/components/transfer/TransferInstructionsPage.js b/app/components/transfer/TransferInstructionsPage.js index dcfa1023d7..d2ab6baca5 100644 --- a/app/components/transfer/TransferInstructionsPage.js +++ b/app/components/transfer/TransferInstructionsPage.js @@ -10,11 +10,6 @@ import globalMessages from '../../i18n/global-messages'; import styles from './TransferInstructionsPage.scss'; const messages = defineMessages({ - instructionTitle: { - id: 'transfer.instructions.instructions.title.label', - defaultMessage: '!!!Instructions', - description: 'Label "Instructions" on the transfer instructions page.' - }, instructionsText: { id: 'transfer.instructions.instructions.text', defaultMessage: '!!!Before you can transfer funds, you must create a Yoroi wallet and back it up. Upon completion, you will receive a 15-word recovery phrase which can be used to restore your Yoroi wallet at any time.', @@ -33,15 +28,18 @@ const messages = defineMessages({ }); messages.fieldIsRequired = globalMessages.fieldIsRequired; +messages.instructionTitle = globalMessages.instructionTitle; type Props = { onFollowInstructionsPrerequisites: Function, onConfirm: Function, onPaperConfirm: Function, + onMasterKeyConfirm: Function, disableTransferFunds: boolean, attentionText: string, confirmationText: string, confirmationPaperText: string, + confirmationMasterKeyText: string, }; @observer @@ -57,10 +55,12 @@ export default class TransferInstructionsPage extends Component { onFollowInstructionsPrerequisites, onConfirm, onPaperConfirm, + onMasterKeyConfirm, disableTransferFunds, attentionText, confirmationText, confirmationPaperText, + confirmationMasterKeyText, } = this.props; const instructionsButtonClasses = classnames([ @@ -138,6 +138,14 @@ export default class TransferInstructionsPage extends Component { skin={ButtonSkin} /> + + diff --git a/app/components/transfer/TransferMasterKeyPage.js b/app/components/transfer/TransferMasterKeyPage.js new file mode 100644 index 0000000000..50f6ea90b7 --- /dev/null +++ b/app/components/transfer/TransferMasterKeyPage.js @@ -0,0 +1,168 @@ +// @flow +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import classnames from 'classnames'; +import { Input } from 'react-polymorph/lib/components/Input'; +import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; +import { Button } from 'react-polymorph/lib/components/Button'; +import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; +import { defineMessages, intlShape } from 'react-intl'; +import ReactToolboxMobxForm from '../../utils/ReactToolboxMobxForm'; +import BorderedBox from '../widgets/BorderedBox'; +import globalMessages from '../../i18n/global-messages'; +import styles from './TransferMasterKeyPage.scss'; +import config from '../../config'; + +const messages = defineMessages({ + masterKeyInputLabel: { + id: 'transfer.form.masterkey.input.label', + defaultMessage: '!!!Master key', + description: 'Label for the master key input on the transfer master key page.' + }, + masterKeyInputHint: { + id: 'transfer.form.masterkey.input.hint', + defaultMessage: '!!!Enter master key', + description: 'Hint "Enter master key" for the master keyinput on the transfer master key page.' + }, + masterKeyRequirements: { + id: 'transfer.form.masterkey.requirement', + defaultMessage: '!!!Note: master keys are 192 characters and hexadecimal-encoded', + description: 'Description of master key format' + }, +}); + +messages.fieldIsRequired = globalMessages.fieldIsRequired; +messages.invalidMasterKey = globalMessages.invalidMasterKey; +messages.nextButtonLabel = globalMessages.nextButtonLabel; +messages.backButtonLabel = globalMessages.backButtonLabel; +messages.step1 = globalMessages.step1; +messages.instructionTitle = globalMessages.instructionTitle; + +type Props = { + onSubmit: Function, + onBack: Function, + step0: string, +}; + +@observer +export default class TransferMasterKeyPage extends Component { + + static contextTypes = { + intl: intlShape.isRequired + }; + + form = new ReactToolboxMobxForm({ + fields: { + masterKey: { + label: this.context.intl.formatMessage(messages.masterKeyInputLabel), + placeholder: this.context.intl.formatMessage(messages.masterKeyInputHint), + value: '', + validators: [({ field }) => { + const value = field.value; + if (value === '') { + return [false, this.context.intl.formatMessage(messages.fieldIsRequired)]; + } + if (value.length !== 192) { + return [false, this.context.intl.formatMessage(messages.invalidMasterKey)]; + } + if (!value.match('^[0-9a-fA-F]+$')) { + return [false, this.context.intl.formatMessage(messages.invalidMasterKey)]; + } + return true; + }], + }, + }, + }, { + options: { + validateOnChange: true, + validationDebounceWait: config.forms.FORM_VALIDATION_DEBOUNCE_WAIT, + }, + }); + + submit = () => { + this.form.submit({ + onSuccess: (form) => { + const { masterKey } = form.values(); + const payload = { + masterKey, + }; + this.props.onSubmit(payload); + }, + onError: () => {} + }); + }; + + render() { + const { intl } = this.context; + const { form } = this; + const { onBack, step0 } = this.props; + + const nextButtonClasses = classnames([ + 'proceedTransferButtonClasses', + 'primary', + styles.button, + ]); + const backButtonClasses = classnames([ + 'backTransferButtonClasses', + 'flat', + styles.button, + ]); + + const masterKeyField = form.$('masterKey'); + + return ( +
+ + +
+ + { /* Instructions for how to transfer */ } +
+
+ {intl.formatMessage(messages.instructionTitle)} +
+ +
    + { +
    + {step0} + {intl.formatMessage(messages.step1)} +

    + {intl.formatMessage(messages.masterKeyRequirements)} +
    + } +
+
+ + + +
+
+ +
+ +
+ +
+ ); + } +} diff --git a/app/components/transfer/TransferMasterKeyPage.scss b/app/components/transfer/TransferMasterKeyPage.scss new file mode 100644 index 0000000000..10eecaee5c --- /dev/null +++ b/app/components/transfer/TransferMasterKeyPage.scss @@ -0,0 +1,52 @@ +@import '../../themes/mixins/loading-spinner'; + +.component { + padding: 20px; +} + +.button { + display: block !important; + margin: 20px auto 0; +} + +.body { + color: var(--theme-bordered-box-text-color); + font-family: var(--font-regular); + font-size: 14px; + line-height: 19px; + + .title { + font-size: 11px; + font-family: var(--font-medium); + font-weight: 600; + line-height: 1.38; + margin-bottom: 4px; + padding-left: 10px; + text-transform: uppercase; + } + + .text { + font-size: 15px; + margin-bottom: 0.3%; + word-break: break-word; + } + + .instructionsList { + list-style-type: disc; + list-style-position: inside; + margin-bottom: 20px; + } + + .buttonsWrapper { + display: flex; + flex-direction: row-reverse; + justify-content: center; + } + + .submitWithPasswordButton { + &.spinning { + @include loading-spinner("../../assets/images/spinner-light.svg"); + } + } +} + diff --git a/app/components/transfer/TransferMnemonicPage.js b/app/components/transfer/TransferMnemonicPage.js index 96635b07bd..b5d4d1ac69 100644 --- a/app/components/transfer/TransferMnemonicPage.js +++ b/app/components/transfer/TransferMnemonicPage.js @@ -15,16 +15,6 @@ import styles from './TransferMnemonicPage.scss'; import config from '../../config'; const messages = defineMessages({ - title: { - id: 'transfer.form.instructions.title.label', - defaultMessage: '!!!Instructions', - description: 'Label "Instructions" on the transfer mnemonic page.' - }, - step1: { - id: 'transfer.form.instructions.step1.text', - defaultMessage: '!!!It will take about 1 minute to restore your balance. In the next step, you will be presented with a transaction that will move all of your funds. Please review the details of the transaction carefully. You will need to pay a standard transaction fee on the Cardano network to make the transaction.', - description: 'Text for instructions step 1 on the transfer mnemonic page.' - }, recoveryPhraseInputLabel: { id: 'transfer.form.recovery.phrase.input.label', defaultMessage: '!!!Recovery phrase', @@ -45,19 +35,13 @@ const messages = defineMessages({ defaultMessage: '!!!Invalid recovery phrase', description: 'Error message shown when invalid recovery phrase was entered.' }, - backButtonLabel: { - id: 'transfer.form.back', - defaultMessage: '!!!Back', - description: 'Label for the back button on the transfer mnemonic page.' - }, - nextButtonLabel: { - id: 'transfer.form.next', - defaultMessage: '!!!Next', - description: 'Label for the next button on the transfer mnemonic page.' - }, }); messages.fieldIsRequired = globalMessages.fieldIsRequired; +messages.nextButtonLabel = globalMessages.nextButtonLabel; +messages.backButtonLabel = globalMessages.backButtonLabel; +messages.step1 = globalMessages.step1; +messages.instructionTitle = globalMessages.instructionTitle; type Props = { onSubmit: Function, @@ -138,7 +122,7 @@ export default class TransferMnemonicPage extends Component { { /* Instructions for how to transfer */ }
- {intl.formatMessage(messages.title)} + {intl.formatMessage(messages.instructionTitle)}
    diff --git a/app/components/wallet/WalletAdd.js b/app/components/wallet/WalletAdd.js index b608274d91..72700cdef5 100644 --- a/app/components/wallet/WalletAdd.js +++ b/app/components/wallet/WalletAdd.js @@ -24,6 +24,11 @@ const messages = defineMessages({ defaultMessage: '!!!Connect to Trezor', description: 'Description for the "Trezor" button on the wallet add dialog.', }, + useLedgerDescription: { + id: 'wallet.add.dialog.ledger.description', + defaultMessage: '!!!Connect to Ledger', + description: 'Description for the "Ledger" button on the wallet add dialog.', + }, restoreDescription: { id: 'wallet.add.dialog.restore.description', defaultMessage: '!!!Restore wallet from backup', @@ -38,12 +43,19 @@ const messages = defineMessages({ id: 'wallet.add.dialog.createTrezorWalletNotificationMessage', defaultMessage: '!!!Trezor Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.', description: 'Trezor Connect notification message shown during async wallet restore for Hardware wallet on the wallet add screen.', + }, + createLedgerWalletNotificationMessage: { + id: 'wallet.add.dialog.createLedgerWalletNotificationMessage', + defaultMessage: '!!!Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.', + description: 'Ledger Connect notification message shown during async wallet restore for Hardware wallet on the wallet add screen.', } }); type Props = { onTrezor: Function, isCreateTrezorWalletActive: boolean, + onLedger: Function, + isCreateLedgerWalletActive: boolean, onCreate: Function, onRestore: Function, isRestoreActive: boolean, @@ -61,6 +73,8 @@ export default class WalletAdd extends Component { const { onTrezor, isCreateTrezorWalletActive, + onLedger, + isCreateLedgerWalletActive, onCreate, onRestore, isRestoreActive, @@ -71,6 +85,8 @@ export default class WalletAdd extends Component { let activeNotification = null; if (isCreateTrezorWalletActive) { activeNotification = 'createTrezorWalletNotificationMessage'; + } else if (isCreateLedgerWalletActive) { + activeNotification = 'createLedgerWalletNotificationMessage'; } else if (isRestoreActive) { activeNotification = 'restoreNotificationMessage'; } @@ -78,8 +94,15 @@ export default class WalletAdd extends Component { return (
    + {/* Enable this when Ledger is available */} + {/*
    -
    +
    ); const dailogActions = [{ className: isActionProcessing ? styles.processing : null, - label: intl.formatMessage(messages.nextButtonLabel), + label: intl.formatMessage(globalMessages.nextButtonLabel), primary: true, disabled: false, onClick: submit, @@ -173,7 +145,7 @@ export default class AboutDialog extends Component { {introBlock} {middleBlock} - + ); } } diff --git a/app/components/wallet/trezorConnect/ConnectDialog.js b/app/components/wallet/hwConnect/trezor/ConnectDialog.js similarity index 64% rename from app/components/wallet/trezorConnect/ConnectDialog.js rename to app/components/wallet/hwConnect/trezor/ConnectDialog.js index b3a1925b58..22d19574f3 100644 --- a/app/components/wallet/trezorConnect/ConnectDialog.js +++ b/app/components/wallet/hwConnect/trezor/ConnectDialog.js @@ -5,49 +5,39 @@ import { defineMessages, intlShape } from 'react-intl'; import classnames from 'classnames'; import SvgInline from 'react-svg-inline'; -import globalMessages from '../../../i18n/global-messages'; -import LocalizableError from '../../../i18n/LocalizableError'; +import globalMessages from '../../../../i18n/global-messages'; +import LocalizableError from '../../../../i18n/LocalizableError'; -import Dialog from '../../widgets/Dialog'; -import DialogBackButton from '../../widgets/DialogBackButton'; -import DialogCloseButton from '../../widgets/DialogCloseButton'; +import Dialog from '../../../widgets/Dialog'; +import DialogBackButton from '../../../widgets/DialogBackButton'; +import DialogCloseButton from '../../../widgets/DialogCloseButton'; -import ProgressStepBlock from './common/ProgressStepBlock'; -import HelpLinkBlock from './common/HelpLinkBlock'; -import TrezorErrorBlock from './common/TrezorErrorBlock'; +import ProgressStepBlock from '../common/ProgressStepBlock'; +import HelpLinkBlock from './HelpLinkBlock'; +import HWErrorBlock from '../common/HWErrorBlock'; -import connectLoadGIF from '../../../assets/images/trezor/connect/connect-load.gif'; -import connectStartGIF from '../../../assets/images/trezor/connect/connect-start.gif'; -import connectErrorSVG from '../../../assets/images/trezor/connect/connect-error.inline.svg'; +import connectLoadGIF from '../../../../assets/images/hardware-wallet/trezor/connect-load.gif'; +import connectErrorSVG from '../../../../assets/images/hardware-wallet/trezor/connect-error.inline.svg'; -import type { ProgressInfo } from '../../../stores/ada/TrezorConnectStore'; -import { StepState } from '../../../stores/ada/TrezorConnectStore'; +import { ProgressInfo, StepState } from '../../../../types/HWConnectStoreTypes'; -import { Logger } from '../../../utils/logging'; +import { Logger } from '../../../../utils/logging'; -import styles from './ConnectDialog.scss'; +import styles from '../common/ConnectDialog.scss'; + +const connectStartGIF = connectLoadGIF; const messages = defineMessages({ connectIntroTextLine1: { - id: 'wallet.trezor.dialog.step.connect.introText.line.1', + id: 'wallet.connect.trezor.dialog.step.connect.introText.line.1', defaultMessage: '!!!After connecting your Trezor device to the computer press the Connect button.', description: 'Header text of about step on the Connect to Trezor Hardware Wallet dialog.' }, connectIntroTextLine2: { - id: 'wallet.trezor.dialog.step.connect.introText.line.2', + id: 'wallet.connect.trezor.dialog.step.connect.introText.line.2', defaultMessage: '!!!A new tab will appear, please follow the instructions in the new tab.', description: 'Header text of about step on the Connect to Trezor Hardware Wallet dialog.' }, - connectIntroTextLine3: { - id: 'wallet.trezor.dialog.step.connect.introText.line.3', - defaultMessage: '!!!This process shares the Cardano public key with Yoroi.', - description: 'Header text of about step on the Connect to Trezor Hardware Wallet dialog.' - }, - connectButtonLabel: { - id: 'wallet.trezor.dialog.connect.button.label', - defaultMessage: '!!!Connect', - description: 'Label for the "Connect" button on the Connect to Trezor Hardware Wallet dialog.' - }, }); type Props = { @@ -74,7 +64,7 @@ export default class ConnectDialog extends Component {
    {intl.formatMessage(messages.connectIntroTextLine1)}
    {intl.formatMessage(messages.connectIntroTextLine2)}
    - {intl.formatMessage(messages.connectIntroTextLine3)}
    + {intl.formatMessage(globalMessages.hwConnectDialogConnectIntroTextLine3)}
    ); let middleBlock = null; @@ -109,7 +99,7 @@ export default class ConnectDialog extends Component { const dailogActions = [{ className: isActionProcessing ? styles.processing : null, - label: intl.formatMessage(messages.connectButtonLabel), + label: intl.formatMessage(globalMessages.hwConnectDialogConnectButtonLabel), primary: true, disabled: isActionProcessing, onClick: submit @@ -129,7 +119,7 @@ export default class ConnectDialog extends Component { {introBlock} {middleBlock} - + ); } } diff --git a/app/components/wallet/hwConnect/trezor/HelpLinkBlock.js b/app/components/wallet/hwConnect/trezor/HelpLinkBlock.js new file mode 100644 index 0000000000..fcdf2efbfc --- /dev/null +++ b/app/components/wallet/hwConnect/trezor/HelpLinkBlock.js @@ -0,0 +1,46 @@ +// @flow +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import SvgInline from 'react-svg-inline'; + +import externalLinkSVG from '../../../../assets/images/link-external.inline.svg'; +import { ProgressInfo } from '../../../../types/HWConnectStoreTypes'; +import styles from '../common/HelpLinkBlock.scss'; + +const messages = defineMessages({ + helpLinkYoroiWithTrezor: { + id: 'wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor', + defaultMessage: '!!!https://yoroi-wallet.com/', + description: 'Tutorial link about how to use Yoroi with Trezor on the Connect to Trezor Hardware Wallet dialog.' + }, + helpLinkYoroiWithTrezorText: { + id: 'wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text', + defaultMessage: '!!!Click here to know more about how to use Yoroi with Trezor.', + description: 'Tutorial link text about how to use Yoroi with Trezor on the Connect to Trezor Hardware Wallet dialog.' + }, +}); + +type Props = { + progressInfo: ProgressInfo, +}; + +@observer +export default class HelpLinkBlock extends Component { + + static contextTypes = { + intl: intlShape.isRequired + }; + + render() { + const { intl } = this.context; + + return ( + ); + } +} diff --git a/app/components/wallet/trezorConnect/SaveDialog.js b/app/components/wallet/hwConnect/trezor/SaveDialog.js similarity index 64% rename from app/components/wallet/trezorConnect/SaveDialog.js rename to app/components/wallet/hwConnect/trezor/SaveDialog.js index 5803af39f3..b449e6fb03 100644 --- a/app/components/wallet/trezorConnect/SaveDialog.js +++ b/app/components/wallet/hwConnect/trezor/SaveDialog.js @@ -7,52 +7,37 @@ import SvgInline from 'react-svg-inline'; import { Input } from 'react-polymorph/lib/components/Input'; import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; -import globalMessages from '../../../i18n/global-messages'; -import LocalizableError from '../../../i18n/LocalizableError'; +import globalMessages from '../../../../i18n/global-messages'; +import LocalizableError from '../../../../i18n/LocalizableError'; -import Dialog from '../../widgets/Dialog'; -import DialogCloseButton from '../../widgets/DialogCloseButton'; +import Dialog from '../../../widgets/Dialog'; +import DialogCloseButton from '../../../widgets/DialogCloseButton'; -import ProgressStepBlock from './common/ProgressStepBlock'; -import HelpLinkBlock from './common/HelpLinkBlock'; -import TrezorErrorBlock from './common/TrezorErrorBlock'; +import ProgressStepBlock from '../common/ProgressStepBlock'; +import HelpLinkBlock from './HelpLinkBlock'; +import HWErrorBlock from '../common/HWErrorBlock'; -import saveLoadGIF from '../../../assets/images/trezor/connect/save-load.inline.svg'; -import saveStartSVG from '../../../assets/images/trezor/connect/save-start.inline.svg'; -import saveErrorSVG from '../../../assets/images/trezor/connect/save-error.inline.svg'; +import saveLoadSVG from '../../../../assets/images/hardware-wallet/trezor/save-load.inline.svg'; +import saveErrorSVG from '../../../../assets/images/hardware-wallet/trezor/save-error.inline.svg'; -import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; -import { isValidWalletName } from '../../../utils/validations'; +import ReactToolboxMobxForm from '../../../../utils/ReactToolboxMobxForm'; +import { isValidWalletName } from '../../../../utils/validations'; -import type { ProgressInfo } from '../../../stores/ada/TrezorConnectStore'; -import { StepState } from '../../../stores/ada/TrezorConnectStore'; +import { ProgressInfo, StepState } from '../../../../types/HWConnectStoreTypes'; -import { Logger } from '../../../utils/logging'; +import { Logger } from '../../../../utils/logging'; -import styles from './SaveDialog.scss'; -import config from '../../../config'; +import styles from '../common/SaveDialog.scss'; +import config from '../../../../config'; + +const saveStartSVG = saveLoadSVG; const messages = defineMessages({ - saveWalletNameInputLabel: { - id: 'wallet.trezor.dialog.step.save.walletName.label', - defaultMessage: '!!!Wallet name', - description: 'Label for the wallet name input on the Connect to Trezor Hardware Wallet dialog.' - }, - saveWalletNameInputPlaceholder: { - id: 'wallet.trezor.dialog.step.save.walletName.hint', - defaultMessage: '!!!Enter wallet name', - description: 'Placeholder "Enter wallet name" for the wallet name input on the wallet restore dialog.' - }, saveWalletNameInputBottomInfo: { - id: 'wallet.trezor.dialog.step.save.walletName.info', + id: 'wallet.connect.trezor.dialog.step.save.walletName.info', defaultMessage: '!!!We have fetched Trezor device’s name for you; you can use as it is or assign a different name.', description: 'Hint for the wallet name input on the wallet restore dialog.' }, - saveButtonLabel: { - id: 'wallet.trezor.dialog.save.button.label', - defaultMessage: '!!!Save', - description: 'Label for the "Save" button on the Connect to Trezor Hardware Wallet dialog.' - }, }); type Props = { @@ -80,8 +65,8 @@ export default class SaveDialog extends Component { this.form = new ReactToolboxMobxForm({ fields: { walletName: { - label: intl.formatMessage(messages.saveWalletNameInputLabel), - placeholder: intl.formatMessage(messages.saveWalletNameInputPlaceholder), + label: intl.formatMessage(globalMessages.hwConnectDialogSaveWalletNameInputLabel), + placeholder: intl.formatMessage(globalMessages.hwConnectDialogSaveWalletNameInputPH), value: defaultWalletName, validators: [({ field }) => ( [ @@ -126,7 +111,7 @@ export default class SaveDialog extends Component { case StepState.LOAD: middleBlock = (
    - +
    ); break; case StepState.PROCESS: @@ -148,7 +133,7 @@ export default class SaveDialog extends Component { const dailogActions = [{ className: isActionProcessing ? styles.processing : null, - label: intl.formatMessage(messages.saveButtonLabel), + label: intl.formatMessage(globalMessages.hwConnectDialogSaveButtonLabel), primary: true, disabled: isActionProcessing, onClick: this.save @@ -167,7 +152,7 @@ export default class SaveDialog extends Component { {walletNameBlock} {middleBlock} - + ); } diff --git a/app/components/wallet/send/trezor/TrezorSendConfirmationDialog.js b/app/components/wallet/send/HWSendConfirmationDialog.js similarity index 70% rename from app/components/wallet/send/trezor/TrezorSendConfirmationDialog.js rename to app/components/wallet/send/HWSendConfirmationDialog.js index db81efe275..bb68b6a9c5 100644 --- a/app/components/wallet/send/trezor/TrezorSendConfirmationDialog.js +++ b/app/components/wallet/send/HWSendConfirmationDialog.js @@ -2,34 +2,17 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import classnames from 'classnames'; -import { defineMessages, intlShape } from 'react-intl'; +import { intlShape } from 'react-intl'; +import type { MessageDescriptorMap } from 'react-intl'; -import Dialog from '../../../widgets/Dialog'; -import DialogCloseButton from '../../../widgets/DialogCloseButton'; -import ErrorBlock from '../../../widgets/ErrorBlock'; +import Dialog from '../../widgets/Dialog'; +import DialogCloseButton from '../../widgets/DialogCloseButton'; +import ErrorBlock from '../../widgets/ErrorBlock'; -import globalMessages from '../../../../i18n/global-messages'; -import LocalizableError from '../../../../i18n/LocalizableError'; +import globalMessages from '../../../i18n/global-messages'; +import LocalizableError from '../../../i18n/LocalizableError'; -import styles from './TrezorSendConfirmationDialog.scss'; - -const messages = defineMessages({ - infoLine1: { - id: 'wallet.send.trezor.confirmationDialog.info.line.1', - defaultMessage: '!!!After connecting your Trezor device to your computer, press the Send using Trezor button.', - description: 'Informative message line 1 in the wallet trezor send confirmation dialog.' - }, - infoLine2: { - id: 'wallet.send.trezor.confirmationDialog.info.line.2', - defaultMessage: '!!!A new tab will appear. Please follow the instructions in the new tab.', - description: 'Informative message line 2 in the wallet trezor send confirmation dialog.' - }, - sendUsingTrezorButtonLabel: { - id: 'wallet.send.trezor.confirmationDialog.submit', - defaultMessage: '!!!Send using Trezor', - description: 'Label for the send button in the wallet send confirmation dialog.' - }, -}); +import styles from './HWSendConfirmationDialog.scss'; type Props = { amount: string, @@ -38,6 +21,7 @@ type Props = { transactionFee: string, currencyUnit: string, amountToNaturalUnits: Function, + messages: MessageDescriptorMap, isSubmitting: boolean, error: ?LocalizableError, onSubmit: Function, @@ -45,7 +29,7 @@ type Props = { }; @observer -export default class TrezorSendConfirmationDialog extends Component { +export default class HWSendConfirmationDialog extends Component { static contextTypes = { intl: intlShape.isRequired, @@ -60,6 +44,7 @@ export default class TrezorSendConfirmationDialog extends Component { transactionFee, currencyUnit, isSubmitting, + messages, error, onCancel, } = this.props; @@ -117,13 +102,13 @@ export default class TrezorSendConfirmationDialog extends Component { ]); const actions = [ { - label: intl.formatMessage(globalMessages.walletSendConfirmationBackButtonLabel), + label: intl.formatMessage(globalMessages.backButtonLabel), onClick: isSubmitting ? () => {} // noop : onCancel }, { - label: intl.formatMessage(messages.sendUsingTrezorButtonLabel), + label: intl.formatMessage(messages.sendUsingHWButtonLabel), onClick: this.submit, primary: true, className: confirmButtonClasses, diff --git a/app/components/wallet/send/trezor/TrezorSendConfirmationDialog.scss b/app/components/wallet/send/HWSendConfirmationDialog.scss similarity index 89% rename from app/components/wallet/send/trezor/TrezorSendConfirmationDialog.scss rename to app/components/wallet/send/HWSendConfirmationDialog.scss index 9dff8460c6..8e736e3f1d 100644 --- a/app/components/wallet/send/trezor/TrezorSendConfirmationDialog.scss +++ b/app/components/wallet/send/HWSendConfirmationDialog.scss @@ -1,4 +1,4 @@ -@import '../../../../themes/mixins/loading-spinner'; +@import '../../../themes/mixins/loading-spinner'; .dialog { font-family: var(--font-medium); @@ -11,11 +11,11 @@ }; .submitButtonSpinning { - @include loading-spinner("../../../../assets/images/spinner-light.svg"); + @include loading-spinner("../../../assets/images/spinner-light.svg"); } .infoBlock { - background-color: var(--theme-trezor-send-confirmation-info-block-background-color); + background-color: var(--theme-hw-send-confirmation-info-block-background-color); font-family: var(--font-regular); padding: 5px; margin-bottom: 20px; diff --git a/app/components/wallet/send/WalletSendConfirmationDialog.js b/app/components/wallet/send/WalletSendConfirmationDialog.js index a3db95475f..2d47cf04e2 100644 --- a/app/components/wallet/send/WalletSendConfirmationDialog.js +++ b/app/components/wallet/send/WalletSendConfirmationDialog.js @@ -116,7 +116,7 @@ export default class WalletSendConfirmationDialog extends Component { const actions = [ { - label: intl.formatMessage(globalMessages.walletSendConfirmationBackButtonLabel), + label: intl.formatMessage(globalMessages.backButtonLabel), onClick: isSubmitting ? () => {} // noop : onCancel diff --git a/app/components/wallet/send/WalletSendForm.js b/app/components/wallet/send/WalletSendForm.js index 4c87ea7c26..44964158a3 100755 --- a/app/components/wallet/send/WalletSendForm.js +++ b/app/components/wallet/send/WalletSendForm.js @@ -17,7 +17,7 @@ import BorderedBox from '../../widgets/BorderedBox'; import styles from './WalletSendForm.scss'; import globalMessages from '../../../i18n/global-messages'; import WalletSendConfirmationDialog from './WalletSendConfirmationDialog'; -import TrezorSendConfirmationDialog from './trezor/TrezorSendConfirmationDialog'; +import HWSendConfirmationDialog from './HWSendConfirmationDialog'; import { formattedAmountToBigNumber, formattedAmountToNaturalUnits @@ -66,11 +66,6 @@ const messages = defineMessages({ defaultMessage: '!!!You can add a message if you want', description: 'Hint in the "description" text area in the wallet send form.' }, - nextButtonLabel: { - id: 'wallet.send.form.next', - defaultMessage: '!!!Next', - description: 'Label for the next button on the wallet send form.' - }, invalidAddress: { id: 'wallet.send.form.errors.invalidAddress', defaultMessage: '!!!Please enter a valid address.', @@ -104,20 +99,21 @@ const messages = defineMessages({ }); messages.fieldIsRequired = globalMessages.fieldIsRequired; +messages.nextButtonLabel = globalMessages.nextButtonLabel; type Props = { currencyUnit: string, currencyMaxIntegerDigits: number, currencyMaxFractionalDigits: number, hasAnyPending: boolean, - isTrezorTWallet: boolean, + isHardwareWallet: boolean, validateAmount: (amountInNaturalUnits: string) => Promise, calculateTransactionFee: (receiver: string, amount: string) => Promise, addressValidator: Function, openDialogAction: Function, isDialogOpen: Function, webWalletConfirmationDialogRenderCallback: Function, - trezorTWalletConfirmationDialogRenderCallback: Function, + hardwareWalletConfirmationDialogRenderCallback: Function, }; type State = { @@ -288,7 +284,7 @@ export default class WalletSendForm extends Component { /** Makes custom button component depends on type of active wallet * basically controlles which confirmation dialog to open * CASE 1: Web Wallet - * CASE 2: Trezor Model T Wallet */ + * CASE 2: Hardware Wallet (Trezor or Ledger) */ _makeInvokeConfirmationButton(): Node { const { intl } = this.context; @@ -308,8 +304,8 @@ export default class WalletSendForm extends Component { * WalletSendForm.js is a component and we already have Send Confirmation dialog's containers * WalletSendForm.js tries to open a container but invoking it component * this whole logic should be in WalletSendForm's container */ - const targetDialog = this.props.isTrezorTWallet ? - TrezorSendConfirmationDialog : + const targetDialog = this.props.isHardwareWallet ? + HWSendConfirmationDialog : WalletSendConfirmationDialog; const onMouseUp = () => openDialogAction({ dialog: targetDialog @@ -330,22 +326,22 @@ export default class WalletSendForm extends Component { /** Makes component for respective send confirmation dialog * returns null when dialog is not needed * CASE 1: Web Wallet - * CASE 2: Trezor Model T Wallet */ + * CASE 2: Hardware Wallet (Trezor or Ledger) */ _makeConfirmationDialogComponent(): Node { let component = null; const { isDialogOpen, webWalletConfirmationDialogRenderCallback, - trezorTWalletConfirmationDialogRenderCallback + hardwareWalletConfirmationDialogRenderCallback } = this.props; // this function is called from render hence it should return ASAP, hence using renderCB let renderCB = null; if (isDialogOpen(WalletSendConfirmationDialog)) { renderCB = webWalletConfirmationDialogRenderCallback; - } else if (isDialogOpen(TrezorSendConfirmationDialog)) { - renderCB = trezorTWalletConfirmationDialogRenderCallback; + } else if (isDialogOpen(HWSendConfirmationDialog)) { + renderCB = hardwareWalletConfirmationDialogRenderCallback; } if (renderCB) { diff --git a/app/components/wallet/summary/WalletSummary.js b/app/components/wallet/summary/WalletSummary.js index e75f393f2f..a3d9690f4f 100644 --- a/app/components/wallet/summary/WalletSummary.js +++ b/app/components/wallet/summary/WalletSummary.js @@ -85,7 +85,7 @@ export default class WalletSummary extends Component { {!isLoadingTransactions ? ( + {/* It should be enable in the future with a video on how to connect Ledger */} + {/* + /> */} { onFollowInstructionsPrerequisites, onConfirm, onPaperConfirm, + onMasterKeyConfirm, disableTransferFunds, } = this.props; @@ -53,10 +60,12 @@ export default class DaedalusTransferInstructionsPage extends Component { onFollowInstructionsPrerequisites={onFollowInstructionsPrerequisites} onConfirm={onConfirm} onPaperConfirm={onPaperConfirm} + onMasterKeyConfirm={onMasterKeyConfirm} disableTransferFunds={disableTransferFunds} attentionText={intl.formatMessage(messages.attentionText)} confirmationText={intl.formatMessage(messages.confirmationText)} confirmationPaperText={intl.formatMessage(messages.confirmationPaperText)} + confirmationMasterKeyText={intl.formatMessage(messages.confirmationMasterKeyText)} /> ); } diff --git a/app/containers/transfer/DaedalusTransferMasterKeyFormPage.js b/app/containers/transfer/DaedalusTransferMasterKeyFormPage.js new file mode 100644 index 0000000000..a81a31f2f4 --- /dev/null +++ b/app/containers/transfer/DaedalusTransferMasterKeyFormPage.js @@ -0,0 +1,42 @@ +// @flow +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import globalMessages from '../../i18n/global-messages'; +import TransferMasterKeyPage from '../../components/transfer/TransferMasterKeyPage'; + +const messages = defineMessages({ + step0: { + id: 'daedalusTransfer.form.instructions.step0MasterKey.text', + defaultMessage: '!!!Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.', + description: 'Text for instructions step 0 on the Daedalus transfer master key form page.' + }, +}); + +messages.fieldIsRequired = globalMessages.fieldIsRequired; + +type Props = { + onSubmit: Function, + onBack: Function, +}; + +@observer +export default class DaedalusTransferMasterKeyFormPage extends Component { + + static contextTypes = { + intl: intlShape.isRequired + }; + + render() { + const { intl } = this.context; + const { onBack, onSubmit } = this.props; + + return ( + + ); + } +} diff --git a/app/containers/transfer/DaedalusTransferPage.js b/app/containers/transfer/DaedalusTransferPage.js index f102ddf72b..97a6751b36 100644 --- a/app/containers/transfer/DaedalusTransferPage.js +++ b/app/containers/transfer/DaedalusTransferPage.js @@ -8,6 +8,7 @@ import StaticTopbarTitle from '../../components/topbar/StaticTopbarTitle'; import TopBar from '../../components/topbar/TopBar'; import DaedalusTransferInstructionsPage from './DaedalusTransferInstructionsPage'; import DaedalusTransferFormPage from './DaedalusTransferFormPage'; +import DaedalusTransferMasterKeyFormPage from './DaedalusTransferMasterKeyFormPage'; import DaedalusTransferWaitingPage from './DaedalusTransferWaitingPage'; import DaedalusTransferSummaryPage from './DaedalusTransferSummaryPage'; import DaedalusTransferErrorPage from './DaedalusTransferErrorPage'; @@ -59,8 +60,16 @@ export default class DaedalusTransferPage extends Component { this._getDaedalusTransferActions().startTransferPaperFunds.trigger(); } - setupTransferFunds = (payload: { recoveryPhrase: string }) => { - this._getDaedalusTransferActions().setupTransferFunds.trigger(payload); + startTransferMasterKey = () => { + this._getDaedalusTransferActions().startTransferMasterKey.trigger(); + } + + setupTransferFundsWithMnemonic = (payload: { recoveryPhrase: string }) => { + this._getDaedalusTransferActions().setupTransferFundsWithMnemonic.trigger(payload); + }; + + setupTransferFundsWithMasterKey = (payload: { masterKey: string }) => { + this._getDaedalusTransferActions().setupTransferFundsWithMasterKey.trigger(payload); }; /** Broadcast the transfer transaction if one exists and return to wallet page */ @@ -115,6 +124,7 @@ export default class DaedalusTransferPage extends Component { onAnswerYes={this.goToReceiveScreen} onConfirm={this.startTransferFunds} onPaperConfirm={this.startTransferPaperFunds} + onMasterKeyConfirm={this.startTransferMasterKey} disableTransferFunds={daedalusTransfer.disableTransferFunds} /> @@ -123,7 +133,7 @@ export default class DaedalusTransferPage extends Component { return ( wallets.isValidMnemonic( mnemonic, @@ -138,7 +148,7 @@ export default class DaedalusTransferPage extends Component { return ( wallets.isValidPaperMnemonic(mnemonic, 27)} validWords={validWords} @@ -146,6 +156,15 @@ export default class DaedalusTransferPage extends Component { /> ); + case 'gettingMasterKey': + return ( + + + + ); case 'restoringAddresses': case 'checkingAddresses': case 'generatingTx': diff --git a/app/containers/wallet/WalletAddPage.js b/app/containers/wallet/WalletAddPage.js index 36121e7f6b..25a2604d37 100644 --- a/app/containers/wallet/WalletAddPage.js +++ b/app/containers/wallet/WalletAddPage.js @@ -8,6 +8,7 @@ import WalletRestoreDialog from '../../components/wallet/WalletRestoreDialog'; import WalletCreateDialog from '../../components/wallet/WalletCreateDialog'; import WalletBackupDialog from '../../components/wallet/WalletBackupDialog'; import WalletTrezorConnectDialogContainer from './dialogs/WalletTrezorConnectDialogContainer'; +import WalletLedgerConnectDialogContainer from './dialogs/WalletLedgerConnectDialogContainer'; import WalletCreateDialogContainer from './dialogs/WalletCreateDialogContainer'; import WalletRestoreDialogContainer from './dialogs/WalletRestoreDialogContainer'; import WalletBackupDialogContainer from './dialogs/WalletBackupDialogContainer'; @@ -18,6 +19,7 @@ import resolver from '../../utils/imports'; import type { InjectedProps } from '../../types/injectedPropsType'; import AdaWalletsStore from '../../stores/ada/AdaWalletsStore'; import TrezorConnectStore from '../../stores/ada/TrezorConnectStore'; +import LedgerConnectStore from '../../stores/ada/LedgerConnectStore'; import AddWalletFooter from '../footer/AddWalletFooter'; type Props = InjectedProps; @@ -63,12 +65,18 @@ export default class WalletAddPage extends Component { const { actions, stores } = this.props; const { uiDialogs } = stores; const { isRestoreActive } = wallets; - const { isCreateTrezorWalletActive } = this._getTrezorConnectStore(); + const isCreateTrezorWalletActive = this._getTrezorConnectStore().isCreateHWActive; + const isCreateLedgerWalletActive = this._getLedgerConnectStore().isCreateHWActive; const openTrezorConnectDialog = () => { actions.dialogs.open.trigger({ dialog: WalletTrezorConnectDialogContainer }); + this.props.actions[environment.API].trezorConnect.init.trigger(); + }; + const openLedgerConnectDialog = () => { + actions.dialogs.open.trigger({ dialog: WalletLedgerConnectDialogContainer }); + this.props.actions[environment.API].ledgerConnect.init.trigger(); }; - let content = null; + let content = null; if (uiDialogs.isOpen(WalletCreateDialog)) { content = ( @@ -89,11 +97,21 @@ export default class WalletAddPage extends Component { onClose={this.onClose} /> ); + } else if (uiDialogs.isOpen(WalletLedgerConnectDialogContainer)) { + content = ( + + ); } else { content = ( actions.dialogs.open.trigger({ dialog: WalletCreateDialog })} onRestore={() => actions.dialogs.open.trigger({ dialog: WalletRestoreDialog })} isRestoreActive={isRestoreActive} @@ -119,4 +137,7 @@ export default class WalletAddPage extends Component { return this.props.stores.substores[environment.API].trezorConnect; } + _getLedgerConnectStore(): LedgerConnectStore { + return this.props.stores.substores[environment.API].ledgerConnect; + } } diff --git a/app/containers/wallet/WalletSendPage.js b/app/containers/wallet/WalletSendPage.js index c8aa5c241c..f39f945fad 100644 --- a/app/containers/wallet/WalletSendPage.js +++ b/app/containers/wallet/WalletSendPage.js @@ -1,8 +1,9 @@ // @flow import React, { Component } from 'react'; -import type { Node } from 'react'; import { observer } from 'mobx-react'; -import { intlShape } from 'react-intl'; +import type { Node } from 'react'; +import { defineMessages, intlShape } from 'react-intl'; +import type { MessageDescriptorMap } from 'react-intl'; import environment from '../../environment'; import type { InjectedProps } from '../../types/injectedPropsType'; @@ -14,10 +15,50 @@ import { } from '../../config/numbersConfig'; import WalletSendForm from '../../components/wallet/send/WalletSendForm'; +// Web Wallet Confirmation import WalletSendConfirmationDialogContainer from './dialogs/WalletSendConfirmationDialogContainer'; -import TrezorSendConfirmationDialog from '../../components/wallet/send/trezor/TrezorSendConfirmationDialog'; import type { DialogProps } from './dialogs/WalletSendConfirmationDialogContainer'; +// Hardware Wallet Confirmation +import HWSendConfirmationDialog from '../../components/wallet/send/HWSendConfirmationDialog'; + +const messagesLedger: MessageDescriptorMap = defineMessages({ + infoLine1: { + id: 'wallet.send.ledger.confirmationDialog.info.line.1', + defaultMessage: '!!!After connecting your Ledger device to your computer’s USB port, press the Send using Ledger button.', + description: 'Informative message line 1 in the ledger wallet send confirmation dialog.' + }, + infoLine2: { + id: 'wallet.send.ledger.confirmationDialog.info.line.2', + defaultMessage: '!!!Make sure Cardano ADA app must remain open on the Ledger device throughout the process.', + description: 'Informative message line 2 in the ledger wallet send confirmation dialog.' + }, + sendUsingHWButtonLabel: { + id: 'wallet.send.ledger.confirmationDialog.submit', + defaultMessage: '!!!Send using Ledger', + description: 'Label for the send button in the ledger walle send confirmation dialog.' + }, +}); + +const messagesTrezor: MessageDescriptorMap = defineMessages({ + infoLine1: { + id: 'wallet.send.trezor.confirmationDialog.info.line.1', + defaultMessage: '!!!After connecting your Trezor device to your computer, press the Send using Trezor button.', + description: 'Informative message line 1 in the trezor wallet send confirmation dialog.' + }, + infoLine2: { + id: 'wallet.send.trezor.confirmationDialog.info.line.2', + defaultMessage: '!!!A new tab will appear. Please follow the instructions in the new tab.', + description: 'Informative message line 2 in the trezor wallet send confirmation dialog.' + }, + sendUsingHWButtonLabel: { + id: 'wallet.send.trezor.confirmationDialog.submit', + defaultMessage: '!!!Send using Trezor', + description: 'Label for the send button in the trezor wallet send confirmation dialog.' + }, +}); + + type Props = InjectedProps; @observer export default class WalletSendPage extends Component { @@ -50,9 +91,9 @@ export default class WalletSendPage extends Component { isDialogOpen={uiDialogs.isOpen} openDialogAction={actions.dialogs.open.trigger} webWalletConfirmationDialogRenderCallback={this.webWalletDoConfirmation} - trezorTWalletConfirmationDialogRenderCallback={this.trezorTWalletDoConfirmation} + hardwareWalletConfirmationDialogRenderCallback={this.hardwareWalletDoConfirmation} hasAnyPending={hasAnyPending} - isTrezorTWallet={activeWallet.isTrezorTWallet} + isHardwareWallet={activeWallet.isHardwareWallet} /> ); } @@ -73,24 +114,54 @@ export default class WalletSendPage extends Component { />); }; - /** Trezor Model T Wallet Confirmation dialog + /** Hardware Wallet (Trezor or Ledger) Confirmation dialog * Callback that creates a component to avoid the component knowing about actions/stores * separate container is not needed, this container acts as container for Confirmation dialog */ - trezorTWalletDoConfirmation = (dialogProps: DialogProps): Node => { - const trezorSendAction = this.props.actions[environment.API].trezorSend; - const trezorSendStore = this.props.stores.substores[environment.API].trezorSend; - return ( - ); + hardwareWalletDoConfirmation = (dialogProps: DialogProps): Node => { + const { active } = this.props.stores.substores[environment.API].wallets; + // Guard against potential null values + if (!active) throw new Error('Active wallet required for hardwareWalletDoConfirmation.'); + + let hwSendConfirmationDialog: Node = null; + if (active.isLedgerNanoSWallet) { + const ledgerSendAction = this.props.actions[environment.API].ledgerSend; + ledgerSendAction.init.trigger(); + const ledgerSendStore = this.props.stores.substores[environment.API].ledgerSend; + hwSendConfirmationDialog = ( + ); + } else if (active.isTrezorTWallet) { + const trezorSendAction = this.props.actions[environment.API].trezorSend; + const trezorSendStore = this.props.stores.substores[environment.API].trezorSend; + hwSendConfirmationDialog = ( + ); + } else { + throw new Error('Unsupported hardware wallet found at hardwareWalletDoConfirmation.'); + } + + return hwSendConfirmationDialog; }; } diff --git a/app/containers/wallet/WalletSummaryPage.js b/app/containers/wallet/WalletSummaryPage.js index 3dd8c31f19..a68462e40d 100644 --- a/app/containers/wallet/WalletSummaryPage.js +++ b/app/containers/wallet/WalletSummaryPage.js @@ -31,6 +31,7 @@ const messages = defineMessages({ const targetNotificationIds = [ globalMessages.walletCreatedNotificationMessage.id, globalMessages.walletRestoredNotificationMessage.id, + globalMessages.ledgerNanoSWalletIntegratedNotificationMessage.id, globalMessages.trezorTWalletIntegratedNotificationMessage.id, ]; diff --git a/app/containers/wallet/dialogs/WalletBackupDialogContainer.js b/app/containers/wallet/dialogs/WalletBackupDialogContainer.js index 0af036b423..16a935f6c0 100644 --- a/app/containers/wallet/dialogs/WalletBackupDialogContainer.js +++ b/app/containers/wallet/dialogs/WalletBackupDialogContainer.js @@ -36,6 +36,7 @@ export default class WalletBackupDialogContainer extends Component { acceptWalletBackupTermRecovery, restartWalletBackup, finishWalletBackup, + removeOneMnemonicWord, acceptPrivacyNoticeForWalletBackup, continueToRecoveryPhraseForWalletBackup } = actions.walletBackup; @@ -57,7 +58,7 @@ export default class WalletBackupDialogContainer extends Component { // Props for WalletRecoveryPhraseEntryDialog isTermDeviceAccepted={isTermDeviceAccepted} enteredPhrase={enteredPhrase} - canFinishBackup={isRecoveryPhraseValid && isTermDeviceAccepted && isTermRecoveryAccepted} + hasWord={() => recoveryPhraseWords.length > 0} isTermRecoveryAccepted={isTermRecoveryAccepted} isValid={isRecoveryPhraseValid} isSubmitting={createWalletRequest.isExecuting} @@ -68,6 +69,9 @@ export default class WalletBackupDialogContainer extends Component { onFinishBackup={() => { finishWalletBackup.trigger(); }} + removeWord={() => { + removeOneMnemonicWord.trigger(); + }} onRestartBackup={restartWalletBackup.trigger} recoveryPhraseSorted={recoveryPhraseSorted} /> diff --git a/app/containers/wallet/dialogs/WalletLedgerConnectDialogContainer.js b/app/containers/wallet/dialogs/WalletLedgerConnectDialogContainer.js new file mode 100644 index 0000000000..b389c826d0 --- /dev/null +++ b/app/containers/wallet/dialogs/WalletLedgerConnectDialogContainer.js @@ -0,0 +1,84 @@ +// @flow +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; + +import environment from '../../../environment'; +import type { InjectedDialogContainerProps } from '../../../types/injectedPropsType'; + +import AboutDialog from '../../../components/wallet/hwConnect/ledger/AboutDialog'; +import ConnectDialog from '../../../components/wallet/hwConnect/ledger/ConnectDialog'; +import SaveDialog from '../../../components/wallet/hwConnect/ledger/SaveDialog'; + +import { Logger } from '../../../utils/logging'; + +import LedgerConnectStore from '../../../stores/ada/LedgerConnectStore'; +import HWConnectActions from '../../../actions/ada/hw-connect-actions'; + +import { ProgressStep } from '../../../types/HWConnectStoreTypes'; + +type Props = InjectedDialogContainerProps; +@observer +export default class WalletLedgerConnectDialogContainer extends Component { + + cancel = () => { + this.props.onClose(); + this._getHWConnectActions().cancel.trigger(); + }; + + render() { + const ledgerConnectStore = this._getLedgerConnectStore(); + const hwConnectActions = this._getHWConnectActions(); + + let component = null; + + switch (ledgerConnectStore.progressInfo.currentStep) { + case ProgressStep.ABOUT: + component = ( + ); + break; + case ProgressStep.CONNECT: + component = ( + ); + break; + case ProgressStep.SAVE: + component = ( + ); + break; + default: + Logger.error('WalletLedgerConnectDialogContainer::render: something unexpected happened'); + break; + } + + return component; + } + + /** Returns the store which is responsible for this Container */ + _getLedgerConnectStore(): LedgerConnectStore { + return this.props.stores.substores[environment.API].ledgerConnect; + } + + /** Returns the action which is responsible for this Container */ + _getHWConnectActions(): HWConnectActions { + return this.props.actions[environment.API].ledgerConnect; + } +} diff --git a/app/containers/wallet/dialogs/WalletTrezorConnectDialogContainer.js b/app/containers/wallet/dialogs/WalletTrezorConnectDialogContainer.js index dcfbf7f12e..aab85ebd86 100644 --- a/app/containers/wallet/dialogs/WalletTrezorConnectDialogContainer.js +++ b/app/containers/wallet/dialogs/WalletTrezorConnectDialogContainer.js @@ -5,14 +5,15 @@ import { observer } from 'mobx-react'; import environment from '../../../environment'; import type { InjectedDialogContainerProps } from '../../../types/injectedPropsType'; -import AboutDialog from '../../../components/wallet/trezorConnect/AboutDialog'; -import ConnectDialog from '../../../components/wallet/trezorConnect/ConnectDialog'; -import SaveDialog from '../../../components/wallet/trezorConnect/SaveDialog'; +import AboutDialog from '../../../components/wallet/hwConnect/trezor/AboutDialog'; +import ConnectDialog from '../../../components/wallet/hwConnect/trezor/ConnectDialog'; +import SaveDialog from '../../../components/wallet/hwConnect/trezor/SaveDialog'; import { Logger } from '../../../utils/logging'; -import TrezorConnectStore, { ProgressStep } from '../../../stores/ada/TrezorConnectStore'; -import TrezorConnectActions from '../../../actions/ada/trezor-connect-actions'; +import TrezorConnectStore from '../../../stores/ada/TrezorConnectStore'; +import { ProgressStep } from '../../../types/HWConnectStoreTypes'; +import HWConnectActions from '../../../actions/ada/hw-connect-actions'; type Props = InjectedDialogContainerProps; @observer @@ -20,12 +21,12 @@ export default class WalletTrezorConnectDialogContainer extends Component cancel = () => { this.props.onClose(); - this._getTrezorConnectActions().cancel.trigger(); + this._getHWConnectActions().cancel.trigger(); }; render() { const trezorConnectStore = this._getTrezorConnectStore(); - const trezorConnectActions = this._getTrezorConnectActions(); + const hwConnectActions = this._getHWConnectActions(); let component = null; @@ -36,7 +37,7 @@ export default class WalletTrezorConnectDialogContainer extends Component progressInfo={trezorConnectStore.progressInfo} isActionProcessing={trezorConnectStore.isActionProcessing} error={trezorConnectStore.error} - submit={trezorConnectActions.submitAbout.trigger} + submit={hwConnectActions.submitAbout.trigger} cancel={this.cancel} />); break; @@ -46,8 +47,8 @@ export default class WalletTrezorConnectDialogContainer extends Component progressInfo={trezorConnectStore.progressInfo} isActionProcessing={trezorConnectStore.isActionProcessing} error={trezorConnectStore.error} - goBack={trezorConnectActions.goBacktToAbout.trigger} - submit={trezorConnectActions.submitConnect.trigger} + goBack={hwConnectActions.goBackToAbout.trigger} + submit={hwConnectActions.submitConnect.trigger} cancel={this.cancel} />); break; @@ -58,7 +59,7 @@ export default class WalletTrezorConnectDialogContainer extends Component isActionProcessing={trezorConnectStore.isActionProcessing} error={trezorConnectStore.error} defaultWalletName={trezorConnectStore.defaultWalletName} - submit={trezorConnectActions.submitSave.trigger} + submit={hwConnectActions.submitSave.trigger} cancel={this.cancel} />); break; @@ -76,7 +77,7 @@ export default class WalletTrezorConnectDialogContainer extends Component } /** Returns the action which is responsible for this Container */ - _getTrezorConnectActions(): TrezorConnectActions { + _getHWConnectActions(): HWConnectActions { return this.props.actions[environment.API].trezorConnect; } } diff --git a/app/domain/HWSignTx.js b/app/domain/HWSignTx.js new file mode 100644 index 0000000000..366b045084 --- /dev/null +++ b/app/domain/HWSignTx.js @@ -0,0 +1,51 @@ +// @flow + +// https://github.com/trezor/connect/issues/350 +import type { + InputTypeUTxO, + OutputTypeAddress, + OutputTypeChange, +} from '@cardano-foundation/ledgerjs-hw-app-cardano'; + +export type TrezorInput = { + path: string, + prev_hash: string, + prev_index: number, // I’m not sure what it is. cuOutIndex + type: number // refers to script type +} + +export type TrezorOutput = { + address?: string, + path?: string, + amount: string, +} + +export type TrezorSignTxPayload = { + inputs: Array, + outputs: Array, + transactions: Array, + protocol_magic: number // 764824073 = mainnet | 1097911063 = testnet (not yet supported) +} + +export type LedgerSignTxPayload = { + inputs: Array, + outputs: Array, +} + +export type LedgerUnsignedUtxo = { + txHash: string, + address: string, + coins: number, + outputIndex: number +}; +export type LedgerUnsignedInput = { + coins: number, + txHash: string, + outputIndex: number, + utxo: LedgerUnsignedUtxo +} +export type LedgerUnsignedOutput = { + address: string, + coins: number, + isChange: boolean +} diff --git a/app/domain/TrezorSignTx.js b/app/domain/TrezorSignTx.js deleted file mode 100644 index 477d5c91f7..0000000000 --- a/app/domain/TrezorSignTx.js +++ /dev/null @@ -1,22 +0,0 @@ -// @flow - -// https://github.com/trezor/connect/issues/350 -export type TrezorInput = { - path: string, - prev_hash: string, - prev_index: number, // I’m not sure what it is. cuOutIndex - type: number // refers to script type -} - -export type TrezorOutput = { - address?: string, - path?: string, - amount: string, -} - -export type TrezorSignTxPayload = { - inputs: Array, - outputs: Array, - transactions: Array, - protocol_magic: number // 764824073 = mainnet | 1097911063 = testnet (not yet supported) -} diff --git a/app/domain/Wallet.js b/app/domain/Wallet.js index 5e0de01c90..7a298746da 100644 --- a/app/domain/Wallet.js +++ b/app/domain/Wallet.js @@ -4,7 +4,8 @@ import BigNumber from 'bignumber.js'; import type { AssuranceMode, AssuranceModeOption } from '../types/transactionAssuranceTypes'; import { assuranceModes, assuranceModeOptions } from '../config/transactionAssuranceConfig'; import type { WalletType, WalletHardwareInfo } from '../types/WalletType'; -import { WalletTypeOption, TrezorT } from '../config/WalletTypeConfig'; +import { WalletTypeOption } from '../types/WalletType'; +import Config from '../config'; /** External representation of the internal Wallet in the API layer */ export default class Wallet { @@ -52,8 +53,15 @@ export default class Wallet { @computed get isTrezorTWallet(): boolean { return (this.isHardwareWallet && !!this.hardwareInfo - && this.hardwareInfo.vendor === TrezorT.vendor - && this.hardwareInfo.model === TrezorT.model); + && this.hardwareInfo.vendor === Config.wallets.hardwareWallet.trezorT.VENDOR + && this.hardwareInfo.model === Config.wallets.hardwareWallet.trezorT.MODEL); + } + + @computed get isLedgerNanoSWallet(): boolean { + return (this.isHardwareWallet + && !!this.hardwareInfo + && this.hardwareInfo.vendor === Config.wallets.hardwareWallet.ledgerNanoS.VENDOR + && this.hardwareInfo.model === Config.wallets.hardwareWallet.ledgerNanoS.MODEL); } @computed get assuranceMode(): AssuranceMode { diff --git a/app/i18n/global-messages.js b/app/i18n/global-messages.js index 13ca47a935..861b2998d5 100644 --- a/app/i18n/global-messages.js +++ b/app/i18n/global-messages.js @@ -7,10 +7,10 @@ import { defineMessages } from 'react-intl'; */ export default defineMessages({ - invalidMnemonic: { - id: 'global.errors.invalidMnemonic', - defaultMessage: '!!!Invalid phrase entered, please check.', - description: 'Error message shown when invalid bip39 mnemonic was entered.' + invalidMasterKey: { + id: 'global.errors.invalidMasterKey', + defaultMessage: '!!!Invalid master key entered, please check.', + description: 'Error message shown when invalid master key was entered.' }, fieldIsRequired: { id: 'global.errors.fieldIsRequired', @@ -37,6 +37,21 @@ export default defineMessages({ defaultMessage: '!!!Note: Password needs to be at least 12 characters long.', description: 'Password instructions note.', }, + nextButtonLabel: { + id: 'global.labels.next', + defaultMessage: '!!!Next', + description: 'Label next button.' + }, + backButtonLabel: { + id: 'global.labels.back', + defaultMessage: '!!!Back', + description: 'Label next button.' + }, + instructionTitle: { + id: 'transfer.instructions.instructions.title.label', + defaultMessage: '!!!Instructions', + description: 'Label "Instructions"' + }, cancel: { id: 'global.labels.cancel', defaultMessage: '!!!Cancel', @@ -142,31 +157,91 @@ export default defineMessages({ defaultMessage: '!!!Total', description: 'Label for the "Total" in the wallet send confirmation dialog.', }, - walletSendConfirmationBackButtonLabel: { - id: 'wallet.send.confirmationDialog.back', - defaultMessage: '!!!Back', - description: 'Label for the back button in the wallet send confirmation dialog.' + hwConnectDialogConnectButtonLabel: { + id: 'wallet.connect.hw.dialog.connect.button.label', + defaultMessage: '!!!Connect', + description: 'Label for the "Connect" button on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogSaveButtonLabel: { + id: 'wallet.connect.hw.dialog.save.button.label', + defaultMessage: '!!!Save', + description: 'Label for the "Save" button on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogAboutIntroTextLine1: { + id: 'wallet.connect.hw.dialog.step.about.introText.line.1', + defaultMessage: '!!!A hardware wallet is a small USB device that adds an extra level of security to your wallet.', + description: 'Header text of about step on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogAboutIntroTextLine2: { + id: 'wallet.connect.hw.dialog.step.about.introText.line.2', + defaultMessage: '!!!It is more secure because your private key never leaves the hardware wallet.', + description: 'Header text of about step on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogAboutIntroTextLine3: { + id: 'wallet.connect.hw.dialog.step.about.introText.line.3', + defaultMessage: '!!!Protects your funds when using a computer compromised with viruses, phishing attempts, malware and others.', + description: 'Header text of about step on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogAboutPrerequisite4: { + id: 'wallet.connect.hw.dialog.step.about.prerequisite.4', + defaultMessage: '!!!Your computer must remain connected to the Internet throughout the process.', + description: 'Fourth Prerequisite on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogAboutPrerequisiteHeader: { + id: 'wallet.connect.hw.dialog.step.about.prerequisite.header', + defaultMessage: '!!!Prerequisites', + description: 'Prerequisite header on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogConnectIntroTextLine3: { + id: 'wallet.connect.hw.dialog.step.connect.introText.line.3', + defaultMessage: '!!!This process shares the Cardano public key with Yoroi.', + description: 'Header text of about step on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogSaveWalletNameInputLabel: { + id: 'wallet.connect.hw.dialog.step.save.walletName.label', + defaultMessage: '!!!Wallet name', + description: 'Label for the wallet name input on the Connect to any Hardware Wallet dialog.' + }, + hwConnectDialogSaveWalletNameInputPH: { + id: 'wallet.connect.hw.dialog.step.save.walletName.hint', + defaultMessage: '!!!Enter wallet name', + description: 'Placeholder for the wallet name input on the Connect to any Hardware Wallet dialog.' + }, + ledgerConnectAllDialogTitle: { + id: 'wallet.connect.ledger.dialog.title.label', + defaultMessage: '!!!Connect to Ledger Hardware Wallet', + description: 'Label "Connect to Ledger Hardware Wallet" on the Connect to Ledger Hardware Wallet dialog.' + }, + ledgerError101: { + id: 'wallet.connect.ledger.error.101', + defaultMessage: '!!!Failed to connect. Please check your ledger device and retry.', + description: '' }, trezorConnectAllDialogTitle: { - id: 'wallet.trezor.dialog.title.label', + id: 'wallet.connect.trezor.dialog.title.label', defaultMessage: '!!!Connect to Trezor Hardware Wallet', description: 'Label "Connect to Trezor Hardware Wallet" on the Connect to Trezor Hardware Wallet dialog.' }, trezorError101: { - id: 'wallet.trezor.error.101', + id: 'wallet.connect.trezor.error.101', defaultMessage: '!!!Failed to connect trezor.io. Please check your Internet connection and retry.', description: '' }, - trezorError102: { - id: 'wallet.trezor.error.102', + hwError101: { + id: 'wallet.hw.common.error.101', defaultMessage: '!!!Necessary permissions were not granted by the user. Please retry.', description: '' }, trezorError103: { - id: 'wallet.trezor.error.103', + id: 'wallet.connect.trezor.error.103', defaultMessage: '!!!Cancelled. Please retry.', description: '' }, + hwConnectDialogSaveError101: { + id: 'wallet.connect.hw.dialog.step.save.error.101', + defaultMessage: '!!!Failed to save. Please check your Internet connection and retry.', + description: ' on the Connect to Trezor Hardware Wallet dialog.' + }, walletCreatedNotificationMessage: { id: 'wallet.summary.page.walletCreatedNotificationMessage', defaultMessage: '!!!You have successfully created a new Wallet', @@ -177,6 +252,11 @@ export default defineMessages({ defaultMessage: '!!!You have successfully restored your Wallet', description: 'Notification Message for successful wallet restoration.', }, + ledgerNanoSWalletIntegratedNotificationMessage: { + id: 'wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage', + defaultMessage: '!!!You have successfully integrated with your Ledger Nano S device', + description: 'Notification Message for successful integration with Ledger Nano S device.', + }, trezorTWalletIntegratedNotificationMessage: { id: 'wallet.summary.page.trezorTWalletIntegratedNotificationMessage', defaultMessage: '!!!You have successfully integrated with your Trezor Model T device', @@ -187,6 +267,11 @@ export default defineMessages({ defaultMessage: '!!!No transactions found', description: 'Message shown when wallet transaction search returns zero results.' }, + step1: { + id: 'transfer.form.instructions.step1.text', + defaultMessage: '!!!It will take about 1 minute to restore your balance. In the next step, you will be presented with a transaction that will move all of your funds. Please review the details of the transaction carefully. You will need to pay a standard transaction fee on the Cardano network to make the transaction.', + description: 'Text for instructions step 1 on the transfer mnemonic page.' + }, }); export const environmentSpecificMessages = { diff --git a/app/i18n/locales/de-DE.json b/app/i18n/locales/de-DE.json index 42a6d802dd..512a64c86b 100644 --- a/app/i18n/locales/de-DE.json +++ b/app/i18n/locales/de-DE.json @@ -47,11 +47,12 @@ "daedalusTransfer.error.noTransferTxError": "Es gibt keine zu versendende Transaktion.", "daedalusTransfer.error.transferFundsError": "Das Guthaben konnte nicht überwiesen werden.", "daedalusTransfer.error.webSocketRestoreError": "Fehler beim Wiederherstellen der Blockchain-Adressen.", - "daedalusTransfer.errorPage.backButton.label": "Zurück", "daedalusTransfer.errorPage.title.label": "Daedalus-Wallet konnte nicht wiederhergestellt werden.", "daedalusTransfer.form.instructions.step0.text": "Bitte die 12-stellige Wiederherstellungs-Phrase deiner Daedalus-Wallet eingeben, um den Kontostand von Daedalus nach Yoroi zu übertragen.", + "daedalusTransfer.form.instructions.step0MasterKey.text": "!!!Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.form.instructions.step0Paper.text": "Bitte die 27-stellige Wiederherstellungs-Phrase deiner Daedalus Paper-Wallet eingeben, um den Kontostand von Daedalus nach Yoroi zu übertragen.", "daedalusTransfer.instructions.attention.confirmation": "Gesamtguthaben von Daedalus-Wallet übernehmen", + "daedalusTransfer.instructions.attention.confirmationMasterKey": "!!!Transfer all funds from Daedalus master key", "daedalusTransfer.instructions.attention.confirmationPaper": "Gesamtguthaben von Daedalus Paper-Wallet übernehmen", "daedalusTransfer.instructions.attention.text": "Yoroi und Daedalus-Wallets verwenden unterschiedliche Schlüssel-Ableitungs-Schemen und haben ein eigenes Adressformat. Aus diesem Grund kann nicht mit der Daedalus-Wallet fortgefahren werden. Stattdessen muss eine Yoroi-Wallet erstellt und der Kontostand dorthin übertragen werden (In Zukunft wird das nicht mehr so sein). Daedalus- und Yoroi-Wallets sind jedoch vollkommen kompatibel, um Guthaben zu übertragen. Wenn du keine Daedalus-Wallet installiert oder keinen Zugriff darauf hast, kannst du die aus 12 Wörtern (bzw. 27 bei einer Paper-Wallet) bestehende Wiederherstellungs-Phrase verwenden, um den Kontostand von Daedalus nach Yoroi zu übertragen.", "daedalusTransfer.summary.addressFrom.subLabel": "Daedalus Wallet-Adressen", @@ -64,13 +65,15 @@ "environment.apiVersion.cardano": "1.0.4", "environment.currency.ada": "ADA", "global.errors.fieldIsRequired": "Dieses Feld muss ausgefüllt werden.", - "global.errors.invalidMnemonic": "Ungültige Phrase eingegeben, bitte überprüfen.", + "global.errors.invalidMasterKey": "!!!Invalid master key entered, please check.", "global.errors.invalidRepeatPassword": "Stimmt nicht überein.", "global.errors.invalidWalletName": "Der Wallet-Name muss aus mindestens 1 und maximal 40 Zeichen bestehen.", "global.errors.invalidWalletPassword": "Ungültiges Passwort", + "global.labels.back": "!!!Back", "global.labels.cancel": "Abbrechen", "global.labels.change": "Wechseln", "global.labels.create": "Erstellen", + "global.labels.next": "!!!Next", "global.labels.remove": "Entfernen", "global.labels.save": "Speichern", "global.language.chinese.simplified": "简体中文", @@ -133,11 +136,11 @@ "settings.support.reportProblem.link": "Support-Anfrage", "settings.support.reportProblem.title": "Ein Problem melden", "testnet.label.message": "WARNUNG: Das ist ein Testnetz. ADA im Testnetz hat keinen Geldwert. Weitere Informationen sind in der FAQ zu finden {faqLink}", - "transfer.form.back": "Zurück", "transfer.form.errors.invalidRecoveryPhrase": "Ungültige Wiederherstellungs-Phrase", "transfer.form.instructions.step1.text": "Es wird ungefähr eine Minute dauern, um deinen Kontostand wiederherzustellen. Im nächsten Schritt wird eine Transaktion angezeigt, die dein gesamtes Guthaben transferiert. Bitte überprüfe die Details der Transaktion sehr sorgfältig. Es ist notwendig, für diese Transaktion eine Standard-Transaktionsgebühr im Cardano Netzwerk zu bezahlen.", - "transfer.form.instructions.title.label": "Anleitungen", - "transfer.form.next": "Weiter", + "transfer.form.masterkey.input.hint": "!!!Enter master key", + "transfer.form.masterkey.input.label": "!!!Master key", + "transfer.form.masterkey.requirement": "!!!Note: master keys are 192 characters and hexadecimal-encoded", "transfer.form.recovery.phrase.input.hint": "Wiederherstellungs-Phrase eingeben", "transfer.form.recovery.phrase.input.label": "Wiederherstellungs-Phrase", "transfer.form.recovery.phrase.input.noResults": "Keine Ergebnisse", @@ -153,7 +156,9 @@ "transfer.summary.transactionFee.label": "Transaktionsgebühren", "transfer.summary.transferButton.label": "Geld überweisen", "wallet.add.dialog.create.description": "Eine neue Wallet erstellen", + "wallet.add.dialog.createLedgerWalletNotificationMessage": "!!!Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.createTrezorWalletNotificationMessage": "Es wird gerade eine Verbindung zu Trezor aufgebaut. Solange kann keine Wallet neu angelegt oder wiederhergestellt werden.", + "wallet.add.dialog.ledger.description": "!!!Connect to Ledger", "wallet.add.dialog.restore.description": "Wallet aus einem Backup wiederherstellen", "wallet.add.dialog.restoreNotificationMessage": "Die Wiederherstellung der Wallet ist in Bearbeitung. Solange kann keine Wallet neu erstellt oder wiederhergestellt werden.", "wallet.add.dialog.title.label": "Wallet hinzufügen", @@ -169,6 +174,49 @@ "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.device": "Ich verstehe, dass meine geheimen Schlüssel nur auf diesem Gerät und niemals auf den Server des Unternehmens gespeichert werden.", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "Ich verstehe, dass wenn diese Anwendung auf ein anderes Gerät verschoben oder gelöscht wird, \n mein Kontostand nur mittels dieser Wortgruppe wiederhergestellt werden kann, die ich mir notiert und sicher aufbewahrt habe.", "wallet.backup.recovery.phrase.entry.dialog.verification.instructions": "Klicke auf jedes Wort in der richtigen Reihenfolge, um die Wiederherstellungs-Phrase zu überprüfen", + "wallet.connect.hw.dialog.connect.button.label": "Verbinden", + "wallet.connect.hw.dialog.save.button.label": "Speichern", + "wallet.connect.hw.dialog.step.about.introText.line.1": "Eine Hardware Wallet ist ein kleines USB-Gerät, das eine zusätzliche Sicherheitsstufe für die Wallet bringt.", + "wallet.connect.hw.dialog.step.about.introText.line.2": "Es ist sicherer weil ihr privater Schlüssel niemals die Hardware-Wallet verlässt.", + "wallet.connect.hw.dialog.step.about.introText.line.3": "Damit wird Ihr Konto auch dann geschützt, wenn Ihr Computer durch Malware, Phishing usw. kompromittiert worden sein sollte.", + "wallet.connect.hw.dialog.step.about.label": "ÜBER", + "wallet.connect.hw.dialog.step.about.prerequisite.4": "Ihr Computer muss für den gesamten Prozess mit dem Internet verbunden bleiben.", + "wallet.connect.hw.dialog.step.about.prerequisite.header": "Voraussetzungen", + "wallet.connect.hw.dialog.step.connect.introText.line.3": "Dieser Prozess teilt den öffentlichen Schlüssel von Cardano mit Yoroi..", + "wallet.connect.hw.dialog.step.connect.label": "VERBINDEN", + "wallet.connect.hw.dialog.step.save.error.101": "Speichern fehlgeschlagen. Bitte die Internetverbindung prüfen und erneut versuchen.", + "wallet.connect.hw.dialog.step.save.label": "SPEICHERN", + "wallet.connect.hw.dialog.step.save.walletName.hint": "Wallet Namen eingeben", + "wallet.connect.hw.dialog.step.save.walletName.label": "WALLET NAMEN", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor": "!!!https://yoroi-wallet.com/", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor.text": "!!!Click here to know more about how to use Yoroi with Trezor.", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part1": "!!!Only Supports", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link": "!!!https://www.ledger.com/products/ledger-nano-s", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link.text": "!!!Ledger Nano S", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part3": "!!! model.", + "wallet.connect.ledger.dialog.step.about.prerequisite.2.part2": "!!!Cardano ADA app must be installed on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.3": "!!!The Trezor device screen must be unlocked.", + "wallet.connect.ledger.dialog.step.about.prerequisite.5": "!!!Trezor device must remain connected to the computer throughout the process", + "wallet.connect.ledger.dialog.step.connect.introText.line.1": "!!!After connecting your Trezor device to the computer press the Connect button.", + "wallet.connect.ledger.dialog.step.connect.introText.line.2": "!!!A new tab will appear, please follow the instructions in the new tab.", + "wallet.connect.ledger.dialog.step.save.walletName.info": "!!!We have fetched Trezor device’s name for you; you can use as it is or assign a different name.", + "wallet.connect.ledger.dialog.title.label": "!!!Connect to Ledger Hardware Wallet", + "wallet.connect.ledger.error.101": "!!!Failed to connect. Please check your ledger device and retry.", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Klicke hier um mehr über die Nutzung von Yoroi mit dem Trezor zu erfahren", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part1": "Unterstützt nur ", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T in der Version 2.0.8", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part3": " oder später.", + "wallet.connect.trezor.dialog.step.about.prerequisite.2": "Das Trezor Gerät muss vorher initialisiert werden.", + "wallet.connect.trezor.dialog.step.about.prerequisite.3": "Das Trezor-Display muss entsperrt werden.", + "wallet.connect.trezor.dialog.step.about.prerequisite.5": "Das Trezor-Gerät muss für den gesamten Prozess mit den Computer verbunden bleiben.", + "wallet.connect.trezor.dialog.step.connect.introText.line.1": "Nachdem sie den Trezor mit dem Computer verbunden haben, drücken sie bitte den Verbinden-Knopf.", + "wallet.connect.trezor.dialog.step.connect.introText.line.2": "Eine neue Seite wird erscheinen. Bitte die Anweisungen darauf befolgen.", + "wallet.connect.trezor.dialog.step.save.walletName.info": "Wir haben den Namen des Trezor-Geräts abgerufen. Es kann so verwendet oder mit einem anderen Namen versehen werden.", + "wallet.connect.trezor.dialog.title.label": "MIT DEM TREZOR HARDWARE WALLET VERBINDEN", + "wallet.connect.trezor.error.101": "Verbindung zu trezor.io fehlgeschlagen. Bitte die Internetverbindung prüfen und erneut versuchen.", + "wallet.connect.trezor.error.103": "Abgebrochen. Bitte wiederholen.", "wallet.create.dialog.create.personal.wallet.button.label": "Erstelle eine persönliche Wallet", "wallet.create.dialog.name.label": "Wallet-Name", "wallet.create.dialog.passwordFieldPlaceholder": "Passwort", @@ -176,11 +224,13 @@ "wallet.create.dialog.title": "Erstelle eine neue Wallet", "wallet.create.dialog.walletNameHint": "z.B. Einkaufs-Wallet", "wallet.create.dialog.walletPasswordLabel": "Wallet-Passwort", + "wallet.footer.buyLedgerHardwareWallet.text": "!!!Buy a Ledger hardware wallet.", "wallet.footer.buyTrezorHardwareWallet.text": "Kaufe eine Trezor Hardware-Wallet", "wallet.footer.howToConnectTrezor.text": "So verbindest du einen Trezor", "wallet.footer.howToCreateWallet.text": "Wie man eine Wallet erstellt", "wallet.footer.howToRestoreWallet.text": "Wie man eine Wallet wiederherstellt", "wallet.footer.whatIsHardwareWallet.text": "Was ist eine Hardware-Wallet", + "wallet.hw.common.error.101": "Es sind keine ausreichenden Berechtigungen erteilt worden. Bitte erneut versuchen.", "wallet.navigation.receive": "Empfangen", "wallet.navigation.send": "Senden", "wallet.navigation.transactions": "Transaktionen", @@ -194,6 +244,7 @@ "wallet.receive.page.walletReceiveInstructions": "Teile diese Wallet-Adresse mit, um Zahlungen zu erhalten. Um deine Privatsphäre zu schützen, werden jedes Mal neue Adressen erzeugt, sobald sie verwendet wurden.", "wallet.recovery.phrase.show.entry.dialog.button.labelClear": "Löschen", "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm": "Bestätigen", + "wallet.recovery.phrase.show.entry.dialog.button.labelRemoveLast": "!!!Remove last", "wallet.redeem.choices.tab.title.forceVended": "do not translate", "wallet.redeem.choices.tab.title.paperVended": "do not translate", "wallet.redeem.choices.tab.title.recoveryForceVended": "do not translate", @@ -249,7 +300,6 @@ "wallet.restore.dialog.walletPasswordLabel": "Wallet-Passwort", "wallet.send.confirmationDialog.addressToLabel": "An", "wallet.send.confirmationDialog.amountLabel": "Betrag", - "wallet.send.confirmationDialog.back": "Zurück", "wallet.send.confirmationDialog.feesLabel": "Gebühren", "wallet.send.confirmationDialog.submit": "Senden", "wallet.send.confirmationDialog.title": "Transaktion bestätigen", @@ -264,13 +314,16 @@ "wallet.send.form.errors.invalidAddress": "Bitte eine gültige Adresse eingeben.", "wallet.send.form.errors.invalidAmount": "Bitte einen gültigen Betrag eingeben.", "wallet.send.form.errors.invalidTitle": "Bitte einen Titel mit mindestens 3 Zeichen eingeben.", - "wallet.send.form.next": "Weiter", "wallet.send.form.receiver.hint": "Wallet-Adresse", "wallet.send.form.receiver.label": "Empfänger", "wallet.send.form.sendingIsDisabled": "Es kann keine neue Transaktion gesendet werden, solange noch eine aussteht.", "wallet.send.form.title.hint": "z.B. Geld für Frank", "wallet.send.form.title.label": "Titel", "wallet.send.form.transactionFeeError": "Nicht genug ADA für die Gebühren. Versuche einen kleineren Betrag zu senden.", + "wallet.send.ledger.confirmationDialog.info.line.1": "!!!After connecting your Trezor device to your computer, press the Send using Trezor button.", + "wallet.send.ledger.confirmationDialog.info.line.2": "!!!A new tab will appear. Please follow the instructions in the new tab.", + "wallet.send.ledger.confirmationDialog.submit": "!!!Send using Trezor", + "wallet.send.ledger.error.101": "!!!Signing cancelled on Ledger device. Please retry.", "wallet.send.trezor.confirmationDialog.info.line.1": "Nachdem dein Trezor mit deinem Computer verbunden ist, bitte die Schaltfläche 'Mit Trezor senden' drücken.", "wallet.send.trezor.confirmationDialog.info.line.2": "Eine neue Seite wird erscheinen. Bitte der Anleitung darin folgen.", "wallet.send.trezor.confirmationDialog.submit": "Senden mit Trezor", @@ -290,6 +343,7 @@ "wallet.settings.passwordLastUpdated": "Zuletzt aktualisiert {lastUpdated}", "wallet.summary.no.transaction": "Keine Transaktionen gefunden", "wallet.summary.no.transactions": "Keine letzten Transaktionen", + "wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage": "!!!You have successfully integrated with your Ledger Nano S device", "wallet.summary.page.pendingIncomingConfirmationLabel": "Eingehende anstehende Bestätigung", "wallet.summary.page.pendingOutgoingConfirmationLabel": "Ausgehende anstehende Bestätigung", "wallet.summary.page.showMoreTransactionsButtonLabel": "Zeige mehr Transaktionen", @@ -323,36 +377,5 @@ "wallet.transaction.type": "{currency} Transaktion", "wallet.transaction.type.exchange": "Börse", "wallet.transaction.type.intrawallet": "{currency} interne Wallet-Transaktion", - "wallet.transaction.type.multiparty": "{currency} mehrfach-Teilnehmer Transaktion", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Klicke hier, um mehr über die Nutzung von Yoroi mit dem Trezor zu erfahren", - "wallet.trezor.dialog.connect.button.label": "Verbinden", - "wallet.trezor.dialog.next.button.label": "Weiter", - "wallet.trezor.dialog.save.button.label": "Speichern", - "wallet.trezor.dialog.step.about.introText.line.1": "Eine Hardware-Wallet ist ein kleines USB-Gerät, welches deiner Wallet eine zusätzliche Sicherheitsstufe hinzufügt.", - "wallet.trezor.dialog.step.about.introText.line.2": "Es ist sicherer, weil dein privater Schlüssel niemals die Hardware-Wallet verlässt.", - "wallet.trezor.dialog.step.about.introText.line.3": "Damit ist dein Guthaben selbst dann geschützt, wenn dein Computer durch Malware, Phishing usw. kompromittiert worden sein sollte.", - "wallet.trezor.dialog.step.about.label": "ÜBER", - "wallet.trezor.dialog.step.about.prerequisite.1.part1": "Unterstützt nur ", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/master/ChangeLog", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T in der Version 2.1.0", - "wallet.trezor.dialog.step.about.prerequisite.1.part3": " oder später.", - "wallet.trezor.dialog.step.about.prerequisite.2": "Das Trezor-Gerät muss vorinitialisiert sein.", - "wallet.trezor.dialog.step.about.prerequisite.3": "Der Trezor-Bildschirm muss entsperrt werden.", - "wallet.trezor.dialog.step.about.prerequisite.4": "Dein Computer muss während des gesamten Prozess mit dem Internet verbunden bleiben.", - "wallet.trezor.dialog.step.about.prerequisite.5": "Das Trezor-Gerät muss während des gesamten Prozess mit deinem Computer verbunden bleiben.", - "wallet.trezor.dialog.step.about.prerequisite.header": "Voraussetzungen", - "wallet.trezor.dialog.step.connect.introText.line.1": "Nachdem du den Trezor mit dem Computer verbunden hast, drücke bitte die 'Verbinden' Schaltfläche.", - "wallet.trezor.dialog.step.connect.introText.line.2": "Ein neuer Tab wird erscheinen. Bitte den Anleitungen darin folgen.", - "wallet.trezor.dialog.step.connect.introText.line.3": "Dieser Prozess teilt den öffentlichen Schlüssel von Cardano mit Yoroi.", - "wallet.trezor.dialog.step.connect.label": "VERBINDEN", - "wallet.trezor.dialog.step.save.error.101": "Speichern fehlgeschlagen. Bitte die Internetverbindung prüfen und erneut versuchen.", - "wallet.trezor.dialog.step.save.label": "SPEICHERN", - "wallet.trezor.dialog.step.save.walletName.hint": "Wallet-Name eingeben", - "wallet.trezor.dialog.step.save.walletName.info": "Wir haben den Namen des Trezor-Geräts abgerufen. Du kannst diesen so verwenden, oder einen neuen vergeben.", - "wallet.trezor.dialog.step.save.walletName.label": "WALLET-NAME", - "wallet.trezor.dialog.title.label": "MIT DEM TREZOR HARDWARE-WALLET VERBINDEN", - "wallet.trezor.error.101": "Verbindung zu trezor.io fehlgeschlagen. Bitte die Internetverbindung prüfen und erneut versuchen.", - "wallet.trezor.error.102": "Notwendige Berechtigungen wurden vom Benutzer nicht erteilt. Bitte erneut versuchen.", - "wallet.trezor.error.103": "Abgebrochen. Bitte wiederholen." + "wallet.transaction.type.multiparty": "{currency} mehrfach-Teilnehmer Transaktion" } \ No newline at end of file diff --git a/app/i18n/locales/en-US.json b/app/i18n/locales/en-US.json index b02a933c46..81efbc988a 100644 --- a/app/i18n/locales/en-US.json +++ b/app/i18n/locales/en-US.json @@ -47,11 +47,12 @@ "daedalusTransfer.error.noTransferTxError": "There is no transaction to be sent.", "daedalusTransfer.error.transferFundsError": "Unable to transfer funds.", "daedalusTransfer.error.webSocketRestoreError": "Error while restoring blockchain addresses.", - "daedalusTransfer.errorPage.backButton.label": "Back", "daedalusTransfer.errorPage.title.label": "Unable to restore Daedalus wallet.", "daedalusTransfer.form.instructions.step0.text": "Enter the 12-word recovery phrase used to back up your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", + "daedalusTransfer.form.instructions.step0MasterKey.text": "Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.form.instructions.step0Paper.text": "Enter the 27-word recovery phrase used to back up your Daedalus Paper wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.instructions.attention.confirmation": "Transfer all funds from Daedalus wallet", + "daedalusTransfer.instructions.attention.confirmationMasterKey": "Transfer all funds from Daedalus master key", "daedalusTransfer.instructions.attention.confirmationPaper": "Transfer all funds from Daedalus Paper wallet", "daedalusTransfer.instructions.attention.text": "Yoroi and Daedalus wallets use different key derivation schemes and they each have a separate format for addresses. For this reason, you cannot continue with your Daedalus wallet and must instead create a Yoroi wallet and transfer your funds (this will change in the future). Daedalus and Yoroi wallets are fully compatible for transferring funds. If you don’t have a working copy of Daedalus, you can use your 12-word recovery phrase (or 27-words for a paper wallet) to transfer the balance from Daedalus into Yoroi.", "daedalusTransfer.summary.addressFrom.subLabel": "Daedalus wallet Addresses", @@ -64,13 +65,15 @@ "environment.apiVersion.cardano": "1.0.4", "environment.currency.ada": "ADA", "global.errors.fieldIsRequired": "This field is required.", - "global.errors.invalidMnemonic": "Invalid phrase entered, please check.", + "global.errors.invalidMasterKey": "Invalid master key entered, please check.", "global.errors.invalidRepeatPassword": "Doesn't match.", "global.errors.invalidWalletName": "Wallet name requires at least 1 and at most 40 letters.", "global.errors.invalidWalletPassword": "Invalid password", + "global.labels.back": "Back", "global.labels.cancel": "Cancel", "global.labels.change": "Change", "global.labels.create": "Create", + "global.labels.next": "Next", "global.labels.remove": "Remove", "global.labels.save": "Save", "global.language.chinese.simplified": "简体中文", @@ -133,11 +136,11 @@ "settings.support.reportProblem.link": "Support request", "settings.support.reportProblem.title": "Reporting a problem", "testnet.label.message": "WARNING: This is a testnet. ADA on the testnet has no monetary value. For more information, check out the FAQ at {faqLink}", - "transfer.form.back": "Back", "transfer.form.errors.invalidRecoveryPhrase": "Invalid recovery phrase", "transfer.form.instructions.step1.text": "It will take about 1 minute to restore your balance. In the next step, you will be presented with a transaction that will move all of your funds. Please review the details of the transaction carefully. You will need to pay a standard transaction fee on the Cardano network to make the transaction.", - "transfer.form.instructions.title.label": "Instructions", - "transfer.form.next": "Next", + "transfer.form.masterkey.input.hint": "Enter master key", + "transfer.form.masterkey.input.label": "Master key", + "transfer.form.masterkey.requirement": "Note: master keys are 192 characters and hexadecimal-encoded", "transfer.form.recovery.phrase.input.hint": "Enter recovery phrase", "transfer.form.recovery.phrase.input.label": "Recovery phrase", "transfer.form.recovery.phrase.input.noResults": "No results", @@ -153,7 +156,9 @@ "transfer.summary.transactionFee.label": "Transaction fees", "transfer.summary.transferButton.label": "Transfer Funds", "wallet.add.dialog.create.description": "Create a new wallet", + "wallet.add.dialog.createLedgerWalletNotificationMessage": "Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.createTrezorWalletNotificationMessage": "Trezor Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", + "wallet.add.dialog.ledger.description": "Connect to Ledger Hardware Wallet", "wallet.add.dialog.restore.description": "Restore wallet from backup", "wallet.add.dialog.restoreNotificationMessage": "Wallet restoration is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.title.label": "Add wallet", @@ -169,6 +174,49 @@ "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.device": "I understand that my secret keys are held securely on this device only, not on the company's servers.", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "I understand that if this application is moved to another device or deleted, my money can\n be only recovered with the backup phrase that I have written down and saved in a secure place.", "wallet.backup.recovery.phrase.entry.dialog.verification.instructions": "Tap each word in the correct order to verify your recovery phrase", + "wallet.connect.hw.dialog.connect.button.label": "Connect", + "wallet.connect.hw.dialog.save.button.label": "Save", + "wallet.connect.hw.dialog.step.about.introText.line.1": "A hardware wallet is a small USB device that adds an extra level of security to your wallet.", + "wallet.connect.hw.dialog.step.about.introText.line.2": "It is more secure because your private key never leaves the hardware wallet.", + "wallet.connect.hw.dialog.step.about.introText.line.3": "This protects your funds even if your computer is compromised due to malware, phishing attempts, etc.", + "wallet.connect.hw.dialog.step.about.label": "ABOUT", + "wallet.connect.hw.dialog.step.about.prerequisite.4": "Your computer must remain connected to the Internet throughout the process.", + "wallet.connect.hw.dialog.step.about.prerequisite.header": "Prerequisites", + "wallet.connect.hw.dialog.step.connect.introText.line.3": "This process shares the Cardano public key with Yoroi.", + "wallet.connect.hw.dialog.step.connect.label": "CONNECT", + "wallet.connect.hw.dialog.step.save.error.101": "Failed to save. Please check your Internet connection and retry.", + "wallet.connect.hw.dialog.step.save.label": "SAVE", + "wallet.connect.hw.dialog.step.save.walletName.hint": "Enter wallet name", + "wallet.connect.hw.dialog.step.save.walletName.label": "WALLET NAME", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor.text": "Click here to learn more about using Yoroi with Ledger", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part1": "Only Supports ", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link": "https://www.ledger.com/products/ledger-nano-s", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link.text": "Ledger Nano S", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part3": " model.", + "wallet.connect.ledger.dialog.step.about.prerequisite.2.part2": "Cardano ADA app must be installed on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.3": "Cardano ADA app must remain open on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.5": "The Ledger device must remain connected to your computer throughout the process.", + "wallet.connect.ledger.dialog.step.connect.introText.line.1": "After connecting your Ledger device to your computer's USB port, press the Connect button.", + "wallet.connect.ledger.dialog.step.connect.introText.line.2": "Make sure Cardano ADA app is open on the Ledger device.", + "wallet.connect.ledger.dialog.step.save.walletName.info": "Set a wallet name of your choice.", + "wallet.connect.ledger.dialog.title.label": "CONNECT TO LEDGER HARDWARE WALLET", + "wallet.connect.ledger.error.101": "Failed to connect. Please check your ledger device and retry.", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Click here to learn more about using Yoroi with Trezor", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part1": "Only Supports ", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T with version 2.0.8", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part3": " or later.", + "wallet.connect.trezor.dialog.step.about.prerequisite.2": "The Trezor device must be pre-initialized.", + "wallet.connect.trezor.dialog.step.about.prerequisite.3": "The Trezor device screen must be unlocked.", + "wallet.connect.trezor.dialog.step.about.prerequisite.5": "The Trezor device must remain connected to your computer throughout the process.", + "wallet.connect.trezor.dialog.step.connect.introText.line.1": "After connecting your Trezor device to your computer, press the Connect button.", + "wallet.connect.trezor.dialog.step.connect.introText.line.2": "A new tab will appear. Please follow the instructions in the new tab.", + "wallet.connect.trezor.dialog.step.save.walletName.info": "We have fetched the Trezor device’s name for you. You can use as it is, or assign a different name.", + "wallet.connect.trezor.dialog.title.label": "CONNECT TO TREZOR HARDWARE WALLET", + "wallet.connect.trezor.error.101": "Failed to connect trezor.io. Please check your Internet connection and retry.", + "wallet.connect.trezor.error.103": "Cancelled. Please retry.", "wallet.create.dialog.create.personal.wallet.button.label": "Create personal wallet", "wallet.create.dialog.name.label": "Wallet Name", "wallet.create.dialog.passwordFieldPlaceholder": "Password", @@ -176,11 +224,13 @@ "wallet.create.dialog.title": "Create a new wallet", "wallet.create.dialog.walletNameHint": "e.g: Shopping Wallet", "wallet.create.dialog.walletPasswordLabel": "Wallet password", + "wallet.footer.buyLedgerHardwareWallet.text": "Buy a Ledger hardware wallet.", "wallet.footer.buyTrezorHardwareWallet.text": "Buy a Trezor hardware wallet", "wallet.footer.howToConnectTrezor.text": "How to connect a Trezor", "wallet.footer.howToCreateWallet.text": "How to create a wallet", "wallet.footer.howToRestoreWallet.text": "How to restore a wallet", "wallet.footer.whatIsHardwareWallet.text": "What is a hardware wallet", + "wallet.hw.common.error.101": "Necessary permissions were not granted by the user. Please retry.", "wallet.navigation.receive": "Receive", "wallet.navigation.send": "Send", "wallet.navigation.transactions": "Transactions", @@ -194,6 +244,7 @@ "wallet.receive.page.walletReceiveInstructions": "Share this wallet address to receive payments. To protect your privacy, new addresses are generated automatically once you use them.", "wallet.recovery.phrase.show.entry.dialog.button.labelClear": "Clear", "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm": "Confirm", + "wallet.recovery.phrase.show.entry.dialog.button.labelRemoveLast": "Remove last", "wallet.redeem.choices.tab.title.forceVended": "Force vended", "wallet.redeem.choices.tab.title.paperVended": "Paper vended", "wallet.redeem.choices.tab.title.recoveryForceVended": "Recovery - force vended", @@ -249,7 +300,6 @@ "wallet.restore.dialog.walletPasswordLabel": "Wallet password", "wallet.send.confirmationDialog.addressToLabel": "To", "wallet.send.confirmationDialog.amountLabel": "Amount", - "wallet.send.confirmationDialog.back": "Back", "wallet.send.confirmationDialog.feesLabel": "Fees", "wallet.send.confirmationDialog.submit": "Send", "wallet.send.confirmationDialog.title": "Confirm transaction", @@ -264,13 +314,16 @@ "wallet.send.form.errors.invalidAddress": "Please enter a valid address.", "wallet.send.form.errors.invalidAmount": "Please enter a valid amount.", "wallet.send.form.errors.invalidTitle": "Please enter a title with at least 3 characters.", - "wallet.send.form.next": "Next", "wallet.send.form.receiver.hint": "Wallet Address", "wallet.send.form.receiver.label": "Receiver", "wallet.send.form.sendingIsDisabled": "Cannot send a new transaction while an existing one is still pending.", "wallet.send.form.title.hint": "E.g. Money for Frank", "wallet.send.form.title.label": "Title", "wallet.send.form.transactionFeeError": "Not enough Ada for fees. Try sending a smaller amount.", + "wallet.send.ledger.confirmationDialog.info.line.1": "After connecting your Ledger device to your computer's USB port, press the Send using Ledger button.", + "wallet.send.ledger.confirmationDialog.info.line.2": "Make sure the Cardano ADA app remains open on the Ledger device throughout the process.", + "wallet.send.ledger.confirmationDialog.submit": "Send using Ledger", + "wallet.send.ledger.error.101": "Signing cancelled on Ledger device. Please retry", "wallet.send.trezor.confirmationDialog.info.line.1": "After connecting your Trezor device to your computer, press the Send using Trezor button.", "wallet.send.trezor.confirmationDialog.info.line.2": "A new tab will appear. Please follow the instructions in the new tab.", "wallet.send.trezor.confirmationDialog.submit": "Send using Trezor", @@ -290,6 +343,7 @@ "wallet.settings.passwordLastUpdated": "Last updated {lastUpdated}", "wallet.summary.no.transaction": "No transactions found", "wallet.summary.no.transactions": "No recent transactions", + "wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage": "You have successfully integrated with your Ledger Nano S device", "wallet.summary.page.pendingIncomingConfirmationLabel": "Incoming pending confirmation", "wallet.summary.page.pendingOutgoingConfirmationLabel": "Outgoing pending confirmation", "wallet.summary.page.showMoreTransactionsButtonLabel": "Show more transactions", @@ -323,36 +377,5 @@ "wallet.transaction.type": "{currency} transaction", "wallet.transaction.type.exchange": "Exchange", "wallet.transaction.type.intrawallet": "{currency} intrawallet transaction", - "wallet.transaction.type.multiparty": "{currency} multiparty transaction", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Click here to learn more about using Yoroi with Trezor", - "wallet.trezor.dialog.connect.button.label": "Connect", - "wallet.trezor.dialog.next.button.label": "Next", - "wallet.trezor.dialog.save.button.label": "Save", - "wallet.trezor.dialog.step.about.introText.line.1": "A hardware wallet is a small USB device that adds an extra level of security to your wallet.", - "wallet.trezor.dialog.step.about.introText.line.2": "It is more secure because your private key never leaves the hardware wallet.", - "wallet.trezor.dialog.step.about.introText.line.3": "This protects your funds even if your computer is compromised due to malware, phishing attempts, etc.", - "wallet.trezor.dialog.step.about.label": "ABOUT", - "wallet.trezor.dialog.step.about.prerequisite.1.part1": "Only Supports ", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/master/ChangeLog", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T with version 2.1.0", - "wallet.trezor.dialog.step.about.prerequisite.1.part3": " or later.", - "wallet.trezor.dialog.step.about.prerequisite.2": "The Trezor device must be pre-initialized.", - "wallet.trezor.dialog.step.about.prerequisite.3": "The Trezor device screen must be unlocked.", - "wallet.trezor.dialog.step.about.prerequisite.4": "Your computer must remain connected to the Internet throughout the process.", - "wallet.trezor.dialog.step.about.prerequisite.5": "The Trezor device must remain connected to your computer throughout the process.", - "wallet.trezor.dialog.step.about.prerequisite.header": "Prerequisites", - "wallet.trezor.dialog.step.connect.introText.line.1": "After connecting your Trezor device to your computer, press the Connect button.", - "wallet.trezor.dialog.step.connect.introText.line.2": "A new tab will appear. Please follow the instructions in the new tab.", - "wallet.trezor.dialog.step.connect.introText.line.3": "This process shares the Cardano public key with Yoroi.", - "wallet.trezor.dialog.step.connect.label": "CONNECT", - "wallet.trezor.dialog.step.save.error.101": "Failed to save. Please check your Internet connection and retry.", - "wallet.trezor.dialog.step.save.label": "SAVE", - "wallet.trezor.dialog.step.save.walletName.hint": "Enter wallet name", - "wallet.trezor.dialog.step.save.walletName.info": "We have fetched the Trezor device’s name for you. You can use as it is, or assign a different name.", - "wallet.trezor.dialog.step.save.walletName.label": "WALLET NAME", - "wallet.trezor.dialog.title.label": "CONNECT TO TREZOR HARDWARE WALLET", - "wallet.trezor.error.101": "Failed to connect trezor.io. Please check your Internet connection and retry.", - "wallet.trezor.error.102": "Necessary permissions were not granted by the user. Please retry.", - "wallet.trezor.error.103": "Cancelled. Please retry." -} \ No newline at end of file + "wallet.transaction.type.multiparty": "{currency} multiparty transaction" +} diff --git a/app/i18n/locales/fr-FR.json b/app/i18n/locales/fr-FR.json index 50ed08c2cb..da1f3a178a 100644 --- a/app/i18n/locales/fr-FR.json +++ b/app/i18n/locales/fr-FR.json @@ -47,11 +47,12 @@ "daedalusTransfer.error.noTransferTxError": "Il n'y pas de transaction à envoyer.", "daedalusTransfer.error.transferFundsError": "Impossible d'envoyer les fonds.", "daedalusTransfer.error.webSocketRestoreError": "Erreur lors de la récupération des adresses blockchain.", - "daedalusTransfer.errorPage.backButton.label": "Retour", "daedalusTransfer.errorPage.title.label": "Impossible de réinitialiser le wallet Daedalus.", "daedalusTransfer.form.instructions.step0.text": "Veuillez entrer votre phrase de secours composée de 12 mots, utilisée pour faire une sauvegarde du wallet Daedalus et transférer tous les fonds vers Yoroi.", + "daedalusTransfer.form.instructions.step0MasterKey.text": "!!!Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.form.instructions.step0Paper.text": "Veuillez entrer votre phrase de secours composée de 27 mots utilisée pour faire une sauvegarde du Daedalus paper walle et transférer tous les fonds vers Yoroi.", "daedalusTransfer.instructions.attention.confirmation": "Transférer tous les fonds du wallet Daedalus", + "daedalusTransfer.instructions.attention.confirmationMasterKey": "!!!Transfer all funds from Daedalus master key", "daedalusTransfer.instructions.attention.confirmationPaper": "Transférer tous les fonds d'un Daedalus paper wallet", "daedalusTransfer.instructions.attention.text": "Yoroi et Daedalus utilisent des fonctions de dérivation de clé différentes et elles ont chacune un format distinct pour les adresses. Pour cette raison, vous ne peut pas continuer avec votre wallet de Daedalus et vous devez plutôt créer un wallet Yoroi et transférer vos fonds (cela va changer à l’avenir). Daedalus et Yoroi sont entièrement compatibles pour l'envoi de fonds. Si vous n’avez pas une copie de Daedalus qui fonctionne, vous pouvez utiliser votre Phrase de secours de mot-12 (ou 27-mots dans le cas d'un paper wallet) pour transférer vos fonds de Daedalus à Yoroi.", "daedalusTransfer.summary.addressFrom.subLabel": "Adresse du wallet Daedalus", @@ -64,13 +65,15 @@ "environment.apiVersion.cardano": "1.0.4", "environment.currency.ada": "ADA", "global.errors.fieldIsRequired": "Veuillez remplir ce champ.", - "global.errors.invalidMnemonic": "Phrase invalide. S'il-vous-plaît revérifier.", + "global.errors.invalidMasterKey": "!!!Invalid master key entered, please check.", "global.errors.invalidRepeatPassword": "Incorrect.", "global.errors.invalidWalletName": "La longueur du nom du wallet doit être comprise entre 1 et 40 caractères.", "global.errors.invalidWalletPassword": "Mot de passe incorrect", + "global.labels.back": "!!!Back", "global.labels.cancel": "Annuler", "global.labels.change": "Modifier", "global.labels.create": "Créer", + "global.labels.next": "!!!Next", "global.labels.remove": "Annuler", "global.labels.save": "Sauvegarder", "global.language.chinese.simplified": "简体中文", @@ -107,7 +110,7 @@ "settings.general.aboutYoroi.networkLabel": "Réseau:", "settings.general.aboutYoroi.telegram": "EMURGO Telegram", "settings.general.aboutYoroi.twitter": "Yoroi Twitter", - "settings.general.aboutYoroi.versionLabel": "Version actuelle :", + "settings.general.aboutYoroi.versionLabel": "Version actuelle :", "settings.general.aboutYoroi.website": "Site web de Yoroi", "settings.general.aboutYoroi.youtube": "EMURGO YouTube", "settings.general.languageSelect.label": "Langage", @@ -133,11 +136,11 @@ "settings.support.reportProblem.link": "Contacter le support", "settings.support.reportProblem.title": "Partager un problème", "testnet.label.message": "ATTENTION: Ceci est un testnet. L'ADA présent sur le testnet n'a aucune vraie valeur financière. Pour plus d'information, veuillez consulter notre Foire aux Questions ici {faqLink}", - "transfer.form.back": "Retour", "transfer.form.errors.invalidRecoveryPhrase": "Phrase de secours incorrecte", "transfer.form.instructions.step1.text": "La restauration du solde prend en moyenne 1 minute. Lors de l'étape suivant, vous verrez une transaction se proposer à vous afin de déplacer tous vos fonds vers Yoroi. Veuillez vérifier avec minutie les détails de la transaction. Vous devrez vous acquitter des frais de transactions standards sur le réseau Cardano afin d'effectuer la transaction.", - "transfer.form.instructions.title.label": "Mode d'emploi", - "transfer.form.next": "Suivant", + "transfer.form.masterkey.input.hint": "!!!Enter master key", + "transfer.form.masterkey.input.label": "!!!Master key", + "transfer.form.masterkey.requirement": "!!!Note: master keys are 192 characters and hexadecimal-encoded", "transfer.form.recovery.phrase.input.hint": "Entrez votre phrase de secours", "transfer.form.recovery.phrase.input.label": "Phrase de secours", "transfer.form.recovery.phrase.input.noResults": "Aucun résultat", @@ -153,7 +156,9 @@ "transfer.summary.transactionFee.label": "Frais de transaction", "transfer.summary.transferButton.label": "Transférer des fonds", "wallet.add.dialog.create.description": "Créer un nouveau wallet", + "wallet.add.dialog.createLedgerWalletNotificationMessage": "!!!Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.createTrezorWalletNotificationMessage": "Trezor Connect est en cours. Vous ne pouvez pas restaurer ou importer d'autres wallet lors de cette procédure.", + "wallet.add.dialog.ledger.description": "!!!Connect to Ledger", "wallet.add.dialog.restore.description": "Restaurer son wallet à partir d'une sauvegarde", "wallet.add.dialog.restoreNotificationMessage": "La restauration du wallet est en cours. Vous ne pouvez pas restaurer ou importer d'autres wallet lors de cette procédure.", "wallet.add.dialog.title.label": "Ajouter un wallet", @@ -169,6 +174,49 @@ "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.device": "J'ai compris que mes clefs secrètes sont uniquement gardées sur cet appareil, et non pas sur les serveurs de l'entreprise.", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "J'ai compris que si l'application est déplacée vers un autre appareil our bien effacée, mes fonds ne peuvent être récupérés qu'avec la phrase de secours que j'ai noté de manière manuscrite et mis de coté.", "wallet.backup.recovery.phrase.entry.dialog.verification.instructions": "Veuillez choisir chaque mot dans l'ordre pour vérifier votre phrase de secours", + "wallet.connect.hw.dialog.connect.button.label": "Connection", + "wallet.connect.hw.dialog.save.button.label": "Enregistrer", + "wallet.connect.hw.dialog.step.about.introText.line.1": "Un hardware wallet est un petit appareil USB qui ajoute un niveau de sécurité supplémentaire à votre wallet.", + "wallet.connect.hw.dialog.step.about.introText.line.2": "C'est plus sécurisé car la clef privée ne quitte jamais le hardware wallet.", + "wallet.connect.hw.dialog.step.about.introText.line.3": "Cela protège vos fonds, même si votre ordinateur est compromis en raison des tentatives de phishing, de malware, etc.", + "wallet.connect.hw.dialog.step.about.label": "A propos", + "wallet.connect.hw.dialog.step.about.prerequisite.4": "Votre ordinateur doit rester connecté à Internet durant tout le processus.", + "wallet.connect.hw.dialog.step.about.prerequisite.header": "Prérequis", + "wallet.connect.hw.dialog.step.connect.introText.line.3": "Ce processus partage la clé publique de Cardano avec Yoroi.", + "wallet.connect.hw.dialog.step.connect.label": "Connecter", + "wallet.connect.hw.dialog.step.save.error.101": "Erreur durant la sauvegarde. Vérifiez votre connection internet et réessayez.", + "wallet.connect.hw.dialog.step.save.label": "Sauvegarder", + "wallet.connect.hw.dialog.step.save.walletName.hint": "Veuillez entrer le nom du wallet", + "wallet.connect.hw.dialog.step.save.walletName.label": "Nom du wallet", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor": "!!!https://yoroi-wallet.com/", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor.text": "!!!Click here to know more about how to use Yoroi with Trezor.", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part1": "!!!Only Supports", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link": "!!!https://www.ledger.com/products/ledger-nano-s", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link.text": "!!!Ledger Nano S", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part3": "!!! model.", + "wallet.connect.ledger.dialog.step.about.prerequisite.2.part2": "!!!Cardano ADA app must be installed on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.3": "!!!The Trezor device screen must be unlocked.", + "wallet.connect.ledger.dialog.step.about.prerequisite.5": "!!!Trezor device must remain connected to the computer throughout the process", + "wallet.connect.ledger.dialog.step.connect.introText.line.1": "!!!After connecting your Trezor device to the computer press the Connect button.", + "wallet.connect.ledger.dialog.step.connect.introText.line.2": "!!!A new tab will appear, please follow the instructions in the new tab.", + "wallet.connect.ledger.dialog.step.save.walletName.info": "!!!We have fetched Trezor device’s name for you; you can use as it is or assign a different name.", + "wallet.connect.ledger.dialog.title.label": "!!!Connect to Ledger Hardware Wallet", + "wallet.connect.ledger.error.101": "!!!Failed to connect. Please check your ledger device and retry.", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Cliquer ici pour apprendre à se servir de Trezor et Yoroi", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part1": "Uniquement supporté par ", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trevor Model T Version 2.0.8", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part3": " ou plus récent.", + "wallet.connect.trezor.dialog.step.about.prerequisite.2": "Le Trezor doit être pré-initialisé.", + "wallet.connect.trezor.dialog.step.about.prerequisite.3": "Le Trezor doit être déverrouillé.", + "wallet.connect.trezor.dialog.step.about.prerequisite.5": "Le Trezor doit rester connecté à votre ordinateur durant tout le processus.", + "wallet.connect.trezor.dialog.step.connect.introText.line.1": "Après avoir connecté votre Trezor à votre ordinateur, appuyer sur \"Connect\" sur le Trezor.", + "wallet.connect.trezor.dialog.step.connect.introText.line.2": "Une nouvelle fenêtre va s'ouvrir. Veuillez sur les instructions dans cette nouvelle fenêtre.", + "wallet.connect.trezor.dialog.step.save.walletName.info": "We have fetched the Trezor device’s name for you. You can use as it is, or assign a different name.", + "wallet.connect.trezor.dialog.title.label": "Se connecter au Trezor Hardware Wallet", + "wallet.connect.trezor.error.101": "Erreur durant la connection à trezor.io. Vérifiez votre connection internet et réessayez.", + "wallet.connect.trezor.error.103": "Annulation. Veuillez réessayer.", "wallet.create.dialog.create.personal.wallet.button.label": "Créer un wallet", "wallet.create.dialog.name.label": "Nom du wallet", "wallet.create.dialog.passwordFieldPlaceholder": "Mot de passe", @@ -176,11 +224,13 @@ "wallet.create.dialog.title": "Créer un nouveau wallet", "wallet.create.dialog.walletNameHint": "ex: Wallet de shopping", "wallet.create.dialog.walletPasswordLabel": "Mot de passe du wallet", + "wallet.footer.buyLedgerHardwareWallet.text": "!!!Buy a Ledger hardware wallet.", "wallet.footer.buyTrezorHardwareWallet.text": "Acheter un hardware wallet Trezor", "wallet.footer.howToConnectTrezor.text": "Comment connecter Trezor", "wallet.footer.howToCreateWallet.text": "Comment créer un wallet", "wallet.footer.howToRestoreWallet.text": "Comment récupérer un wallet", "wallet.footer.whatIsHardwareWallet.text": "Qu'est ce qu'un hardware wallet", + "wallet.hw.common.error.101": "Les autorisations nécessaires n'ont pas été fournies par l'utilisateur. Veuillez réessayer.", "wallet.navigation.receive": "Recevoir", "wallet.navigation.send": "Envoyer", "wallet.navigation.transactions": "Transactions", @@ -194,6 +244,7 @@ "wallet.receive.page.walletReceiveInstructions": "Partagez cette adresse du wallet pour recevoir des paiements. Afin d'assurer votre sécurité, de nouvelles adresses sont générées à chaque utilisation.", "wallet.recovery.phrase.show.entry.dialog.button.labelClear": "Effacer", "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm": "Confirmer", + "wallet.recovery.phrase.show.entry.dialog.button.labelRemoveLast": "!!!Remove last", "wallet.redeem.choices.tab.title.forceVended": "do not translate", "wallet.redeem.choices.tab.title.paperVended": "do not translate", "wallet.redeem.choices.tab.title.recoveryForceVended": "do not translate", @@ -249,7 +300,6 @@ "wallet.restore.dialog.walletPasswordLabel": "Mot de passe du wallet", "wallet.send.confirmationDialog.addressToLabel": "A", "wallet.send.confirmationDialog.amountLabel": "Montant", - "wallet.send.confirmationDialog.back": "Retour", "wallet.send.confirmationDialog.feesLabel": "Frais", "wallet.send.confirmationDialog.submit": "Envoyer", "wallet.send.confirmationDialog.title": "Confirmer la transaction", @@ -264,13 +314,16 @@ "wallet.send.form.errors.invalidAddress": "Veuillez entrer une adresse valide.", "wallet.send.form.errors.invalidAmount": "Veuillez entrer un montant valide.", "wallet.send.form.errors.invalidTitle": "Veuillez entrer un titre d'au moins 3 caractères.", - "wallet.send.form.next": "Suivant", "wallet.send.form.receiver.hint": "Adresse du wallet", "wallet.send.form.receiver.label": "Récipient", "wallet.send.form.sendingIsDisabled": "Impossible d'envoyer une nouvelle transaction lorsque la précédente n'est pas terminée.", "wallet.send.form.title.hint": "Ex: Envoi à Marc", "wallet.send.form.title.label": "Titre", "wallet.send.form.transactionFeeError": "Fonds Ada insuffisants. Essayez un montant plus petit.", + "wallet.send.ledger.confirmationDialog.info.line.1": "!!!After connecting your Trezor device to your computer, press the Send using Trezor button.", + "wallet.send.ledger.confirmationDialog.info.line.2": "!!!A new tab will appear. Please follow the instructions in the new tab.", + "wallet.send.ledger.confirmationDialog.submit": "!!!Send using Trezor", + "wallet.send.ledger.error.101": "!!!Signing cancelled on Ledger device. Please retry.", "wallet.send.trezor.confirmationDialog.info.line.1": "Après avoir connecté votre Trezor à votre ordinateur, appuyer sur le bouton envoi du Trezor.", "wallet.send.trezor.confirmationDialog.info.line.2": "Une nouvelle fenêtre va s'ouvrir. Veuillez sur les instructions dans cette nouvelle fenêtre.", "wallet.send.trezor.confirmationDialog.submit": "Envoyer au travers de Trezor", @@ -290,6 +343,7 @@ "wallet.settings.passwordLastUpdated": "Dernière mise à jour {lastUpdated}", "wallet.summary.no.transaction": "Aucune transaction", "wallet.summary.no.transactions": "Pas de transaction récente", + "wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage": "!!!You have successfully integrated with your Ledger Nano S device", "wallet.summary.page.pendingIncomingConfirmationLabel": "En attente de confirmation", "wallet.summary.page.pendingOutgoingConfirmationLabel": "En attente de confirmation", "wallet.summary.page.showMoreTransactionsButtonLabel": "Voir plus de transaction", @@ -323,36 +377,5 @@ "wallet.transaction.type": "transaction en {currency}", "wallet.transaction.type.exchange": "Echange", "wallet.transaction.type.intrawallet": "Transaction entre wallets {currency}", - "wallet.transaction.type.multiparty": "Transaction multipartis {currency}", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Cliquer ici pour apprendre à se servir de Trezor et Yoroi", - "wallet.trezor.dialog.connect.button.label": "Connection", - "wallet.trezor.dialog.next.button.label": "Suivant", - "wallet.trezor.dialog.save.button.label": "Enregistrer", - "wallet.trezor.dialog.step.about.introText.line.1": "Un hardware wallet est un petit appareil USB qui ajoute un niveau de sécurité supplémentaire à votre wallet.", - "wallet.trezor.dialog.step.about.introText.line.2": "C'est plus sécurisé car la clef privée ne quitte jamais le hardware wallet.", - "wallet.trezor.dialog.step.about.introText.line.3": "Cela protège vos fonds, même si votre ordinateur est compromis en raison des tentatives de phishing, de malware, etc.", - "wallet.trezor.dialog.step.about.label": "A propos", - "wallet.trezor.dialog.step.about.prerequisite.1.part1": "Uniquement supporté par ", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/master/ChangeLog", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trevor Model T Version 2.1.0", - "wallet.trezor.dialog.step.about.prerequisite.1.part3": " ou plus récent.", - "wallet.trezor.dialog.step.about.prerequisite.2": "Le Trezor doit être pré-initialisé.", - "wallet.trezor.dialog.step.about.prerequisite.3": "Le Trezor doit être déverrouillé.", - "wallet.trezor.dialog.step.about.prerequisite.4": "Votre ordinateur doit rester connecté à Internet durant tout le processus.", - "wallet.trezor.dialog.step.about.prerequisite.5": "Le Trezor doit rester connecté à votre ordinateur durant tout le processus.", - "wallet.trezor.dialog.step.about.prerequisite.header": "Prérequis", - "wallet.trezor.dialog.step.connect.introText.line.1": "Après avoir connecté votre Trezor à votre ordinateur, appuyer sur \"Connect\" sur le Trezor.", - "wallet.trezor.dialog.step.connect.introText.line.2": "Une nouvelle fenêtre va s'ouvrir. Veuillez sur les instructions dans cette nouvelle fenêtre.", - "wallet.trezor.dialog.step.connect.introText.line.3": "Ce processus partage la clé publique de Cardano avec Yoroi.", - "wallet.trezor.dialog.step.connect.label": "Connecter", - "wallet.trezor.dialog.step.save.error.101": "Erreur durant la sauvegarde. Vérifiez votre connection internet et réessayez.", - "wallet.trezor.dialog.step.save.label": "Sauvegarder", - "wallet.trezor.dialog.step.save.walletName.hint": "Veuillez entrer le nom du wallet", - "wallet.trezor.dialog.step.save.walletName.info": "Nous avons récupéré le nom du Trezor. Vous pouvez utiliser ce nom, ou bien en choisir un autre.", - "wallet.trezor.dialog.step.save.walletName.label": "Nom du wallet", - "wallet.trezor.dialog.title.label": "Se connecter au Trezor Hardware Wallet", - "wallet.trezor.error.101": "Erreur durant la connection à trezor.io. Vérifiez votre connection internet et réessayez.", - "wallet.trezor.error.102": "Les autorisations nécessaires n'ont pas été fournies par l'utilisateur. Veuillez réessayer.", - "wallet.trezor.error.103": "Annulation. Veuillez réessayer." + "wallet.transaction.type.multiparty": "Transaction multipartis {currency}" } \ No newline at end of file diff --git a/app/i18n/locales/ja-JP.json b/app/i18n/locales/ja-JP.json index fef7e57019..ee007a3e2e 100644 --- a/app/i18n/locales/ja-JP.json +++ b/app/i18n/locales/ja-JP.json @@ -47,11 +47,12 @@ "daedalusTransfer.error.noTransferTxError": "送信する取引はありません。", "daedalusTransfer.error.transferFundsError": "送金できません。", "daedalusTransfer.error.webSocketRestoreError": "ブロックチェーンアドレスの復元中にエラーが発生しました。", - "daedalusTransfer.errorPage.backButton.label": "戻る", "daedalusTransfer.errorPage.title.label": "ダイダロスウォレットを復元できません。", "daedalusTransfer.form.instructions.step0.text": "ダイダロスの12単語の復元フレーズ入力してください。残高がヨロイウォレットに転送されます。", + "daedalusTransfer.form.instructions.step0MasterKey.text": "!!!Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.form.instructions.step0Paper.text": "ダイダロスペーパウォレットの27単語の復元フレーズ入力してください。残高がヨロイウォレットに転送されます。", "daedalusTransfer.instructions.attention.confirmation": "ダイダロスウォレットからすべての資金を送金する", + "daedalusTransfer.instructions.attention.confirmationMasterKey": "!!!Transfer all funds from Daedalus master key", "daedalusTransfer.instructions.attention.confirmationPaper": "ダイダロスペーパーウォレットから復元する", "daedalusTransfer.instructions.attention.text": "ヨロイウォレットとダイダロスウォレットは、それぞれ異なる鍵導出スキームおよび異なるアドレス形式を使用しています。そのため、ダイダロスウォレットを引き続き使用することはできず、ヨロイウォレットを作成し、送金する必要があります。(今後は1つのアドレスで両方のウォレットが使用できる予定です。)現時点では、ダイダロスウォレットの資金を使用するためにはヨロイウォレットに送金する必要があります。ダイダロスウォレットとヨロイウォレットは、送金において互換性があります。ダイダロスを立ち上げることができない場合、12単語(またはペーパーウォレットの27単語)から成るリカバリフレーズを使用してダイダロスからヨロイウォレットに残高を送金する事ができます。", "daedalusTransfer.summary.addressFrom.subLabel": "ダイダロスウォレットのアドレス", @@ -64,13 +65,15 @@ "environment.apiVersion.cardano": "1.0.4", "environment.currency.ada": "ADA", "global.errors.fieldIsRequired": "この項目は必須です。", - "global.errors.invalidMnemonic": "入力されたフレーズは無効です。確認してください。", + "global.errors.invalidMasterKey": "!!!Invalid master key entered, please check.", "global.errors.invalidRepeatPassword": "一致しません。", "global.errors.invalidWalletName": "1文字以上40文字以下のウォレット名を入力してください。", "global.errors.invalidWalletPassword": "無効なパスワード", + "global.labels.back": "!!!Back", "global.labels.cancel": "キャンセル", "global.labels.change": "変更", "global.labels.create": "作成", + "global.labels.next": "!!!Next", "global.labels.remove": "削除", "global.labels.save": "保存", "global.language.chinese.simplified": "简体中文", @@ -133,11 +136,11 @@ "settings.support.reportProblem.link": "サポート依頼", "settings.support.reportProblem.title": "問題を報告する", "testnet.label.message": "注意:テストネット上のADAには貨幣価値がありません。詳細は、FAQ({faqLink})をご参照ください。", - "transfer.form.back": "戻る", "transfer.form.errors.invalidRecoveryPhrase": "復元フレーズが無効です", "transfer.form.instructions.step1.text": "残高が復元されるまで1分程度かかります。復元後、ダイダロスウォレットからヨロイウォレットへの送金取引が表示されます。トランザクションの詳細を慎重に確認してください。 取引を行うには、Cardanoネットワークの標準トランザクション手数料を支払う必要があります。", - "transfer.form.instructions.title.label": "指示説明", - "transfer.form.next": "次へ", + "transfer.form.masterkey.input.hint": "!!!Enter master key", + "transfer.form.masterkey.input.label": "!!!Master key", + "transfer.form.masterkey.requirement": "!!!Note: master keys are 192 characters and hexadecimal-encoded", "transfer.form.recovery.phrase.input.hint": "復元フレーズを入力してください", "transfer.form.recovery.phrase.input.label": "復元フレーズ", "transfer.form.recovery.phrase.input.noResults": "結果がありません", @@ -153,7 +156,9 @@ "transfer.summary.transactionFee.label": "トランザクション手数料", "transfer.summary.transferButton.label": "送金", "wallet.add.dialog.create.description": "新しいウォレットを作成する", + "wallet.add.dialog.createLedgerWalletNotificationMessage": "!!!Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.createTrezorWalletNotificationMessage": "Trezorに接続中です。完了するまで、新しいウォレットを復元またはインポートすることはできません。", + "wallet.add.dialog.ledger.description": "!!!Connect to Ledger", "wallet.add.dialog.restore.description": "ウォレットを復元する", "wallet.add.dialog.restoreNotificationMessage": "ウォレットの復元が進行中です。完了するまで、新しいウォレットを復元またはインポートすることはできません。", "wallet.add.dialog.title.label": "ウォレットの追加", @@ -169,6 +174,49 @@ "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.device": "秘密鍵は、お客様のデバイスにのみ保管され、弊社のサーバーには保管されませんことを理解しています。", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "このアプリケーションを別のデバイスに移動するか削除した場合、\r\n 安全な場所に書き留められたバックアップフレーズでのみ資金を復元できることを理解しています。", "wallet.backup.recovery.phrase.entry.dialog.verification.instructions": "各単語を正しい順序でタップしてリカバリフレーズを確認する", + "wallet.connect.hw.dialog.connect.button.label": "接続", + "wallet.connect.hw.dialog.save.button.label": "保存", + "wallet.connect.hw.dialog.step.about.introText.line.1": "ハードウェアウォレットは小さなUSB機器で、ウォレットのセキュリティを高めます。", + "wallet.connect.hw.dialog.step.about.introText.line.2": "秘密鍵がハードウェアウォレットに残らないので、さらに高い安全性を担保します。", + "wallet.connect.hw.dialog.step.about.introText.line.3": "お使いのコンピューターがマルウェアやフィッシング詐欺などにより損なわれたとしても、資金は守られます。", + "wallet.connect.hw.dialog.step.about.label": "概要", + "wallet.connect.hw.dialog.step.about.prerequisite.4": "プロセスの実行中は、コンピューターをインターネットに接続したままにしてください。", + "wallet.connect.hw.dialog.step.about.prerequisite.header": "前提条件", + "wallet.connect.hw.dialog.step.connect.introText.line.3": "このプロセスにより、CARDANOの秘密鍵がヨロイに共有されます。", + "wallet.connect.hw.dialog.step.connect.label": "接続", + "wallet.connect.hw.dialog.step.save.error.101": "保存に失敗しました。インターネット接続を確認して再試行してください。", + "wallet.connect.hw.dialog.step.save.label": "保存", + "wallet.connect.hw.dialog.step.save.walletName.hint": "ウォレット名を入力", + "wallet.connect.hw.dialog.step.save.walletName.label": "ウォレット名", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor": "!!!https://yoroi-wallet.com/", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor.text": "!!!Click here to know more about how to use Yoroi with Trezor.", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part1": "!!!Only Supports", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link": "!!!https://www.ledger.com/products/ledger-nano-s", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link.text": "!!!Ledger Nano S", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part3": "!!! model.", + "wallet.connect.ledger.dialog.step.about.prerequisite.2.part2": "!!!Cardano ADA app must be installed on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.3": "!!!The Trezor device screen must be unlocked.", + "wallet.connect.ledger.dialog.step.about.prerequisite.5": "!!!Trezor device must remain connected to the computer throughout the process", + "wallet.connect.ledger.dialog.step.connect.introText.line.1": "!!!After connecting your Trezor device to the computer press the Connect button.", + "wallet.connect.ledger.dialog.step.connect.introText.line.2": "!!!A new tab will appear, please follow the instructions in the new tab.", + "wallet.connect.ledger.dialog.step.save.walletName.info": "!!!We have fetched Trezor device’s name for you; you can use as it is or assign a different name.", + "wallet.connect.ledger.dialog.title.label": "!!!Connect to Ledger Hardware Wallet", + "wallet.connect.ledger.error.101": "!!!Failed to connect. Please check your ledger device and retry.", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Trezorを使ったヨロイの使用方法はこちら", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part1": " ", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T のバージョン2.0.8", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part3": "以上対応。", + "wallet.connect.trezor.dialog.step.about.prerequisite.2": "あらかじめTrezorの初期設定を行ってください。", + "wallet.connect.trezor.dialog.step.about.prerequisite.3": "Trezorデバイスの画面をロック解除してください。", + "wallet.connect.trezor.dialog.step.about.prerequisite.5": "プロセスの実行中は、Trezorデバイスをインターネットに接続したままにしてください。", + "wallet.connect.trezor.dialog.step.connect.introText.line.1": "Trezorデバイスをお使いのコンピューターに接続してから「接続」ボタンを押してください。", + "wallet.connect.trezor.dialog.step.connect.introText.line.2": "新しいタブが開かれます。表示される指示に従ってください。", + "wallet.connect.trezor.dialog.step.save.walletName.info": "Trezorデバイス名を読み出しました。このまま使用するか、任意の名前を入力してください。", + "wallet.connect.trezor.dialog.title.label": "Trezorハードウェアウォレットに接続", + "wallet.connect.trezor.error.101": "Trezor.ioへの接続に失敗しました。インターネット接続を確認して再試行してください。", + "wallet.connect.trezor.error.103": "キャンセルされました。再試行してください。", "wallet.create.dialog.create.personal.wallet.button.label": "パーソナルウォレットを作成", "wallet.create.dialog.name.label": "ウォレット名", "wallet.create.dialog.passwordFieldPlaceholder": "パスワード", @@ -176,11 +224,13 @@ "wallet.create.dialog.title": "新しいウォレットを作成する", "wallet.create.dialog.walletNameHint": "例:ショッピングウォレット", "wallet.create.dialog.walletPasswordLabel": "ウォレットのパスワード", + "wallet.footer.buyLedgerHardwareWallet.text": "!!!Buy a Ledger hardware wallet.", "wallet.footer.buyTrezorHardwareWallet.text": "Trezorハードウェアウォレットを購入", "wallet.footer.howToConnectTrezor.text": "Trezorと接続する方法", "wallet.footer.howToCreateWallet.text": "ウォレットを作成する方法", "wallet.footer.howToRestoreWallet.text": "ウォレットを復元する方法", "wallet.footer.whatIsHardwareWallet.text": "ハードウェアウォレットとは", + "wallet.hw.common.error.101": "アクセス許可が得られませんでした。再試行してください。", "wallet.navigation.receive": "受信", "wallet.navigation.send": "送信", "wallet.navigation.transactions": "取引", @@ -194,6 +244,7 @@ "wallet.receive.page.walletReceiveInstructions": "上記アドレスを送信元に共有し、ADAの送金を依頼してください。セキュリティーの観点から、一度使用したアドレスを再度使用することはできません。新たなアドレスが自動的に生成されます。", "wallet.recovery.phrase.show.entry.dialog.button.labelClear": "クリア", "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm": "確認", + "wallet.recovery.phrase.show.entry.dialog.button.labelRemoveLast": "!!!Remove last", "wallet.redeem.choices.tab.title.forceVended": "強制ヴェンド", "wallet.redeem.choices.tab.title.paperVended": "紙ヴェンド", "wallet.redeem.choices.tab.title.recoveryForceVended": "リカバリー:強制ヴェンド", @@ -249,7 +300,6 @@ "wallet.restore.dialog.walletPasswordLabel": "ウォレットのパスワード", "wallet.send.confirmationDialog.addressToLabel": "受信者", "wallet.send.confirmationDialog.amountLabel": "金額", - "wallet.send.confirmationDialog.back": "戻る", "wallet.send.confirmationDialog.feesLabel": "手数料", "wallet.send.confirmationDialog.submit": "送信", "wallet.send.confirmationDialog.title": "取引を確認する", @@ -264,13 +314,16 @@ "wallet.send.form.errors.invalidAddress": "有効なアドレスを入力してください。", "wallet.send.form.errors.invalidAmount": "有効な金額を入力してください。", "wallet.send.form.errors.invalidTitle": "3文字以上のタイトルを入力してください。", - "wallet.send.form.next": "次へ", "wallet.send.form.receiver.hint": "ウォレットのアドレス", "wallet.send.form.receiver.label": "受信者", "wallet.send.form.sendingIsDisabled": "取引が完了するまで別の取引を行うことはできません。", "wallet.send.form.title.hint": "例:フランク宛の資金", "wallet.send.form.title.label": "タイトル", "wallet.send.form.transactionFeeError": "手数料に対する ADA が不足しています。送金額を減らして再度実行してください。", + "wallet.send.ledger.confirmationDialog.info.line.1": "!!!After connecting your Trezor device to your computer, press the Send using Trezor button.", + "wallet.send.ledger.confirmationDialog.info.line.2": "!!!A new tab will appear. Please follow the instructions in the new tab.", + "wallet.send.ledger.confirmationDialog.submit": "!!!Send using Trezor", + "wallet.send.ledger.error.101": "!!!Signing cancelled on Ledger device. Please retry.", "wallet.send.trezor.confirmationDialog.info.line.1": "Trezorデバイスをお使いのコンピューターに接続してから「Trezorを使って送信」ボタンを押してください。", "wallet.send.trezor.confirmationDialog.info.line.2": "新しいタブが開かれます。表示される指示に従ってください。", "wallet.send.trezor.confirmationDialog.submit": "Trezorを使って送信", @@ -290,6 +343,7 @@ "wallet.settings.passwordLastUpdated": "最終更新日 {lastUpdated}", "wallet.summary.no.transaction": "取引が見つかりません", "wallet.summary.no.transactions": "最近の取引はありません", + "wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage": "!!!You have successfully integrated with your Ledger Nano S device", "wallet.summary.page.pendingIncomingConfirmationLabel": "着信保留確認", "wallet.summary.page.pendingOutgoingConfirmationLabel": "送信保留確認", "wallet.summary.page.showMoreTransactionsButtonLabel": "さらに表示", @@ -334,8 +388,8 @@ "wallet.trezor.dialog.step.about.introText.line.3": "お使いのコンピューターがマルウェアやフィッシング詐欺などにより損なわれたとしても、資金は守られます。", "wallet.trezor.dialog.step.about.label": "概要", "wallet.trezor.dialog.step.about.prerequisite.1.part1": " ", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/master/ChangeLog", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T のバージョン2.1.0", + "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T のバージョン2.0.8", "wallet.trezor.dialog.step.about.prerequisite.1.part3": "以上対応。", "wallet.trezor.dialog.step.about.prerequisite.2": "あらかじめTrezorの初期設定を行ってください。", "wallet.trezor.dialog.step.about.prerequisite.3": "Trezorデバイスの画面をロック解除してください。", diff --git a/app/i18n/locales/ko-KR.json b/app/i18n/locales/ko-KR.json index 0fd2f54369..4c10dd190e 100644 --- a/app/i18n/locales/ko-KR.json +++ b/app/i18n/locales/ko-KR.json @@ -47,11 +47,12 @@ "daedalusTransfer.error.noTransferTxError": "전송할 거래가 없습니다.", "daedalusTransfer.error.transferFundsError": "자금을 이체할 수 없습니다.", "daedalusTransfer.error.webSocketRestoreError": "블록체인 주소를 복원하는 중 오류가 발생했습니다.", - "daedalusTransfer.errorPage.backButton.label": "뒤로", "daedalusTransfer.errorPage.title.label": "Daedalus 지갑을 복원할 수 없습니다.", "daedalusTransfer.form.instructions.step0.text": "Daedalus에서 Yoroi에 모든 자금을 보내고 잔액을 복원하기 위해서 당신의 Daedalus지갑을 백업하기 위해서 사용했던 12단어의 복구 구절을 입력하시오.", + "daedalusTransfer.form.instructions.step0MasterKey.text": "!!!Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.form.instructions.step0Paper.text": "다이달로스 페이퍼 월렛의 잔액를 백업으로 복원하고 요로이로 모든 자금을 전송하기 위해 사용한 27단어로 이루어진 복구구절을 입력해 주십시오.", "daedalusTransfer.instructions.attention.confirmation": "Daedalus 지갑으로 부터 전액 송금하기", + "daedalusTransfer.instructions.attention.confirmationMasterKey": "!!!Transfer all funds from Daedalus master key", "daedalusTransfer.instructions.attention.confirmationPaper": "다이달로스 페이퍼 월렛의 모든 자금을 송금", "daedalusTransfer.instructions.attention.text": "요로이와 다이달로스 지갑은 서로 다른 키 파생 방식이 사용되며, 각각 다른 포맷의 주소를 가지고 있습니다. 이러한 이유로, 사용자는 다이달로스를 계속해서 사용할 수 없으며, 대신에 요로이 지갑을 새로 생성하고 자금을 전송해야 합니다 (추후 변경 될 예정입니다). 다이달로스와 요로이는 자금을 전송하는데 있어서 상호 호환됩니다. 다이달로스의 복사본이 없다면, 12개의 복구구절을 이용하거나 또는 페이퍼 월렛의 27개 단어를 사용하여 요로이로 자금을 이체할 수 있습니다.", "daedalusTransfer.summary.addressFrom.subLabel": "Daedalus 지갑 주소", @@ -64,13 +65,15 @@ "environment.apiVersion.cardano": "1.0.4", "environment.currency.ada": "ADA", "global.errors.fieldIsRequired": "이 필드는 필수입니다.", - "global.errors.invalidMnemonic": "유효하지 않은 구절입니다. 다시 확인해 주십시오.", + "global.errors.invalidMasterKey": "!!!Invalid master key entered, please check.", "global.errors.invalidRepeatPassword": "일치하지 않습니다.", "global.errors.invalidWalletName": "지갑의 이름은 1~40자 사이여야 합니다.", "global.errors.invalidWalletPassword": "잘못된 암호", + "global.labels.back": "!!!Back", "global.labels.cancel": "취소", "global.labels.change": "변경", "global.labels.create": "만들기", + "global.labels.next": "!!!Next", "global.labels.remove": "제거", "global.labels.save": "저장", "global.language.chinese.simplified": "简体中文", @@ -133,11 +136,11 @@ "settings.support.reportProblem.link": "지원 요청", "settings.support.reportProblem.title": "문제 보고 중", "testnet.label.message": "주의: 이것은 테스트넷입니다. 테스트넷에서의 ADA는 화폐의 가치를 가지지 않습니다. 자세한 정보는 FAQ의 {faqLink} 를 확인해 주세요.", - "transfer.form.back": "뒤로", "transfer.form.errors.invalidRecoveryPhrase": "잘못된 복구 구절", "transfer.form.instructions.step1.text": "잔고가 복원될 때까지 1분 정도 소요됩니다. 복원 후, 다이달로스 지갑에서 요로이 지갑으로의 송금 거래가 표시됩니다. 거래의 상세내역을 신중히 확인해 주십시오. 거래를 하기 위해서는 Cardano 네트워크 표준의 거래 수수료 지불이 필요합니다.", - "transfer.form.instructions.title.label": "안내지침", - "transfer.form.next": "다음", + "transfer.form.masterkey.input.hint": "!!!Enter master key", + "transfer.form.masterkey.input.label": "!!!Master key", + "transfer.form.masterkey.requirement": "!!!Note: master keys are 192 characters and hexadecimal-encoded", "transfer.form.recovery.phrase.input.hint": "복구 구절을 입력해 주십시오.", "transfer.form.recovery.phrase.input.label": "복구 구절", "transfer.form.recovery.phrase.input.noResults": "결과가 없습니다.", @@ -153,7 +156,9 @@ "transfer.summary.transactionFee.label": "거래 수수료", "transfer.summary.transferButton.label": "자금 이체", "wallet.add.dialog.create.description": "새 지갑 만들기", + "wallet.add.dialog.createLedgerWalletNotificationMessage": "!!!Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.createTrezorWalletNotificationMessage": "TREZOR에 연결중입니다. 완료될 때까지 새로운 지갑을 복원하거나 가져올 수 없습니다.", + "wallet.add.dialog.ledger.description": "!!!Connect to Ledger", "wallet.add.dialog.restore.description": "백업에서 지갑 복원", "wallet.add.dialog.restoreNotificationMessage": "현재 지갑을 복원하고 있습니다. 완료될 때까지 새 지갑을 복원하거나 가져올 수 없습니다.", "wallet.add.dialog.title.label": "지갑 추가", @@ -169,6 +174,49 @@ "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.device": "본인은 비밀키가 자신의 장치에만 보관되며, 당사의 서버에는 보관되지 않음을 이해합니다.", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "본인은 이 애플리케이션이 다른 장치로 이동하거나 삭제될 경우, 안전한 곳에 보관하고 있는 백업 구절을 통해서만 돈을 복구할 수 있다는 것을 알고 있습니다.", "wallet.backup.recovery.phrase.entry.dialog.verification.instructions": "복구 구절을 인증하려면 각 단어를 정확한 순서대로 탭하십시오.", + "wallet.connect.hw.dialog.connect.button.label": "연결", + "wallet.connect.hw.dialog.save.button.label": "저장", + "wallet.connect.hw.dialog.step.about.introText.line.1": "하드웨어 지갑은 작은 USB 디바이스로, 지갑의 보안성을 높입니다.", + "wallet.connect.hw.dialog.step.about.introText.line.2": "하드웨어 지갑에는 비밀키가 남지 않으므로, 더 높은 안전성을 보장합니다.", + "wallet.connect.hw.dialog.step.about.introText.line.3": "따라서 컴퓨터가 피싱 시도, 말웨어 등으로 인해 손상되더라도 자금을 안전하게 보호합니다.", + "wallet.connect.hw.dialog.step.about.label": "개요", + "wallet.connect.hw.dialog.step.about.prerequisite.4": "진행과정 동안 당신의 컴퓨터는 인터넷에 연결되어 있어야 합니다.", + "wallet.connect.hw.dialog.step.about.prerequisite.header": "전제조건", + "wallet.connect.hw.dialog.step.connect.introText.line.3": "Cardano 공용키를 요로이와 공유하는 과정입니다.", + "wallet.connect.hw.dialog.step.connect.label": "연결", + "wallet.connect.hw.dialog.step.save.error.101": "저장에 실패했습니다. 인터넷 연결상태를 확인하시고 다시 시도해 주십시오.", + "wallet.connect.hw.dialog.step.save.label": "저장", + "wallet.connect.hw.dialog.step.save.walletName.hint": "지갑명을 입력해 주십시오.", + "wallet.connect.hw.dialog.step.save.walletName.label": "지갑명", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor": "!!!https://yoroi-wallet.com/", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor.text": "!!!Click here to know more about how to use Yoroi with Trezor.", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part1": "!!!Only Supports", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link": "!!!https://www.ledger.com/products/ledger-nano-s", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link.text": "!!!Ledger Nano S", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part3": "!!! model.", + "wallet.connect.ledger.dialog.step.about.prerequisite.2.part2": "!!!Cardano ADA app must be installed on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.3": "!!!The Trezor device screen must be unlocked.", + "wallet.connect.ledger.dialog.step.about.prerequisite.5": "!!!Trezor device must remain connected to the computer throughout the process", + "wallet.connect.ledger.dialog.step.connect.introText.line.1": "!!!After connecting your Trezor device to the computer press the Connect button.", + "wallet.connect.ledger.dialog.step.connect.introText.line.2": "!!!A new tab will appear, please follow the instructions in the new tab.", + "wallet.connect.ledger.dialog.step.save.walletName.info": "!!!We have fetched Trezor device’s name for you; you can use as it is or assign a different name.", + "wallet.connect.ledger.dialog.title.label": "!!!Connect to Ledger Hardware Wallet", + "wallet.connect.ledger.error.101": "!!!Failed to connect. Please check your ledger device and retry.", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "TREZOR를 이용한 요로이 사용법은 여기를 클릭하십시오.", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part1": " ", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link.text": "TREZOR 모델 T 2.0.8", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part3": " 이상의 버전만 대응 가능합니다.", + "wallet.connect.trezor.dialog.step.about.prerequisite.2": "TREZOR 디바이스를 사전에 초기화해야 합니다.", + "wallet.connect.trezor.dialog.step.about.prerequisite.3": "TREZOR 디바이스의 화면 잠금을 해제해야 합니다.", + "wallet.connect.trezor.dialog.step.about.prerequisite.5": "진행되는 동안 TREZOR장치는 컴퓨터에 연결되어 있어야 합니다.", + "wallet.connect.trezor.dialog.step.connect.introText.line.1": "TREZOR 디바이스를 컴퓨터에 연결 한 뒤, \"연결\"버튼을 누르십시오.", + "wallet.connect.trezor.dialog.step.connect.introText.line.2": "새로운 탭이 표시됩니다. 새로운 탭에 있는 안내를 따라 주십시오.", + "wallet.connect.trezor.dialog.step.save.walletName.info": "TREZOR 디바이스의 이름을 가져왔습니다. 이름을 그대로 사용하거나 다른 이름을 지정할 수 있습니다.", + "wallet.connect.trezor.dialog.title.label": "TREZOR 하드웨어 지갑에 연결하기", + "wallet.connect.trezor.error.101": "trezor.io에 접속하지 못했습니다. 인터넷 연결상태를 확인하고 다시 시도해 주십시오.", + "wallet.connect.trezor.error.103": "취소되었습니다. 다시 시도해 주십시오.", "wallet.create.dialog.create.personal.wallet.button.label": "개인 지갑 만들기", "wallet.create.dialog.name.label": "지갑 이름", "wallet.create.dialog.passwordFieldPlaceholder": "암호", @@ -176,11 +224,13 @@ "wallet.create.dialog.title": "새 지갑 만들기", "wallet.create.dialog.walletNameHint": "예: 쇼핑 지갑", "wallet.create.dialog.walletPasswordLabel": "지갑 암호", + "wallet.footer.buyLedgerHardwareWallet.text": "!!!Buy a Ledger hardware wallet.", "wallet.footer.buyTrezorHardwareWallet.text": "Trezor 하드웨어 지갑 구매", "wallet.footer.howToConnectTrezor.text": "Trezor 연결 방법", "wallet.footer.howToCreateWallet.text": "지갑 생성 방법", "wallet.footer.howToRestoreWallet.text": "지갑 복구 방법", "wallet.footer.whatIsHardwareWallet.text": "하드웨어 지갑이란", + "wallet.hw.common.error.101": "필요한 권한이 주어지지 않았습니다. 다시 시도해 주십시오.", "wallet.navigation.receive": "받기", "wallet.navigation.send": "보내기", "wallet.navigation.transactions": "거래", @@ -194,6 +244,7 @@ "wallet.receive.page.walletReceiveInstructions": "돈을 받으려면 이 지갑 주소를 공유하십시오. 개인정보를 보호하기 위해 새 주소를 사용하면 새 주소가 자동으로 생성됩니다.", "wallet.recovery.phrase.show.entry.dialog.button.labelClear": "지우기", "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm": "확인", + "wallet.recovery.phrase.show.entry.dialog.button.labelRemoveLast": "!!!Remove last", "wallet.redeem.choices.tab.title.forceVended": "강제 벤드", "wallet.redeem.choices.tab.title.paperVended": "페이퍼 벤드", "wallet.redeem.choices.tab.title.recoveryForceVended": "복구 - 강제 벤드", @@ -249,7 +300,6 @@ "wallet.restore.dialog.walletPasswordLabel": "지갑 암호", "wallet.send.confirmationDialog.addressToLabel": "받는 사람", "wallet.send.confirmationDialog.amountLabel": "금액", - "wallet.send.confirmationDialog.back": "뒤로", "wallet.send.confirmationDialog.feesLabel": "수수료", "wallet.send.confirmationDialog.submit": "보내기", "wallet.send.confirmationDialog.title": "거래 확인", @@ -264,13 +314,16 @@ "wallet.send.form.errors.invalidAddress": "정확한 주소를 입력하십시오.", "wallet.send.form.errors.invalidAmount": "정확한 금액을 입력하십시오.", "wallet.send.form.errors.invalidTitle": "최소 3자 이상의 제목을 입력하십시오.", - "wallet.send.form.next": "다음", "wallet.send.form.receiver.hint": "지갑 주소", "wallet.send.form.receiver.label": "받는 사람", "wallet.send.form.sendingIsDisabled": "보류중인 거래가 있을때는 새로운 거래를 보낼수가 없습니다.", "wallet.send.form.title.hint": "예: Money for Frank", "wallet.send.form.title.label": "제목", "wallet.send.form.transactionFeeError": "수수료에 사용할 충분한 ADA가 없습니다. 더 적은 금액을 송금해 보십시오.", + "wallet.send.ledger.confirmationDialog.info.line.1": "!!!After connecting your Trezor device to your computer, press the Send using Trezor button.", + "wallet.send.ledger.confirmationDialog.info.line.2": "!!!A new tab will appear. Please follow the instructions in the new tab.", + "wallet.send.ledger.confirmationDialog.submit": "!!!Send using Trezor", + "wallet.send.ledger.error.101": "!!!Signing cancelled on Ledger device. Please retry.", "wallet.send.trezor.confirmationDialog.info.line.1": "TREZOR 디바이스를 컴퓨터에 연결한 뒤, \"보내기\" 버튼을 눌러 주십시오.", "wallet.send.trezor.confirmationDialog.info.line.2": "새로운 탭이 표시됩니다. 새로운 탭에 있는 안내를 따라 주십시오.", "wallet.send.trezor.confirmationDialog.submit": "TREZOR를 사용하여 보내기", @@ -290,6 +343,7 @@ "wallet.settings.passwordLastUpdated": "마지막 업데이트 {lastUpdated}", "wallet.summary.no.transaction": "거래 없음", "wallet.summary.no.transactions": "최근 거래 없음", + "wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage": "!!!You have successfully integrated with your Ledger Nano S device", "wallet.summary.page.pendingIncomingConfirmationLabel": "수신 보류 확인", "wallet.summary.page.pendingOutgoingConfirmationLabel": "전송 보류 확인", "wallet.summary.page.showMoreTransactionsButtonLabel": "더 많은 거래 보기", @@ -323,36 +377,5 @@ "wallet.transaction.type": "{currency} 거래", "wallet.transaction.type.exchange": "환율", "wallet.transaction.type.intrawallet": "{currency} 지갑내 거래", - "wallet.transaction.type.multiparty": "{currency} 다중 거래", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "TREZOR를 이용한 요로이 사용법은 여기를 클릭하십시오.", - "wallet.trezor.dialog.connect.button.label": "연결", - "wallet.trezor.dialog.next.button.label": "다음", - "wallet.trezor.dialog.save.button.label": "저장", - "wallet.trezor.dialog.step.about.introText.line.1": "하드웨어 지갑은 작은 USB 디바이스로, 지갑의 보안성을 높입니다.", - "wallet.trezor.dialog.step.about.introText.line.2": "하드웨어 지갑에는 비밀키가 남지 않으므로, 더 높은 안전성을 보장합니다.", - "wallet.trezor.dialog.step.about.introText.line.3": "따라서 컴퓨터가 피싱 시도, 말웨어 등으로 인해 손상되더라도 자금을 안전하게 보호합니다.", - "wallet.trezor.dialog.step.about.label": "개요", - "wallet.trezor.dialog.step.about.prerequisite.1.part1": " ", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/master/ChangeLog", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "TREZOR 모델 T 2.1.0", - "wallet.trezor.dialog.step.about.prerequisite.1.part3": " 이상의 버전만 대응 가능합니다.", - "wallet.trezor.dialog.step.about.prerequisite.2": "TREZOR 디바이스를 사전에 초기화해야 합니다.", - "wallet.trezor.dialog.step.about.prerequisite.3": "TREZOR 디바이스의 화면 잠금을 해제해야 합니다.", - "wallet.trezor.dialog.step.about.prerequisite.4": "진행과정 동안 당신의 컴퓨터는 인터넷에 연결되어 있어야 합니다.", - "wallet.trezor.dialog.step.about.prerequisite.5": "진행되는 동안 TREZOR장치는 컴퓨터에 연결되어 있어야 합니다.", - "wallet.trezor.dialog.step.about.prerequisite.header": "전제조건", - "wallet.trezor.dialog.step.connect.introText.line.1": "TREZOR 디바이스를 컴퓨터에 연결 한 뒤, \"연결\"버튼을 누르십시오.", - "wallet.trezor.dialog.step.connect.introText.line.2": "새로운 탭이 표시됩니다. 새로운 탭에 있는 안내를 따라 주십시오.", - "wallet.trezor.dialog.step.connect.introText.line.3": "Cardano 공용키를 요로이와 공유하는 과정입니다.", - "wallet.trezor.dialog.step.connect.label": "연결", - "wallet.trezor.dialog.step.save.error.101": "저장에 실패했습니다. 인터넷 연결상태를 확인하시고 다시 시도해 주십시오.", - "wallet.trezor.dialog.step.save.label": "저장", - "wallet.trezor.dialog.step.save.walletName.hint": "지갑명을 입력해 주십시오.", - "wallet.trezor.dialog.step.save.walletName.info": "TREZOR 디바이스의 이름을 가져왔습니다. 이름을 그대로 사용하거나 다른 이름을 지정할 수 있습니다.", - "wallet.trezor.dialog.step.save.walletName.label": "지갑명", - "wallet.trezor.dialog.title.label": "TREZOR 하드웨어 지갑에 연결하기", - "wallet.trezor.error.101": "trezor.io에 접속하지 못했습니다. 인터넷 연결상태를 확인하고 다시 시도해 주십시오.", - "wallet.trezor.error.102": "필요한 권한이 주어지지 않았습니다. 다시 시도해 주십시오.", - "wallet.trezor.error.103": "취소되었습니다. 다시 시도해 주십시오." + "wallet.transaction.type.multiparty": "{currency} 다중 거래" } \ No newline at end of file diff --git a/app/i18n/locales/ru-RU.json b/app/i18n/locales/ru-RU.json index 47ea7e5393..58ad7a929e 100644 --- a/app/i18n/locales/ru-RU.json +++ b/app/i18n/locales/ru-RU.json @@ -47,11 +47,12 @@ "daedalusTransfer.error.noTransferTxError": "Нет транзакций для отправки.", "daedalusTransfer.error.transferFundsError": "Не удается перевести средства.", "daedalusTransfer.error.webSocketRestoreError": "Ошибка при востановлении блокчейн адресов.", - "daedalusTransfer.errorPage.backButton.label": "Назад", "daedalusTransfer.errorPage.title.label": "Не удалось восстановить Daedalus кошелек.", "daedalusTransfer.form.instructions.step0.text": "Введите фразу восстановления из 12 слов, используемую для резервного копирования Вашего Daedalus кошелька, чтобы восстановить баланс и перевести все средства из Daedalus на Yoroi.", + "daedalusTransfer.form.instructions.step0MasterKey.text": "!!!Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.form.instructions.step0Paper.text": "Введите фразу восстановления из 27 слов, используемую для резервного копирования Вашего бумажного кошелька Daedalus, чтобы восстановить баланс и перевести все средства из Daedalus на Yoroi. Заметьте - бумажный кошелек после этого будет пуст!", "daedalusTransfer.instructions.attention.confirmation": "Перевести средства из Daedalus кошелька", + "daedalusTransfer.instructions.attention.confirmationMasterKey": "!!!Transfer all funds from Daedalus master key", "daedalusTransfer.instructions.attention.confirmationPaper": "Перевести средства из бумажного кошелька Daedalus", "daedalusTransfer.instructions.attention.text": "Кошельки Yoroi и Daedalus используют разные схемы деривации ключа и разный формат адресов. По этой причине Вы не можете продолжить работу с Вашим Daedalus кошельком и Вам, вместо этого, необходимо создать Yoroi кошелек и перевести Ваши средства (это изменится в будущем). Daedalus и Yoroi кошельки полностью совместимы для перевода средств. Если у Вас нет рабочей копии Daedalus, Вы можете использовать восстановительную фразу из 12 слов (или из 27 слов для бумажного кошелька) для перевода всего баланса из Daedalus в Yoroi.", "daedalusTransfer.summary.addressFrom.subLabel": "Адреса Daedalus кошелька", @@ -64,13 +65,15 @@ "environment.apiVersion.cardano": "1.0.4", "environment.currency.ada": "ADA", "global.errors.fieldIsRequired": "Это поле обязательно к заполнению.", - "global.errors.invalidMnemonic": "Введена неверная фраза, пожалуйста, проверьте.", + "global.errors.invalidMasterKey": "!!!Invalid master key entered, please check.", "global.errors.invalidRepeatPassword": "Не совпадает.", "global.errors.invalidWalletName": "Длина имени кошелька должна быть не менее 1 и не более 40 букв.", "global.errors.invalidWalletPassword": "Неверный пароль", + "global.labels.back": "!!!Back", "global.labels.cancel": "Отменить", "global.labels.change": "Изменить", "global.labels.create": "Создать", + "global.labels.next": "!!!Next", "global.labels.remove": "Удалить", "global.labels.save": "Сохранить", "global.language.chinese.simplified": "简体中文", @@ -133,11 +136,11 @@ "settings.support.reportProblem.link": "Запрос в службу поддержки", "settings.support.reportProblem.title": "Сообщение о проблеме", "testnet.label.message": "Предупреждение: Это тестнет. ADA в тестнете не имеет денежной стоимости. Для получения дополнительной информации, ознакомьтесь с Разделом часто задаваемых вопросов (FAQ) на {faqLink}", - "transfer.form.back": "Назад", "transfer.form.errors.invalidRecoveryPhrase": "Неверная фраза восстановления", "transfer.form.instructions.step1.text": "Восстановление Вашего баланса займет около 1 минуты. На следующем этапе Вам будет показана транзакция, которая переместит все Ваши средства. Пожалуйста, тщательно просмотрите детали транзакции. Вам потребуется оплатить стандартную комиссию в сети Cardano за проведение транзакции.", - "transfer.form.instructions.title.label": "Инструкции", - "transfer.form.next": "Далее", + "transfer.form.masterkey.input.hint": "!!!Enter master key", + "transfer.form.masterkey.input.label": "!!!Master key", + "transfer.form.masterkey.requirement": "!!!Note: master keys are 192 characters and hexadecimal-encoded", "transfer.form.recovery.phrase.input.hint": "Ввести фразу восстановления", "transfer.form.recovery.phrase.input.label": "Фраза восстановления", "transfer.form.recovery.phrase.input.noResults": "Нет результатов", @@ -153,7 +156,9 @@ "transfer.summary.transactionFee.label": "Комиссия", "transfer.summary.transferButton.label": "Перевести средства", "wallet.add.dialog.create.description": "Создать новый кошелек", + "wallet.add.dialog.createLedgerWalletNotificationMessage": "!!!Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.createTrezorWalletNotificationMessage": "В настоящее время осуществляется Trezor Connect. До его полного завершения, невозможно восстановить или импортировать новые кошельки.", + "wallet.add.dialog.ledger.description": "!!!Connect to Ledger", "wallet.add.dialog.restore.description": "Восстановить кошелек из резервной копии", "wallet.add.dialog.restoreNotificationMessage": "В настоящее время осуществляется восстановление кошелька. До его полного завершения, невозможно восстановить или импортировать новые кошельки.", "wallet.add.dialog.title.label": "Добавить кошелек", @@ -169,6 +174,49 @@ "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.device": "Я понимаю, что мои секретные ключи безопасным образом хранятся только на данном устройстве, а не на серверах компании.", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "Я понимаю, что если данное приложение будет перемещено на другое устройство или удалено, мои средства могут\n быть восстановлены только с помощью фразы восстановления, которую я записал и сохранил в безопасном месте.", "wallet.backup.recovery.phrase.entry.dialog.verification.instructions": "Нажмите на каждое слово в правильном порядке для проверки Вашей фразы восстановления", + "wallet.connect.hw.dialog.connect.button.label": "Подключиться", + "wallet.connect.hw.dialog.save.button.label": "Сохранить", + "wallet.connect.hw.dialog.step.about.introText.line.1": "Аппаратный кошелек - это USB устройство, которое предоставляет экстра уровень безопасности.", + "wallet.connect.hw.dialog.step.about.introText.line.2": "Это безопаснее, т.к. приватный ключ (англ. \"private key\") никогда не покидает аппаратный кошелек.", + "wallet.connect.hw.dialog.step.about.introText.line.3": "Он защищает средства, если компьютер скомпрометирован вредоносным ПО, фишингом и т.д.", + "wallet.connect.hw.dialog.step.about.label": "Информация", + "wallet.connect.hw.dialog.step.about.prerequisite.4": "Компьютер должен оставаться подключенным к Интернету на протяжении всего процесса.", + "wallet.connect.hw.dialog.step.about.prerequisite.header": "Необходимые условия", + "wallet.connect.hw.dialog.step.connect.introText.line.3": "Этот процесс делится публичными ключами Cardano с Yoroi.", + "wallet.connect.hw.dialog.step.connect.label": "ПОДКЛЮЧЕНИЕ", + "wallet.connect.hw.dialog.step.save.error.101": "Не удалось сохранить. Проверьте подключение к Интернету и повторите попытку.", + "wallet.connect.hw.dialog.step.save.label": "СОХРАНИТЬ", + "wallet.connect.hw.dialog.step.save.walletName.hint": "Ввести имя кошелька", + "wallet.connect.hw.dialog.step.save.walletName.label": "ИМЯ КОШЕЛЬКА", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor": "!!!https://yoroi-wallet.com/", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor.text": "!!!Click here to know more about how to use Yoroi with Trezor.", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part1": "!!!Only Supports", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link": "!!!https://www.ledger.com/products/ledger-nano-s", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link.text": "!!!Ledger Nano S", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part3": "!!! model.", + "wallet.connect.ledger.dialog.step.about.prerequisite.2.part2": "!!!Cardano ADA app must be installed on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.3": "!!!The Trezor device screen must be unlocked.", + "wallet.connect.ledger.dialog.step.about.prerequisite.5": "!!!Trezor device must remain connected to the computer throughout the process", + "wallet.connect.ledger.dialog.step.connect.introText.line.1": "!!!After connecting your Trezor device to the computer press the Connect button.", + "wallet.connect.ledger.dialog.step.connect.introText.line.2": "!!!A new tab will appear, please follow the instructions in the new tab.", + "wallet.connect.ledger.dialog.step.save.walletName.info": "!!!We have fetched Trezor device’s name for you; you can use as it is or assign a different name.", + "wallet.connect.ledger.dialog.title.label": "!!!Connect to Ledger Hardware Wallet", + "wallet.connect.ledger.error.101": "!!!Failed to connect. Please check your ledger device and retry.", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Нажмите здесь, чтобы узнать больше об использовании Yoroi с Trezor", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part1": "Поддерживает только ", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T с версией 2.0.8", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part3": " или более позднюю версию.", + "wallet.connect.trezor.dialog.step.about.prerequisite.2": "Trezor должен быть пре-инициализирован.", + "wallet.connect.trezor.dialog.step.about.prerequisite.3": "Экран устройства Trezor должен быть разблокирован.", + "wallet.connect.trezor.dialog.step.about.prerequisite.5": "Trezor должен оставаться подключенным в течение всего процесса.", + "wallet.connect.trezor.dialog.step.connect.introText.line.1": "После подключения устройства Trezor к своему компьютеру, нажмите кнопку Подключиться.", + "wallet.connect.trezor.dialog.step.connect.introText.line.2": "Появится новая вкладка. Пожалуйста, следуйте инструкциям в новой вкладке.", + "wallet.connect.trezor.dialog.step.save.walletName.info": "Мы взяли имя Trezor-устройства в качестве названия Вашего кошелька. При желании, Вы можете поменять его.", + "wallet.connect.trezor.dialog.title.label": "ПОДКЛЮЧИТЬСЯ К АППАРАТНОМУ КОШЕЛЬКУ TREZOR", + "wallet.connect.trezor.error.101": "Не удалось подключиться к trezor.io. Пожалуйста, проверьте Ваше подключение к Интернету и повторите попытку.", + "wallet.connect.trezor.error.103": "Отменено. Пожалуйста, повторите попытку.", "wallet.create.dialog.create.personal.wallet.button.label": "Создать личный кошелек", "wallet.create.dialog.name.label": "Имя кошелька", "wallet.create.dialog.passwordFieldPlaceholder": "Пароль", @@ -176,11 +224,13 @@ "wallet.create.dialog.title": "Создать новый кошелек", "wallet.create.dialog.walletNameHint": "например: Кошелек для шоппинга", "wallet.create.dialog.walletPasswordLabel": "Пароль кошелька", + "wallet.footer.buyLedgerHardwareWallet.text": "!!!Buy a Ledger hardware wallet.", "wallet.footer.buyTrezorHardwareWallet.text": "Купить аппаратный кошелек Trezor", "wallet.footer.howToConnectTrezor.text": "Как подключить Trezor", "wallet.footer.howToCreateWallet.text": "Как создать кошелек", "wallet.footer.howToRestoreWallet.text": "Как восстановить кошелек", "wallet.footer.whatIsHardwareWallet.text": "Что такое аппаратный кошелек", + "wallet.hw.common.error.101": "Необходимые разрешения не были предоставлены пользователем. Повторите попытку.", "wallet.navigation.receive": "Получить", "wallet.navigation.send": "Отправить", "wallet.navigation.transactions": "Транзакции", @@ -194,6 +244,7 @@ "wallet.receive.page.walletReceiveInstructions": "Поделитесь данным адресом кошелька для получения платежей. Для защиты Вашей конфиденциальности, новые адреса генерируются автоматически после того, как Вы их использовали.", "wallet.recovery.phrase.show.entry.dialog.button.labelClear": "Очистить", "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm": "Подтвердить", + "wallet.recovery.phrase.show.entry.dialog.button.labelRemoveLast": "!!!Remove last", "wallet.redeem.choices.tab.title.forceVended": "Форсированная продажа", "wallet.redeem.choices.tab.title.paperVended": "Продажа документа", "wallet.redeem.choices.tab.title.recoveryForceVended": "Восстановление - форсированная продажа", @@ -249,7 +300,6 @@ "wallet.restore.dialog.walletPasswordLabel": "Пароль кошелька", "wallet.send.confirmationDialog.addressToLabel": "Кому", "wallet.send.confirmationDialog.amountLabel": "Сумма", - "wallet.send.confirmationDialog.back": "Назад", "wallet.send.confirmationDialog.feesLabel": "Комиссия", "wallet.send.confirmationDialog.submit": "Отправить", "wallet.send.confirmationDialog.title": "Подтвердить транзакцию", @@ -264,13 +314,16 @@ "wallet.send.form.errors.invalidAddress": "Пожалуйста, введите действительный адрес.", "wallet.send.form.errors.invalidAmount": "Пожалуйста, введите правильную сумму.", "wallet.send.form.errors.invalidTitle": "Пожалуйста, введите название минимум с 3 символами.", - "wallet.send.form.next": "Далее", "wallet.send.form.receiver.hint": "Адрес кошелька", "wallet.send.form.receiver.label": "Получатель", "wallet.send.form.sendingIsDisabled": "Невозможно совершить новую транзакцию пока текущая не завершена.", "wallet.send.form.title.hint": "Например: Деньги для Фрэнка", "wallet.send.form.title.label": "Название", "wallet.send.form.transactionFeeError": "Недостаточно Ada для оплаты комиссии. Попробуйте отправить меньшую сумму.", + "wallet.send.ledger.confirmationDialog.info.line.1": "!!!After connecting your Trezor device to your computer, press the Send using Trezor button.", + "wallet.send.ledger.confirmationDialog.info.line.2": "!!!A new tab will appear. Please follow the instructions in the new tab.", + "wallet.send.ledger.confirmationDialog.submit": "!!!Send using Trezor", + "wallet.send.ledger.error.101": "!!!Signing cancelled on Ledger device. Please retry.", "wallet.send.trezor.confirmationDialog.info.line.1": "После подключения Trezor к компьютеру нажмите кнопку Отправить с помощью Trezor.", "wallet.send.trezor.confirmationDialog.info.line.2": "Появится новая вкладка. Пожалуйста, следуйте инструкциям в новой вкладке.", "wallet.send.trezor.confirmationDialog.submit": "Отправить с помощью Trezor", @@ -290,6 +343,7 @@ "wallet.settings.passwordLastUpdated": "Последнее обновление {lastUpdated}", "wallet.summary.no.transaction": "Транзакции не найдены", "wallet.summary.no.transactions": "Нет недавних транзакций", + "wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage": "!!!You have successfully integrated with your Ledger Nano S device", "wallet.summary.page.pendingIncomingConfirmationLabel": "Входящие, ожидающие подтверждения", "wallet.summary.page.pendingOutgoingConfirmationLabel": "Исходящие, ожидающие подтверждения", "wallet.summary.page.showMoreTransactionsButtonLabel": "Показать больше транзакций", @@ -323,36 +377,5 @@ "wallet.transaction.type": "{currency} транзакции", "wallet.transaction.type.exchange": "Обмен", "wallet.transaction.type.intrawallet": "{currency} внутренняя транзакция", - "wallet.transaction.type.multiparty": "{currency} смешанная транзакция", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "Нажмите здесь, чтобы узнать больше об использовании Yoroi с Trezor", - "wallet.trezor.dialog.connect.button.label": "Подключиться", - "wallet.trezor.dialog.next.button.label": "Далее", - "wallet.trezor.dialog.save.button.label": "Сохранить", - "wallet.trezor.dialog.step.about.introText.line.1": "Аппаратный кошелек - это USB устройство, которое предоставляет экстра уровень безопасности.", - "wallet.trezor.dialog.step.about.introText.line.2": "Это безопаснее, т.к. приватный ключ (англ. \"private key\") никогда не покидает аппаратный кошелек.", - "wallet.trezor.dialog.step.about.introText.line.3": "Он защищает средства, если компьютер скомпрометирован вредоносным ПО, фишингом и т.д.", - "wallet.trezor.dialog.step.about.label": "Информация", - "wallet.trezor.dialog.step.about.prerequisite.1.part1": "Поддерживает только ", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/master/ChangeLog", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T с версией 2.1.0", - "wallet.trezor.dialog.step.about.prerequisite.1.part3": " или более позднюю версию.", - "wallet.trezor.dialog.step.about.prerequisite.2": "Trezor должен быть пре-инициализирован.", - "wallet.trezor.dialog.step.about.prerequisite.3": "Экран устройства Trezor должен быть разблокирован.", - "wallet.trezor.dialog.step.about.prerequisite.4": "Компьютер должен оставаться подключенным к Интернету на протяжении всего процесса.", - "wallet.trezor.dialog.step.about.prerequisite.5": "Trezor должен оставаться подключенным в течение всего процесса.", - "wallet.trezor.dialog.step.about.prerequisite.header": "Необходимые условия", - "wallet.trezor.dialog.step.connect.introText.line.1": "После подключения устройства Trezor к своему компьютеру, нажмите кнопку Подключиться.", - "wallet.trezor.dialog.step.connect.introText.line.2": "Появится новая вкладка. Пожалуйста, следуйте инструкциям в новой вкладке.", - "wallet.trezor.dialog.step.connect.introText.line.3": "Этот процесс делится публичными ключами Cardano с Yoroi.", - "wallet.trezor.dialog.step.connect.label": "ПОДКЛЮЧЕНИЕ", - "wallet.trezor.dialog.step.save.error.101": "Не удалось сохранить. Проверьте подключение к Интернету и повторите попытку.", - "wallet.trezor.dialog.step.save.label": "СОХРАНИТЬ", - "wallet.trezor.dialog.step.save.walletName.hint": "Ввести имя кошелька", - "wallet.trezor.dialog.step.save.walletName.info": "Мы взяли имя Trezor-устройства в качестве названия Вашего кошелька. При желании, Вы можете поменять его.", - "wallet.trezor.dialog.step.save.walletName.label": "ИМЯ КОШЕЛЬКА", - "wallet.trezor.dialog.title.label": "ПОДКЛЮЧИТЬСЯ К АППАРАТНОМУ КОШЕЛЬКУ TREZOR", - "wallet.trezor.error.101": "Не удалось подключиться к trezor.io. Пожалуйста, проверьте Ваше подключение к Интернету и повторите попытку.", - "wallet.trezor.error.102": "Необходимые разрешения не были предоставлены пользователем. Повторите попытку.", - "wallet.trezor.error.103": "Отменено. Пожалуйста, повторите попытку." + "wallet.transaction.type.multiparty": "{currency} смешанная транзакция" } \ No newline at end of file diff --git a/app/i18n/locales/terms-of-use/ada/ja-JP.md b/app/i18n/locales/terms-of-use/ada/ja-JP.md index 96ecffdc01..5a910c42c8 100644 --- a/app/i18n/locales/terms-of-use/ada/ja-JP.md +++ b/app/i18n/locales/terms-of-use/ada/ja-JP.md @@ -57,7 +57,7 @@ ユーザーは、本ソフトウェアおよび/または当社により促進されるADAその他の仮想通貨の取引が遅延する可能性があること、また、遅延に伴ういかなる損失についても当社は責任を負わないものとすることを承知しており、これに同意します。 ユーザーは、本ソフトウェアおよび/または当社にアクセス可能なまたはリンクする第三者の資料等または第三者のサイトに含まれる情報、コンテンツまたはサービスのいずれの側面についても当社は責任を負わないものとすることを承知しており、これに同意します。 ユーザーは、ユーザーがユーザーの証明書をいかなる自然人または法人(当社、カルダノ財団、インプット・アウトプット・エイチケイ・リミテッドまたはその他の組織を含みます。)とも共有しないことに同意します。また、ユーザーは、ユーザーの証明書を共有することによりユーザーのADAその他の仮想通貨が失われる可能性があることを承知しており、かかる損失について当社は責任を負わないものとすることに同意します。 -ユーザーは、テストネット上でADAを還元することによりユーザーはテストADAのみを還元すること、また、実際のADAを還元するためには、ユーザーはリリース後にメインネット上で手続を繰り返さなければならないことを承知しており、これに同意します。 +ユーザーは、テストネット上でADAを還元することによりユーザーはテストADAのみを還元すること、また、実際のADAを還元するためには、ユーザーはリリース後にメインネット上で手続を繰り返さなければならないことを承知しており、これに同意します。 本ソフトウェアを利用することにより、ユーザーは、(i)当社が下層プロトコルの運用について責任を負わないこと、また、当社がその機能性、セキュリティおよび可用性について何ら保証しないこと、(ii)規則の運用上、下層プロトコルはメインチェーンの突然の変更(以下「フォーク」といいます。)による影響を受ける可能性があること、また、かかるフォークはユーザーが本ソフトウェア上で保管するADAその他の仮想通貨の価値および/または機能に重大な影響を及ぼす可能性があることを承知しており、これらに同意します。フォークした場合、ユーザーは、当社が(ユーザーに通知した上で、またはユーザーに通知することなく)本ソフトウェアの運用を一時的に停止することができること、また、当社が自己の裁量で(a)自社システムの設定または再設定を行うことができること、または(b)フォークしたプロトコルを一切サポートしない(またはサポートを停止する)旨決定することができることに同意します(但し、ユーザーは、本ソフトウェアから資金を引き出す機会を有するものとします。)。ユーザーは、フォークしたプロトコルのうちサポート対象外のブランチについて当社は一切責任を負わないことを承知しており、これに同意します。 ## 11. その他 diff --git a/app/i18n/locales/zh-Hans.json b/app/i18n/locales/zh-Hans.json index 5e3a7db75d..52d3ef4ac7 100644 --- a/app/i18n/locales/zh-Hans.json +++ b/app/i18n/locales/zh-Hans.json @@ -47,11 +47,12 @@ "daedalusTransfer.error.noTransferTxError": "没有要发送的交易。", "daedalusTransfer.error.transferFundsError": "无法转账。", "daedalusTransfer.error.webSocketRestoreError": "恢复区块链地址时发生错误。", - "daedalusTransfer.errorPage.backButton.label": "返回", "daedalusTransfer.errorPage.title.label": "无法恢复 Daedalus 钱包。", "daedalusTransfer.form.instructions.step0.text": "找到您的 Daedalus 助记符并修复它", + "daedalusTransfer.form.instructions.step0MasterKey.text": "!!!Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.form.instructions.step0Paper.text": "輸入用於備份Daedalus Paper錢包的27字恢復短語,恢復餘額並將所有資金從Daedalus轉移到Yoroi。", "daedalusTransfer.instructions.attention.confirmation": "转出 Daedalus 钱包中的所有资金", + "daedalusTransfer.instructions.attention.confirmationMasterKey": "!!!Transfer all funds from Daedalus master key", "daedalusTransfer.instructions.attention.confirmationPaper": "转出 Daedalus 钱包中的所有资金", "daedalusTransfer.instructions.attention.text": "Yoroi钱包与Daedalus钱包使用不同的金钥衍生配置,并且两者均采用单独的地址格式。因此,您无法继续使用Daedalus钱包,您必须创建一个Yoroi钱包并转移您的资金(这可在将来更改) Daedalus与Yoroi钱包与资金转移完全相容。如果您没有Daedalus的副本,您可以使用12字恢复短语(或纸钱包之27个单词)将余额从Daedalus转移到Yoroi。", "daedalusTransfer.summary.addressFrom.subLabel": "Daedalus 钱包地址", @@ -64,13 +65,15 @@ "environment.apiVersion.cardano": "1.0.4", "environment.currency.ada": "ADA", "global.errors.fieldIsRequired": "该字段为必填字段。", - "global.errors.invalidMnemonic": "输入的短语无效,请检查", + "global.errors.invalidMasterKey": "!!!Invalid master key entered, please check.", "global.errors.invalidRepeatPassword": "不匹配。", "global.errors.invalidWalletName": "钱包名称需要最少 1 个,最多 40 个字母。", "global.errors.invalidWalletPassword": "密码无效", + "global.labels.back": "!!!Back", "global.labels.cancel": "取消", "global.labels.change": "更改", "global.labels.create": "创建", + "global.labels.next": "!!!Next", "global.labels.remove": "移除", "global.labels.save": "保存", "global.language.chinese.simplified": "简体中文", @@ -133,11 +136,11 @@ "settings.support.reportProblem.link": "支援请求", "settings.support.reportProblem.title": "报告问题", "testnet.label.message": "警告:这是一个测试网(testnet)。测试网上的ADA并没有货币价值。有关更多资讯,请查看{faqLink} 上的常见问题。", - "transfer.form.back": "返回", "transfer.form.errors.invalidRecoveryPhrase": "恢复短语无效", "transfer.form.instructions.step1.text": "恢复余额大约需要1分钟。 在下一步,您将看到一笔交易,该交易将移动您的所有资金。 请仔细阅读交易细节。 您需要在Cardano网络上支付标准交易费用才能进行交易。", - "transfer.form.instructions.title.label": "说明", - "transfer.form.next": "下一步", + "transfer.form.masterkey.input.hint": "!!!Enter master key", + "transfer.form.masterkey.input.label": "!!!Master key", + "transfer.form.masterkey.requirement": "!!!Note: master keys are 192 characters and hexadecimal-encoded", "transfer.form.recovery.phrase.input.hint": "输入恢复短语", "transfer.form.recovery.phrase.input.label": "恢复短语", "transfer.form.recovery.phrase.input.noResults": "无结果", @@ -153,7 +156,9 @@ "transfer.summary.transactionFee.label": "交易失败", "transfer.summary.transferButton.label": "转账资金", "wallet.add.dialog.create.description": "创建新钱包", + "wallet.add.dialog.createLedgerWalletNotificationMessage": "!!!Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.createTrezorWalletNotificationMessage": "目前正在修复Trezor Connect中。在完成之前,无法恢复或导入新钱包。", + "wallet.add.dialog.ledger.description": "!!!Connect to Ledger", "wallet.add.dialog.restore.description": "从备份恢复钱包", "wallet.add.dialog.restoreNotificationMessage": "目前正在修复钱包中。在完成之前,无法恢复或导入新钱包。", "wallet.add.dialog.title.label": "添加钱包", @@ -169,6 +174,49 @@ "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.device": "我明白我的密钥是安全地储存在此设备上,而不是公司的伺服器上。", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "我了解如果此应用程序转移到其他设备或被删除,我的资金只能\n 使用我记在某个安全地方的助记词恢复。", "wallet.backup.recovery.phrase.entry.dialog.verification.instructions": "以正确的顺序点击每个单词以验证您的助记词", + "wallet.connect.hw.dialog.connect.button.label": "连接", + "wallet.connect.hw.dialog.save.button.label": "保存", + "wallet.connect.hw.dialog.step.about.introText.line.1": "硬件钱包是一种小型 USB 设备,为您的钱包增加额外的安全性。", + "wallet.connect.hw.dialog.step.about.introText.line.2": "它更安全, 因为您的私钥永远不会离开硬件钱包。", + "wallet.connect.hw.dialog.step.about.introText.line.3": "即使您的电脑因恶意软件、网络威胁等, 也会保护您的资金。", + "wallet.connect.hw.dialog.step.about.label": "关于", + "wallet.connect.hw.dialog.step.about.prerequisite.4": "您的电脑必须在整个过程中保持与互联网的连接。", + "wallet.connect.hw.dialog.step.about.prerequisite.header": "先决条件", + "wallet.connect.hw.dialog.step.connect.introText.line.3": "此过程与Yoroi共享Cardano公钥。", + "wallet.connect.hw.dialog.step.connect.label": "连接", + "wallet.connect.hw.dialog.step.save.error.101": "未能保存。请检查您的互联网连接, 然后重试。", + "wallet.connect.hw.dialog.step.save.label": "保存", + "wallet.connect.hw.dialog.step.save.walletName.hint": "输入钱包名称", + "wallet.connect.hw.dialog.step.save.walletName.label": "钱包名称", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor": "!!!https://yoroi-wallet.com/", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor.text": "!!!Click here to know more about how to use Yoroi with Trezor.", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part1": "!!!Only Supports", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link": "!!!https://www.ledger.com/products/ledger-nano-s", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link.text": "!!!Ledger Nano S", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part3": "!!! model.", + "wallet.connect.ledger.dialog.step.about.prerequisite.2.part2": "!!!Cardano ADA app must be installed on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.3": "!!!The Trezor device screen must be unlocked.", + "wallet.connect.ledger.dialog.step.about.prerequisite.5": "!!!Trezor device must remain connected to the computer throughout the process", + "wallet.connect.ledger.dialog.step.connect.introText.line.1": "!!!After connecting your Trezor device to the computer press the Connect button.", + "wallet.connect.ledger.dialog.step.connect.introText.line.2": "!!!A new tab will appear, please follow the instructions in the new tab.", + "wallet.connect.ledger.dialog.step.save.walletName.info": "!!!We have fetched Trezor device’s name for you; you can use as it is or assign a different name.", + "wallet.connect.ledger.dialog.title.label": "!!!Connect to Ledger Hardware Wallet", + "wallet.connect.ledger.error.101": "!!!Failed to connect. Please check your ledger device and retry.", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "点击这里了解更多关于使用 Yoroi 与 Trezor", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part1": "仅支援 ", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T 2.0.8版本", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part3": " 或更新版本。", + "wallet.connect.trezor.dialog.step.about.prerequisite.2": "Trezor 设备必须预先初始化。", + "wallet.connect.trezor.dialog.step.about.prerequisite.3": "Trezor设备屏幕必须解锁。", + "wallet.connect.trezor.dialog.step.about.prerequisite.5": "在整个过程中, Trezor设备必须保持与电脑的连接。", + "wallet.connect.trezor.dialog.step.connect.introText.line.1": "将Trezor设备连接到电脑后,使用Trezor按钮按发送。", + "wallet.connect.trezor.dialog.step.connect.introText.line.2": "将出现一个标签。 请按照新标签中的说明操作。", + "wallet.connect.trezor.dialog.step.save.walletName.info": "我们已为您取了Trezor设备的名称。您可以按原样使用, 也可以指定不同的名称。", + "wallet.connect.trezor.dialog.title.label": "连接到Trezor硬件钱包", + "wallet.connect.trezor.error.101": "无法连接 trezor.io。请检查您的互联网连接, 然后重试。", + "wallet.connect.trezor.error.103": "取消。请重试。", "wallet.create.dialog.create.personal.wallet.button.label": "创建个人钱包", "wallet.create.dialog.name.label": "钱包名称", "wallet.create.dialog.passwordFieldPlaceholder": "密码", @@ -176,11 +224,13 @@ "wallet.create.dialog.title": "创建新钱包", "wallet.create.dialog.walletNameHint": "例如:购物钱包", "wallet.create.dialog.walletPasswordLabel": "钱包密码", + "wallet.footer.buyLedgerHardwareWallet.text": "!!!Buy a Ledger hardware wallet.", "wallet.footer.buyTrezorHardwareWallet.text": "购买Trezor硬件钱包", "wallet.footer.howToConnectTrezor.text": "如何连接Trezor", "wallet.footer.howToCreateWallet.text": "如何创建钱包", "wallet.footer.howToRestoreWallet.text": "如何恢复钱包", "wallet.footer.whatIsHardwareWallet.text": "什么是硬件钱包", + "wallet.hw.common.error.101": "用户未授予必要的权限。请重试。", "wallet.navigation.receive": "接收", "wallet.navigation.send": "发送", "wallet.navigation.transactions": "交易", @@ -194,6 +244,7 @@ "wallet.receive.page.walletReceiveInstructions": "共享此钱包地址以接收付款。为保护您的隐私,在您使用地址后会自动生成新地址。", "wallet.recovery.phrase.show.entry.dialog.button.labelClear": "清除", "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm": "确认", + "wallet.recovery.phrase.show.entry.dialog.button.labelRemoveLast": "!!!Remove last", "wallet.redeem.choices.tab.title.forceVended": "强行执行", "wallet.redeem.choices.tab.title.paperVended": "Paper執行", "wallet.redeem.choices.tab.title.recoveryForceVended": "强行恢复", @@ -249,7 +300,6 @@ "wallet.restore.dialog.walletPasswordLabel": "钱包密码", "wallet.send.confirmationDialog.addressToLabel": "到", "wallet.send.confirmationDialog.amountLabel": "金额", - "wallet.send.confirmationDialog.back": "返回", "wallet.send.confirmationDialog.feesLabel": "手续费", "wallet.send.confirmationDialog.submit": "发送", "wallet.send.confirmationDialog.title": "确认交易", @@ -264,13 +314,16 @@ "wallet.send.form.errors.invalidAddress": "请输入有效地址。", "wallet.send.form.errors.invalidAmount": "请输入有效金额。", "wallet.send.form.errors.invalidTitle": "请输入至少包含 3 个字符的标题。", - "wallet.send.form.next": "下一步", "wallet.send.form.receiver.hint": "钱包地址", "wallet.send.form.receiver.label": "接收人", "wallet.send.form.sendingIsDisabled": "由于目前有待处理状态的交易,新交易无法发送。", "wallet.send.form.title.hint": "例如:支付给 Frank 的款项", "wallet.send.form.title.label": "标题", "wallet.send.form.transactionFeeError": "没有足够的 Ada 支付手续费。请尝试发送更小的金额。", + "wallet.send.ledger.confirmationDialog.info.line.1": "!!!After connecting your Trezor device to your computer, press the Send using Trezor button.", + "wallet.send.ledger.confirmationDialog.info.line.2": "!!!A new tab will appear. Please follow the instructions in the new tab.", + "wallet.send.ledger.confirmationDialog.submit": "!!!Send using Trezor", + "wallet.send.ledger.error.101": "!!!Signing cancelled on Ledger device. Please retry.", "wallet.send.trezor.confirmationDialog.info.line.1": "将Trezor设备连接到电脑后,使用Trezor按钮按发送。", "wallet.send.trezor.confirmationDialog.info.line.2": "将出现一个标签。 请按照新标签中的说明操作。", "wallet.send.trezor.confirmationDialog.submit": "使用Trezor发送", @@ -290,6 +343,7 @@ "wallet.settings.passwordLastUpdated": "上次更新的 {lastUpdated}", "wallet.summary.no.transaction": "未找到交易", "wallet.summary.no.transactions": "无最近的交易", + "wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage": "!!!You have successfully integrated with your Ledger Nano S device", "wallet.summary.page.pendingIncomingConfirmationLabel": "入账暂挂确认", "wallet.summary.page.pendingOutgoingConfirmationLabel": "出账暂挂确认", "wallet.summary.page.showMoreTransactionsButtonLabel": "显示更多交易(已编辑)", @@ -323,36 +377,5 @@ "wallet.transaction.type": "{currency} 交易", "wallet.transaction.type.exchange": "交易所", "wallet.transaction.type.intrawallet": "{currency} 钱包间交易", - "wallet.transaction.type.multiparty": "{currency} 多方交易", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "点击这里了解更多关于使用 Yoroi 与 Trezor", - "wallet.trezor.dialog.connect.button.label": "连接", - "wallet.trezor.dialog.next.button.label": "下一步", - "wallet.trezor.dialog.save.button.label": "保存", - "wallet.trezor.dialog.step.about.introText.line.1": "硬件钱包是一种小型 USB 设备,为您的钱包增加额外的安全性。", - "wallet.trezor.dialog.step.about.introText.line.2": "它更安全, 因为您的私钥永远不会离开硬件钱包。", - "wallet.trezor.dialog.step.about.introText.line.3": "即使您的电脑因恶意软件、网络威胁等, 也会保护您的资金。", - "wallet.trezor.dialog.step.about.label": "关于", - "wallet.trezor.dialog.step.about.prerequisite.1.part1": "仅支援 ", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/master/ChangeLog", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T 2.1.0版本", - "wallet.trezor.dialog.step.about.prerequisite.1.part3": " 或更新版本。", - "wallet.trezor.dialog.step.about.prerequisite.2": "Trezor 设备必须预先初始化。", - "wallet.trezor.dialog.step.about.prerequisite.3": "Trezor设备屏幕必须解锁。", - "wallet.trezor.dialog.step.about.prerequisite.4": "您的电脑必须在整个过程中保持与互联网的连接。", - "wallet.trezor.dialog.step.about.prerequisite.5": "在整个过程中, Trezor设备必须保持与电脑的连接。", - "wallet.trezor.dialog.step.about.prerequisite.header": "先决条件", - "wallet.trezor.dialog.step.connect.introText.line.1": "将Trezor设备连接到电脑后,使用Trezor按钮按发送。", - "wallet.trezor.dialog.step.connect.introText.line.2": "将出现一个标签。 请按照新标签中的说明操作。", - "wallet.trezor.dialog.step.connect.introText.line.3": "此过程与Yoroi共享Cardano公钥。", - "wallet.trezor.dialog.step.connect.label": "连接", - "wallet.trezor.dialog.step.save.error.101": "未能保存。请检查您的互联网连接, 然后重试。", - "wallet.trezor.dialog.step.save.label": "保存", - "wallet.trezor.dialog.step.save.walletName.hint": "输入钱包名称", - "wallet.trezor.dialog.step.save.walletName.info": "我们已为您取了Trezor设备的名称。您可以按原样使用, 也可以指定不同的名称。", - "wallet.trezor.dialog.step.save.walletName.label": "钱包名称", - "wallet.trezor.dialog.title.label": "连接到Trezor硬件钱包", - "wallet.trezor.error.101": "无法连接 trezor.io。请检查您的互联网连接, 然后重试。", - "wallet.trezor.error.102": "用户未授予必要的权限。请重试。", - "wallet.trezor.error.103": "取消。请重试。" + "wallet.transaction.type.multiparty": "{currency} 多方交易" } \ No newline at end of file diff --git a/app/i18n/locales/zh-Hant.json b/app/i18n/locales/zh-Hant.json index 3391e362df..fdf29634f7 100644 --- a/app/i18n/locales/zh-Hant.json +++ b/app/i18n/locales/zh-Hant.json @@ -47,11 +47,12 @@ "daedalusTransfer.error.noTransferTxError": "無交易傳送。", "daedalusTransfer.error.transferFundsError": "無法轉移資金。", "daedalusTransfer.error.webSocketRestoreError": "還原區塊鏈結時出現錯誤。", - "daedalusTransfer.errorPage.backButton.label": "返回", "daedalusTransfer.errorPage.title.label": "無法還原 Daedalus 錢包。", "daedalusTransfer.form.instructions.step0.text": "尋找 Daedalus 助憶鍵並將其準備妥當", + "daedalusTransfer.form.instructions.step0MasterKey.text": "!!!Enter the unencrypted master key for your Daedalus wallet to restore the balance and transfer all the funds from Daedalus to Yoroi.", "daedalusTransfer.form.instructions.step0Paper.text": "輸入用於備份Daedalus Paper錢包的27字恢復短語,恢復餘額並將所有資金從Daedalus轉移到Yoroi。", "daedalusTransfer.instructions.attention.confirmation": "從Daedalus錢包轉移所有資金", + "daedalusTransfer.instructions.attention.confirmationMasterKey": "!!!Transfer all funds from Daedalus master key", "daedalusTransfer.instructions.attention.confirmationPaper": "轉移 Daedalus 錢包中的全部資金", "daedalusTransfer.instructions.attention.text": "Yoroi 錢包與 Daedalus 錢包使用不同的金鑰衍生配置,並且兩者均採用單獨的地址格式。因此,您無法繼續使用Daedalus錢包,您必須創建一個Yoroi錢包並轉移您的資金(這可在將來更改)。 Daedalus 與 Yoroi 錢包與資金轉移完全相容。 如果您沒有Daedalus的副本,您可以使用12字恢復短語(或紙錢包之27個單詞)將餘額從Daedalus轉移到Yoroi。", "daedalusTransfer.summary.addressFrom.subLabel": "Daedalus 錢包地址", @@ -64,13 +65,15 @@ "environment.apiVersion.cardano": "1.0.4", "environment.currency.ada": "ADA", "global.errors.fieldIsRequired": "此欄位為必填欄。", - "global.errors.invalidMnemonic": "輸入的短語無效, 請檢查", + "global.errors.invalidMasterKey": "!!!Invalid master key entered, please check.", "global.errors.invalidRepeatPassword": "不相符。", "global.errors.invalidWalletName": "錢包名稱需要至少1個, 最多40個字母。", "global.errors.invalidWalletPassword": "無效密碼", + "global.labels.back": "!!!Back", "global.labels.cancel": "取消", "global.labels.change": "變更", "global.labels.create": "建立", + "global.labels.next": "!!!Next", "global.labels.remove": "移除", "global.labels.save": "儲存", "global.language.chinese.simplified": "简体中文", @@ -133,11 +136,11 @@ "settings.support.reportProblem.link": "支援要求", "settings.support.reportProblem.title": "報告問題", "testnet.label.message": "警告:這是一個測試網(testnet)。測試網上的ADA並沒有貨幣價值。有關更多資訊,請查看{faqLink} 上的常見問題。", - "transfer.form.back": "返回", "transfer.form.errors.invalidRecoveryPhrase": "恢復短語不正確", "transfer.form.instructions.step1.text": "恢復你的餘額大約需要1分鐘。在下一步, 您將看到一筆將從Daedalus移動您所有資金到Yoroi的交易。請仔細閱讀交易細節。您需要在卡Cardano網絡上支付標準交易費用才能進行交易。", - "transfer.form.instructions.title.label": "指示", - "transfer.form.next": "下一個", + "transfer.form.masterkey.input.hint": "!!!Enter master key", + "transfer.form.masterkey.input.label": "!!!Master key", + "transfer.form.masterkey.requirement": "!!!Note: master keys are 192 characters and hexadecimal-encoded", "transfer.form.recovery.phrase.input.hint": "輸入恢復短語", "transfer.form.recovery.phrase.input.label": "恢復短語", "transfer.form.recovery.phrase.input.noResults": "無結果", @@ -153,7 +156,9 @@ "transfer.summary.transactionFee.label": "交易費用", "transfer.summary.transferButton.label": "轉移資金", "wallet.add.dialog.create.description": "建立新錢包", + "wallet.add.dialog.createLedgerWalletNotificationMessage": "!!!Ledger Connect is currently in progress. Until it completes, it is not possible to restore or import new wallets.", "wallet.add.dialog.createTrezorWalletNotificationMessage": "Trezor Connect 當前正在進行中。在完成前, 無法還原或導入新的錢包。", + "wallet.add.dialog.ledger.description": "!!!Connect to Ledger", "wallet.add.dialog.restore.description": "從備份還原錢包", "wallet.add.dialog.restoreNotificationMessage": "目前正在修復錢包中。在完成之前,無法恢復或導入新錢包。", "wallet.add.dialog.title.label": "新增錢包", @@ -169,6 +174,49 @@ "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.device": "我明白我的密鑰是安全地儲存在此設備上,而不是公司的伺服器上。", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "我瞭解,若此應用程式被移至另一個裝置,或者被刪除,我的資金\n 僅可使用備份片語復原,該備份片語已在某個安全之處寫下。", "wallet.backup.recovery.phrase.entry.dialog.verification.instructions": "按正確順序點選每個單詞,驗證您的復原片語", + "wallet.connect.hw.dialog.connect.button.label": "連接", + "wallet.connect.hw.dialog.save.button.label": "儲存", + "wallet.connect.hw.dialog.step.about.introText.line.1": "硬件錢包是一種小型USB設備,可為您的錢包增加額外的安全性。", + "wallet.connect.hw.dialog.step.about.introText.line.2": "它更安全, 因為您的私密金鑰永遠不會離開硬件錢包。", + "wallet.connect.hw.dialog.step.about.introText.line.3": "即使您的電腦因惡意軟體、網路入侵等, 這也會保護您的資金。", + "wallet.connect.hw.dialog.step.about.label": "關於", + "wallet.connect.hw.dialog.step.about.prerequisite.4": "您的電腦在整個過程中, 必須保持與互聯網的連接。", + "wallet.connect.hw.dialog.step.about.prerequisite.header": "先決條件", + "wallet.connect.hw.dialog.step.connect.introText.line.3": "這個過程將與Yoroi共享Cardano公開公鑰。", + "wallet.connect.hw.dialog.step.connect.label": "連接", + "wallet.connect.hw.dialog.step.save.error.101": "未能保存。請檢查您的互聯網連接, 然後重試。", + "wallet.connect.hw.dialog.step.save.label": "保存", + "wallet.connect.hw.dialog.step.save.walletName.hint": "輸入錢包名稱", + "wallet.connect.hw.dialog.step.save.walletName.label": "錢包名稱", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor": "!!!https://yoroi-wallet.com/", + "wallet.connect.ledger.dialog.common.step.link.helpYoroiWithTrezor.text": "!!!Click here to know more about how to use Yoroi with Trezor.", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part1": "!!!Only Supports", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link": "!!!https://www.ledger.com/products/ledger-nano-s", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part2.link.text": "!!!Ledger Nano S", + "wallet.connect.ledger.dialog.step.about.prerequisite.1.part3": "!!! model.", + "wallet.connect.ledger.dialog.step.about.prerequisite.2.part2": "!!!Cardano ADA app must be installed on the Ledger device.", + "wallet.connect.ledger.dialog.step.about.prerequisite.3": "!!!The Trezor device screen must be unlocked.", + "wallet.connect.ledger.dialog.step.about.prerequisite.5": "!!!Trezor device must remain connected to the computer throughout the process", + "wallet.connect.ledger.dialog.step.connect.introText.line.1": "!!!After connecting your Trezor device to the computer press the Connect button.", + "wallet.connect.ledger.dialog.step.connect.introText.line.2": "!!!A new tab will appear, please follow the instructions in the new tab.", + "wallet.connect.ledger.dialog.step.save.walletName.info": "!!!We have fetched Trezor device’s name for you; you can use as it is or assign a different name.", + "wallet.connect.ledger.dialog.title.label": "!!!Connect to Ledger Hardware Wallet", + "wallet.connect.ledger.error.101": "!!!Failed to connect. Please check your ledger device and retry.", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", + "wallet.connect.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "點擊此處了解有關使用Yoroi和Trezor的更多情報", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part1": "僅支援 ", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/be58549fd9fe53df237a7318f754b5ec23975425/ChangeLog#L12", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T 2.0.8版本", + "wallet.connect.trezor.dialog.step.about.prerequisite.1.part3": " 或更高版本。", + "wallet.connect.trezor.dialog.step.about.prerequisite.2": "Trezor 設備必須預先初始化。", + "wallet.connect.trezor.dialog.step.about.prerequisite.3": "Trezor設備螢幕必須解鎖。", + "wallet.connect.trezor.dialog.step.about.prerequisite.5": "在整個過程中, Trezor 設備必須保持與電腦的連接。", + "wallet.connect.trezor.dialog.step.connect.introText.line.1": "將 Trezor 設備連接到電腦後, 用Trezor按鈕按發送。", + "wallet.connect.trezor.dialog.step.connect.introText.line.2": "將出現一個新標籤。請按照標籤中的說明進行操作。", + "wallet.connect.trezor.dialog.step.save.walletName.info": "我們已經為您取了Trezor設備的名稱。您可以按原樣使用, 也可以指定不同的名稱。", + "wallet.connect.trezor.dialog.title.label": "與Trezor硬件錢包連接", + "wallet.connect.trezor.error.101": "無法連接trezor.io。請檢查您的互聯網連接, 然後重試。", + "wallet.connect.trezor.error.103": "取消。請重試。", "wallet.create.dialog.create.personal.wallet.button.label": "建立個人錢包", "wallet.create.dialog.name.label": "錢包名稱", "wallet.create.dialog.passwordFieldPlaceholder": "密碼", @@ -176,11 +224,13 @@ "wallet.create.dialog.title": "建立新錢包", "wallet.create.dialog.walletNameHint": "諸如:購物錢包", "wallet.create.dialog.walletPasswordLabel": "錢包密碼", + "wallet.footer.buyLedgerHardwareWallet.text": "!!!Buy a Ledger hardware wallet.", "wallet.footer.buyTrezorHardwareWallet.text": "購買Trezor硬件錢包", "wallet.footer.howToConnectTrezor.text": "如何連接Trezor", "wallet.footer.howToCreateWallet.text": "如何創建錢包", "wallet.footer.howToRestoreWallet.text": "如何恢復錢包", "wallet.footer.whatIsHardwareWallet.text": "什麼是硬件錢包", + "wallet.hw.common.error.101": "使用者未授予必要的許可權。請重試。", "wallet.navigation.receive": "接收", "wallet.navigation.send": "傳送", "wallet.navigation.transactions": "交易", @@ -194,6 +244,7 @@ "wallet.receive.page.walletReceiveInstructions": "分享此錢包地址,以接收付款。為保護您的隱私,一旦使用,就會自動產生新地址。", "wallet.recovery.phrase.show.entry.dialog.button.labelClear": "清除", "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm": "確認", + "wallet.recovery.phrase.show.entry.dialog.button.labelRemoveLast": "!!!Remove last", "wallet.redeem.choices.tab.title.forceVended": "強行執行", "wallet.redeem.choices.tab.title.paperVended": "Paper執行", "wallet.redeem.choices.tab.title.recoveryForceVended": "強行恢復", @@ -249,7 +300,6 @@ "wallet.restore.dialog.walletPasswordLabel": "錢包密碼", "wallet.send.confirmationDialog.addressToLabel": "收件人", "wallet.send.confirmationDialog.amountLabel": "金額", - "wallet.send.confirmationDialog.back": "返回", "wallet.send.confirmationDialog.feesLabel": "費用", "wallet.send.confirmationDialog.submit": "傳送", "wallet.send.confirmationDialog.title": "確認交易", @@ -264,13 +314,16 @@ "wallet.send.form.errors.invalidAddress": "請輸入有效地址。", "wallet.send.form.errors.invalidAmount": "請輸入有效金額。", "wallet.send.form.errors.invalidTitle": "請輸入一個由至少 3 個字元組成的標題。", - "wallet.send.form.next": "下一個", "wallet.send.form.receiver.hint": "錢包地址", "wallet.send.form.receiver.label": "收件人", "wallet.send.form.sendingIsDisabled": "由於目前有待處理狀態的交易,新交易無法發送。", "wallet.send.form.title.hint": "例如:支付給 Frank 的款項", "wallet.send.form.title.label": "標題", "wallet.send.form.transactionFeeError": "費用所需的 Ada 不足。嘗試傳送較小的金額。", + "wallet.send.ledger.confirmationDialog.info.line.1": "!!!After connecting your Trezor device to your computer, press the Send using Trezor button.", + "wallet.send.ledger.confirmationDialog.info.line.2": "!!!A new tab will appear. Please follow the instructions in the new tab.", + "wallet.send.ledger.confirmationDialog.submit": "!!!Send using Trezor", + "wallet.send.ledger.error.101": "!!!Signing cancelled on Ledger device. Please retry.", "wallet.send.trezor.confirmationDialog.info.line.1": "將 Trezor 設備連接到電腦後, 用Trezor按鈕按發送。", "wallet.send.trezor.confirmationDialog.info.line.2": "將出現一個新標籤。請按照標籤中的說明進行操作。", "wallet.send.trezor.confirmationDialog.submit": "使用 Trezor 發送", @@ -290,6 +343,7 @@ "wallet.settings.passwordLastUpdated": "上次更新日期 {lastUpdated}", "wallet.summary.no.transaction": "未找到任何交易", "wallet.summary.no.transactions": "無最近交易", + "wallet.summary.page.ledgerNanoSWalletIntegratedNotificationMessage": "!!!You have successfully integrated with your Ledger Nano S device", "wallet.summary.page.pendingIncomingConfirmationLabel": "傳入擱置確認", "wallet.summary.page.pendingOutgoingConfirmationLabel": "傳出擱置確認", "wallet.summary.page.showMoreTransactionsButtonLabel": "顯示更多交易(已編輯)", @@ -323,36 +377,5 @@ "wallet.transaction.type": "{currency} 交易", "wallet.transaction.type.exchange": "兌換", "wallet.transaction.type.intrawallet": "{currency} 錢包間交易", - "wallet.transaction.type.multiparty": "{currency} 多方交易", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor": "https://youtu.be/Dp0wXwtToX0", - "wallet.trezor.dialog.common.step.link.helpYoroiWithTrezor.text": "點擊此處了解有關使用Yoroi和Trezor的更多情報", - "wallet.trezor.dialog.connect.button.label": "連接", - "wallet.trezor.dialog.next.button.label": "下一個", - "wallet.trezor.dialog.save.button.label": "儲存", - "wallet.trezor.dialog.step.about.introText.line.1": "硬件錢包是一種小型USB設備,可為您的錢包增加額外的安全性。", - "wallet.trezor.dialog.step.about.introText.line.2": "它更安全, 因為您的私密金鑰永遠不會離開硬件錢包。", - "wallet.trezor.dialog.step.about.introText.line.3": "即使您的電腦因惡意軟體、網路入侵等, 這也會保護您的資金。", - "wallet.trezor.dialog.step.about.label": "關於", - "wallet.trezor.dialog.step.about.prerequisite.1.part1": "僅支援 ", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link": "https://github.com/trezor/trezor-core/blob/master/ChangeLog", - "wallet.trezor.dialog.step.about.prerequisite.1.part2.link.text": "Trezor Model T 2.1.0版本", - "wallet.trezor.dialog.step.about.prerequisite.1.part3": " 或更高版本。", - "wallet.trezor.dialog.step.about.prerequisite.2": "Trezor 設備必須預先初始化。", - "wallet.trezor.dialog.step.about.prerequisite.3": "Trezor設備螢幕必須解鎖。", - "wallet.trezor.dialog.step.about.prerequisite.4": "您的電腦在整個過程中, 必須保持與互聯網的連接。", - "wallet.trezor.dialog.step.about.prerequisite.5": "在整個過程中, Trezor 設備必須保持與電腦的連接。", - "wallet.trezor.dialog.step.about.prerequisite.header": "先決條件", - "wallet.trezor.dialog.step.connect.introText.line.1": "將 Trezor 設備連接到電腦後, 用Trezor按鈕按發送。", - "wallet.trezor.dialog.step.connect.introText.line.2": "將出現一個新標籤。請按照標籤中的說明進行操作。", - "wallet.trezor.dialog.step.connect.introText.line.3": "這個過程將與Yoroi共享Cardano公開公鑰。", - "wallet.trezor.dialog.step.connect.label": "連接", - "wallet.trezor.dialog.step.save.error.101": "未能保存。請檢查您的互聯網連接, 然後重試。", - "wallet.trezor.dialog.step.save.label": "保存", - "wallet.trezor.dialog.step.save.walletName.hint": "輸入錢包名稱", - "wallet.trezor.dialog.step.save.walletName.info": "我們已經為您取了Trezor設備的名稱。您可以按原樣使用, 也可以指定不同的名稱。", - "wallet.trezor.dialog.step.save.walletName.label": "錢包名稱", - "wallet.trezor.dialog.title.label": "與Trezor硬件錢包連接", - "wallet.trezor.error.101": "無法連接trezor.io。請檢查您的互聯網連接, 然後重試。", - "wallet.trezor.error.102": "使用者未授予必要的許可權。請重試。", - "wallet.trezor.error.103": "取消。請重試。" + "wallet.transaction.type.multiparty": "{currency} 多方交易" } \ No newline at end of file diff --git a/app/stores/ada/AdaWalletsStore.js b/app/stores/ada/AdaWalletsStore.js index c3e2b6b089..22e4c6ee62 100644 --- a/app/stores/ada/AdaWalletsStore.js +++ b/app/stores/ada/AdaWalletsStore.js @@ -172,11 +172,20 @@ export default class AdaWalletsStore extends WalletStore { }; // =================== NOTIFICATION ==================== // + showLedgerNanoSWalletIntegratedNotification = (): void => { + const notification: Notification = { + id: globalMessages.ledgerNanoSWalletIntegratedNotificationMessage.id, + message: globalMessages.ledgerNanoSWalletIntegratedNotificationMessage, + duration: config.wallets.WALLET_CREATED_NOTIFICATION_DURATION, + }; + this.actions.notifications.open.trigger(notification); + } + showTrezorTWalletIntegratedNotification = (): void => { const notification: Notification = { id: globalMessages.trezorTWalletIntegratedNotificationMessage.id, message: globalMessages.trezorTWalletIntegratedNotificationMessage, - duration: config.wallets.TREZOR_WALLET_INTEGRATED_NOTIFICATION_DURATION, + duration: config.wallets.WALLET_CREATED_NOTIFICATION_DURATION, }; this.actions.notifications.open.trigger(notification); } diff --git a/app/stores/ada/DaedalusTransferStore.js b/app/stores/ada/DaedalusTransferStore.js index baa4d8fe93..b25b57dc62 100644 --- a/app/stores/ada/DaedalusTransferStore.js +++ b/app/stores/ada/DaedalusTransferStore.js @@ -24,6 +24,16 @@ import { } from '../../api/ada/daedalusTransfer'; import environment from '../../environment'; import type { SignedResponse } from '../../api/ada/lib/yoroi-backend-api'; +import { + getCryptoDaedalusWalletFromMnemonics, + getCryptoDaedalusWalletFromMasterKey +} from '../../api/ada/lib/cardanoCrypto/cryptoWallet'; +import { + getResultOrFail +} from '../../api/ada/lib/cardanoCrypto/cryptoUtils'; +import { + RandomAddressChecker, +} from 'rust-cardano-crypto'; declare var CONFIG: ConfigType; const websocketUrl = CONFIG.network.websocketUrl; @@ -48,7 +58,9 @@ export default class DaedalusTransferStore extends Store { const actions = this.actions.ada.daedalusTransfer; actions.startTransferFunds.listen(this._startTransferFunds); actions.startTransferPaperFunds.listen(this._startTransferPaperFunds); - actions.setupTransferFunds.listen(this._setupTransferFunds); + actions.startTransferMasterKey.listen(this._startTransferMasterKey); + actions.setupTransferFundsWithMnemonic.listen(this._setupTransferFundsWithMnemonic); + actions.setupTransferFundsWithMasterKey.listen(this._setupTransferFundsWithMasterKey); actions.backToUninitialized.listen(this._backToUninitialized); actions.transferFunds.listen(this._transferFunds); actions.cancelTransferFunds.listen(this._reset); @@ -67,6 +79,10 @@ export default class DaedalusTransferStore extends Store { this._updateStatus('gettingPaperMnemonics'); } + _startTransferMasterKey = (): void => { + this._updateStatus('gettingMasterKey'); + } + /** @Attention: You should check wallets state outside of the runInAction, because this method run as a reaction. @@ -85,19 +101,14 @@ export default class DaedalusTransferStore extends Store { } } - /** Call the backend service to fetch all the UTXO then find which belong to the Daedalus wallet. + /** + * Call the backend service to fetch all the UTXO then find which belong to the Daedalus wallet. * Finally, generate the tx to transfer the wallet to Yoroi */ - _setupTransferFunds = (payload: { recoveryPhrase: string }): void => { - let { recoveryPhrase: secretWords } = payload; - if (secretWords.split(' ').length === 27) { - const [newSecretWords, unscrambledLen] = - this.api.ada.unscramblePaperMnemonic(secretWords, 27); - if (!newSecretWords || !unscrambledLen) { - throw new Error('Failed to unscramble paper mnemonics!'); - } - secretWords = newSecretWords; - } + _setupTransferWebSocket = ( + checker: CryptoAddressChecker, + wallet: CryptoDaedalusWallet, + ): void => { this._updateStatus('restoringAddresses'); this.ws = new WebSocket(websocketUrl); this.ws.addEventListener('open', () => { @@ -119,22 +130,16 @@ export default class DaedalusTransferStore extends Store { Logger.info(`[ws::message] on: ${data.msg}`); if (data.msg === MSG_TYPE_RESTORE) { this._updateStatus('checkingAddresses'); - const addressesWithFunds = getAddressesWithFunds({ - secretWords, - fullUtxo: data.addresses - }); + const addressesWithFunds = getAddressesWithFunds({ checker, fullUtxo: data.addresses }); this._updateStatus('generatingTx'); - const transferTx = await generateTransferTx({ - secretWords, - addressesWithFunds - }); + const transferTx = await generateTransferTx({ wallet, addressesWithFunds }); runInAction(() => { this.transferTx = transferTx; }); this._updateStatus('readyToTransfer'); } } catch (error) { - Logger.error(`DaedalusTransferStore::setupTransferFunds ${stringifyError(error)}`); + Logger.error(`DaedalusTransferStore::_setupTransferWebSocket ${stringifyError(error)}`); runInAction(() => { this.status = 'error'; this.error = localizedError(error); @@ -159,6 +164,36 @@ export default class DaedalusTransferStore extends Store { ); } }); + }; + + _setupTransferFundsWithMnemonic = (payload: { recoveryPhrase: string }): void => { + let { recoveryPhrase: secretWords } = payload; + if (secretWords.split(' ').length === 27) { + const [newSecretWords, unscrambledLen] = + this.api.ada.unscramblePaperMnemonic(secretWords, 27); + if (!newSecretWords || !unscrambledLen) { + throw new Error('Failed to unscramble paper mnemonics!'); + } + secretWords = newSecretWords; + } + + this._setupTransferWebSocket( + getResultOrFail( + RandomAddressChecker.newCheckerFromMnemonics(secretWords) + ), + getCryptoDaedalusWalletFromMnemonics(secretWords) + ); + } + + _setupTransferFundsWithMasterKey = (payload: { masterKey: string }): void => { + const { masterKey: key } = payload; + + this._setupTransferWebSocket( + getResultOrFail( + RandomAddressChecker.newChecker(key) + ), + getCryptoDaedalusWalletFromMasterKey(key) + ); } _backToUninitialized = (): void => { diff --git a/app/stores/ada/LedgerConnectStore.js b/app/stores/ada/LedgerConnectStore.js new file mode 100644 index 0000000000..d66d67ffb2 --- /dev/null +++ b/app/stores/ada/LedgerConnectStore.js @@ -0,0 +1,361 @@ +// @flow +// Handles Connect to Ledger Hardware Wallet dialog + +import { observable, action } from 'mobx'; + +import { + LedgerBridge, + BIP44_HARDENED_CARDANO_FIRST_ACCOUNT_SUB_PATH as CARDANO_FIRST_ACCOUNT_SUB_PATH +} from 'yoroi-extension-ledger-bridge'; +import type { + GetVersionResponse, + GetExtendedPublicKeyResponse, +} from '@cardano-foundation/ledgerjs-hw-app-cardano'; + +import Config from '../../config'; +import environment from '../../environment'; + +import Store from '../base/Store'; +import Wallet from '../../domain/Wallet'; +import LocalizedRequest from '../lib/LocalizedRequest'; + +import type { + CreateHardwareWalletRequest, + CreateHardwareWalletResponse, +} from '../../api/common'; + +// This is actually just an interface +import { + HWConnectStoreTypes, + StepState, + ProgressStep, + ProgressInfo, + HWDeviceInfo +} from '../../types/HWConnectStoreTypes'; + +import { + prepareLedgerBridger, + disposeLedgerBridgeIFrame +} from '../../utils/iframeHandler'; + +import globalMessages from '../../i18n/global-messages'; +import LocalizableError, { UnexpectedError } from '../../i18n/LocalizableError'; +import { CheckAdressesInUseApiError } from '../../api/ada/errors'; + +import { + Logger, + stringifyData, + stringifyError +} from '../../utils/logging'; + +/** TODO: TrezorConnectStore and LedgerConnectStore has many common methods + * try to make a common base class */ +export default class LedgerConnectStore extends Store implements HWConnectStoreTypes { + + // =================== VIEW RELATED =================== // + @observable progressInfo: ProgressInfo; + error: ?LocalizableError; + hwDeviceInfo: ?HWDeviceInfo; + ledgerBridge: ?LedgerBridge; + + get defaultWalletName(): string { + // Ledger doesn’t provide any device name so using hard-coded name + return Config.wallets.hardwareWallet.ledgerNanoS.DEFAULT_WALLET_NAME; + } + + get isActionProcessing(): boolean { + return this.progressInfo.stepState === StepState.PROCESS; + } + // =================== VIEW RELATED =================== // + + // =================== API RELATED =================== // + createHWRequest: LocalizedRequest = + new LocalizedRequest(this.api.ada.createHardwareWallet); + + /** While ledger wallet creation is taking place, we need to block users from starting a + * ledger wallet creation on a seperate wallet and explain to them why the action is blocked */ + @observable isCreateHWActive: boolean = false; + // =================== API RELATED =================== // + + setup() { + this._reset(); + const ledgerConnectAction = this.actions.ada.ledgerConnect; + ledgerConnectAction.init.listen(this._init); + ledgerConnectAction.cancel.listen(this._cancel); + ledgerConnectAction.submitAbout.listen(this._submitAbout); + ledgerConnectAction.goBackToAbout.listen(this._goBackToAbout); + ledgerConnectAction.submitConnect.listen(this._submitConnect); + ledgerConnectAction.submitSave.listen(this._submitSave); + } + + /** setup() is called when stores are being created + * _init() is called when connect dailog is about to show */ + _init = (): void => { + Logger.debug('LedgerConnectStore::_init called'); + if (this.ledgerBridge == null) { + Logger.debug('LedgerConnectStore::_init new LedgerBridge created'); + this.ledgerBridge = new LedgerBridge(); + } + } + + @action _cancel = (): void => { + this.teardown(); + }; + + teardown(): void { + this._reset(); + super.teardown(); + } + + @action _reset = (): void => { + disposeLedgerBridgeIFrame(); + this.ledgerBridge = undefined; + + this.progressInfo = { + currentStep: ProgressStep.ABOUT, + stepState: StepState.LOAD, + }; + + this.error = undefined; + this.hwDeviceInfo = undefined; + }; + + // =================== ABOUT =================== // + /** ABOUT dialog submit(Next button) */ + @action _submitAbout = (): void => { + this.error = undefined; + this.progressInfo.currentStep = ProgressStep.CONNECT; + this.progressInfo.stepState = StepState.LOAD; + }; + // =================== ABOUT =================== // + + // =================== CONNECT =================== // + /** CONNECT dialog goBack button */ + @action _goBackToAbout = (): void => { + this.error = undefined; + this.progressInfo.currentStep = ProgressStep.ABOUT; + this.progressInfo.stepState = StepState.LOAD; + }; + + /** CONNECT dialog submit (Connect button) */ + @action _submitConnect = (): void => { + this.error = undefined; + this.progressInfo.currentStep = ProgressStep.CONNECT; + this.progressInfo.stepState = StepState.PROCESS; + this._checkAndStoreHWDeviceInfo(); + }; + + _checkAndStoreHWDeviceInfo = async (): Promise => { + try { + if (this.ledgerBridge) { + // Since this.ledgerBridge is undefinable flow need to know that it's a LedgerBridge + const ledgerBridge: LedgerBridge = this.ledgerBridge; + await prepareLedgerBridger(ledgerBridge); + + const versionResp: GetVersionResponse = await ledgerBridge.getVersion(); + + Logger.debug(stringifyData(versionResp)); + // https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#examples + Logger.debug(stringifyData(CARDANO_FIRST_ACCOUNT_SUB_PATH)); + + // get Cardano's first account's + // i.e hdPath = [2147483692, 2147485463, 2147483648] + const extendedPublicKeyResp: GetExtendedPublicKeyResponse + = await ledgerBridge.getExtendedPublicKey(CARDANO_FIRST_ACCOUNT_SUB_PATH); + + this.hwDeviceInfo = this._normalizeHWResponse(versionResp, extendedPublicKeyResp); + + this._goToSaveLoad(); + Logger.info('Ledger device OK'); + } else { + throw new Error(`LedgerBridge Error: LedgerBridge is undefined`); + } + } catch (error) { + this._handleConnectError(error); + } + }; + + _normalizeHWResponse = ( + versionResp: GetVersionResponse, + extendedPublicKeyResp: GetExtendedPublicKeyResponse + ): HWDeviceInfo => { + this._validateHWResponse(versionResp, extendedPublicKeyResp); + + return { + publicMasterKey: extendedPublicKeyResp.publicKeyHex + extendedPublicKeyResp.chainCodeHex, + hwFeatures: { + vendor: Config.wallets.hardwareWallet.ledgerNanoS.VENDOR, + model: Config.wallets.hardwareWallet.ledgerNanoS.MODEL, + label: '', + deviceId: '', + language: '', + majorVersion: parseInt(versionResp.major, 10), + minorVersion: parseInt(versionResp.minor, 10), + patchVersion: parseInt(versionResp.patch, 10), + } + }; + } + + _validateHWResponse = ( + versionResp: GetVersionResponse, + extendedPublicKeyResp: GetExtendedPublicKeyResponse + ): boolean => { + if (versionResp == null) { + throw new Error('Ledger device version response is undefined'); + } + + if (extendedPublicKeyResp == null) { + throw new Error('Ledger device extended public key response is undefined'); + } + + return true; + }; + + _handleConnectError = (error: any): void => { + Logger.error(`LedgerConnectStore::_checkAndStoreHWDeviceInfo ${stringifyError(error)}`); + + this.hwDeviceInfo = undefined; + this.error = this._convertToLocalizableError(error); + + this._goToConnectError(); + }; + + /** Converts error(from API or Ledger API) to LocalizableError */ + _convertToLocalizableError = (error: any): LocalizableError => { + let localizableError: ?LocalizableError = null; + + if (error instanceof LocalizableError) { + // It means some API Error has been thrown + localizableError = error; + } else if (error && error.message) { + // Ledger device related error happend, convert then to LocalizableError + switch (error.message) { + case 'TransportError: Failed to sign with Ledger device: U2F TIMEOUT': + localizableError = new LocalizableError(globalMessages.ledgerError101); + break; + case 'TransportStatusError: Ledger device: Action rejected by user': + localizableError = new LocalizableError(globalMessages.hwError101); + break; + default: + /** we are not able to figure out why Error is thrown + * make it, Something unexpected happened */ + Logger.error(`LedgerConnectStore::_convertToLocalizableError::error: ${error.message}`); + localizableError = new UnexpectedError(); + break; + } + } + + if (!localizableError) { + /** we are not able to figure out why Error is thrown + * make it, Something unexpected happened */ + localizableError = new UnexpectedError(); + } + + return localizableError; + } + + @action _goToConnectError = (): void => { + this.progressInfo.currentStep = ProgressStep.CONNECT; + this.progressInfo.stepState = StepState.ERROR; + }; + // =================== CONNECT =================== // + + // =================== SAVE =================== // + @action _goToSaveLoad = (): void => { + this.error = null; + this.progressInfo.currentStep = ProgressStep.SAVE; + this.progressInfo.stepState = StepState.LOAD; + }; + + /** SAVE dialog submit (Save button) */ + @action _submitSave = (walletName: string): void => { + this.error = null; + this.progressInfo.currentStep = ProgressStep.SAVE; + this.progressInfo.stepState = StepState.PROCESS; + this._saveHW(walletName); + }; + + /** creates new wallet and loads it */ + _saveHW = async (walletName: string): Promise => { + try { + Logger.debug('LedgerConnectStore::_saveHW:: called'); + this._setIsCreateHWActive(true); + this.createHWRequest.reset(); + + const reqParams = this._prepareCreateHWReqParams(walletName); + const ledgerWallet: CreateHardwareWalletResponse = + await this.createHWRequest.execute(reqParams).promise; + + await this._onSaveSucess(ledgerWallet); + } catch (error) { + Logger.error(`LedgerConnectStore::_saveHW::error ${stringifyError(error)}`); + + if (error instanceof CheckAdressesInUseApiError) { + // redirecting CheckAdressesInUseApiError -> hwConnectDialogSaveError101 + // because for user hwConnectDialogSaveError101 is more meaningful in this context + this.error = new LocalizableError(globalMessages.hwConnectDialogSaveError101); + } else if (error instanceof LocalizableError) { + this.error = error; + } else { + // some unknow error + this.error = new UnexpectedError(); + } + this._goToSaveError(); + } finally { + this.createHWRequest.reset(); + this._setIsCreateHWActive(false); + } + }; + + _prepareCreateHWReqParams = (walletName: string): CreateHardwareWalletRequest => { + if (this.hwDeviceInfo == null + || this.hwDeviceInfo.publicMasterKey == null + || this.hwDeviceInfo.hwFeatures == null) { + throw new Error('Ledger device hardware info not valid'); + } + + return { + walletName, + publicMasterKey: this.hwDeviceInfo.publicMasterKey, + hwFeatures: this.hwDeviceInfo.hwFeatures + }; + }; + + async _onSaveSucess(ledgerWallet: Wallet): Promise { + // close the active dialog + Logger.debug('LedgerConnectStore::_onSaveSucess success, closing dialog'); + this.actions.dialogs.closeActiveDialog.trigger(); + + const { wallets } = this.stores.substores[environment.API]; + await wallets._patchWalletRequestWithNewWallet(ledgerWallet); + + // goto the wallet transactions page + Logger.debug('LedgerConnectStore::_onSaveSucess setting new walles as active wallet'); + wallets.goToWalletRoute(ledgerWallet.id); + + // fetch its data + Logger.debug('LedgerConnectStore::_onSaveSucess loading wallet data'); + wallets.refreshWalletsData(); + + // Load the Yoroi with Ledger Icon + this.stores.topbar.initCategories(); + + // show success notification + wallets.showLedgerNanoSWalletIntegratedNotification(); + + this.teardown(); + Logger.info('SUCCESS: Ledger Connected Wallet created and loaded'); + } + + @action _goToSaveError = (): void => { + this.progressInfo.currentStep = ProgressStep.SAVE; + this.progressInfo.stepState = StepState.ERROR; + }; + // =================== SAVE =================== // + + // =================== API =================== // + @action _setIsCreateHWActive = (active: boolean): void => { + this.isCreateHWActive = active; + }; + // =================== API =================== // +} diff --git a/app/stores/ada/LedgerSendStore.js b/app/stores/ada/LedgerSendStore.js new file mode 100644 index 0000000000..3354fa8d68 --- /dev/null +++ b/app/stores/ada/LedgerSendStore.js @@ -0,0 +1,238 @@ +// @flow +import { action, observable } from 'mobx'; +import { defineMessages } from 'react-intl'; + +import { + LedgerBridge, +} from 'yoroi-extension-ledger-bridge'; +import type { + SignTransactionResponse as LedgerSignTxResponse +} from '@cardano-foundation/ledgerjs-hw-app-cardano'; + +import type { LedgerSignTxPayload } from '../../domain/HWSignTx'; + +import Store from '../base/Store'; +import environment from '../../environment'; +import LocalizedRequest from '../lib/LocalizedRequest'; + +import LocalizableError, { UnexpectedError } from '../../i18n/LocalizableError'; +import globalMessages from '../../i18n/global-messages'; + +import type { + CreateLedgerSignTxDataRequest, + CreateLedgerSignTxDataResponse, + PrepareAndBroadcastLedgerSignedTxRequest, +} from '../../api/ada'; +import type { PrepareAndBroadcastLedgerSignedTxResponse } from '../../api/common'; + +import { + Logger, + stringifyError, + stringifyData, +} from '../../utils/logging'; + +import { + prepareLedgerBridger, + disposeLedgerBridgeIFrame +} from '../../utils/iframeHandler'; + +const messages = defineMessages({ + signTxError101: { + id: 'wallet.send.ledger.error.101', + defaultMessage: '!!!Signing cancelled on Ledger device. Please retry or reconnect device.', + description: ' on the Ledger send ADA confirmation dialog.' + }, +}); + +/** Note: Handles Ledger Signing */ +export default class LedgerSendStore extends Store { + // =================== VIEW RELATED =================== // + @observable isActionProcessing: boolean = false; + @observable error: ?LocalizableError; + ledgerBridge: ?LedgerBridge; + // =================== VIEW RELATED =================== // + + // =================== API RELATED =================== // + createLedgerSignTxDataRequest: LocalizedRequest = + new LocalizedRequest(this.api.ada.createLedgerSignTxData); + + broadcastLedgerSignedTxRequest: LocalizedRequest = + new LocalizedRequest(this.api.ada.prepareAndBroadcastLedgerSignedTx); + // =================== API RELATED =================== // + + setup() { + const ledgerSendAction = this.actions.ada.ledgerSend; + ledgerSendAction.init.listen(this._init); + ledgerSendAction.sendUsingLedger.listen(this._send); + ledgerSendAction.cancel.listen(this._cancel); + } + + /** setup() is called when stores are being created + * _init() is called when Confirmation dailog is about to show */ + _init = (): void => { + Logger.debug('LedgerSendStore::_init called'); + if (this.ledgerBridge == null) { + Logger.debug('LedgerSendStore::_init new LedgerBridge created'); + this.ledgerBridge = new LedgerBridge(); + } + } + + _reset() { + disposeLedgerBridgeIFrame(); + this.ledgerBridge = undefined; + + this._setActionProcessing(false); + this._setError(null); + } + + _preSendValidation = (): void => { + if (this.isActionProcessing) { + // this Error will be converted to LocalizableError() + throw new Error('Can\'t send another transaction if one transaction is in progress.'); + } + + const { wallets, addresses } = this.stores.substores[environment.API]; + const activeWallet = wallets.active; + if (!activeWallet) { + // this Error will be converted to LocalizableError() + throw new Error('Active wallet required before sending.'); + } + + const accountId = addresses._getAccountIdByWalletId(activeWallet.id); + if (!accountId) { + // this Error will be converted to LocalizableError() + throw new Error('Active account required before sending.'); + } + } + + /** Generates a payload with Ledger format and tries Send ADA using Ledger signing */ + _send = async (params: CreateLedgerSignTxDataRequest): Promise => { + try { + Logger.debug('LedgerSendStore::_send::called: ' + stringifyData(params)); + + this.createLedgerSignTxDataRequest.reset(); + this.broadcastLedgerSignedTxRequest.reset(); + + this._preSendValidation(); + + this._setError(null); + this._setActionProcessing(true); + + if (this.ledgerBridge) { + // Since this.ledgerBridge is undefinable flow need to know that it's a LedgerBridge + const ledgerBridge: LedgerBridge = this.ledgerBridge; + + const ledgerSignTxDataResp: CreateLedgerSignTxDataResponse = + await this.createLedgerSignTxDataRequest.execute(params).promise; + + await prepareLedgerBridger(ledgerBridge); + + const unsignedTx: LedgerSignTxPayload = { + inputs: ledgerSignTxDataResp.ledgerSignTxPayload.inputs, + outputs: ledgerSignTxDataResp.ledgerSignTxPayload.outputs, + }; + + const ledgerSignTxResp: LedgerSignTxResponse = + await ledgerBridge.signTransaction(unsignedTx.inputs, unsignedTx.outputs); + + await this._prepareAndBroadcastSignedTx( + ledgerSignTxResp, + ledgerSignTxDataResp, + unsignedTx + ); + + } else { + throw new Error(`LedgerBridge Error: LedgerBridge is undefined`); + } + } catch (error) { + Logger.error('LedgerSendStore::_send::error: ' + stringifyError(error)); + this._setError(this._convertToLocalizableError(error)); + } finally { + this.createLedgerSignTxDataRequest.reset(); + this.broadcastLedgerSignedTxRequest.reset(); + this._setActionProcessing(false); + } + }; + + _prepareAndBroadcastSignedTx = async ( + ledgerSignTxResp: LedgerSignTxResponse, + createLedgerSignTxDataResp: CreateLedgerSignTxDataResponse, + unsignedTx: LedgerSignTxPayload, + ): Promise => { + + const reqParams: PrepareAndBroadcastLedgerSignedTxRequest = { + ledgerSignTxResp, + changeAdaAddr: createLedgerSignTxDataResp.changeAddress, + unsignedTx, + txExt: createLedgerSignTxDataResp.txExt + }; + + try { + await this.broadcastLedgerSignedTxRequest.execute(reqParams).promise; + } catch (error) { + Logger.error('LedgerSendStore::_prepareAndBroadcastSignedTx error: ' + stringifyError(error)); + } + + this.actions.dialogs.closeActiveDialog.trigger(); + const { wallets } = this.stores.substores[environment.API]; + wallets.refreshWalletsData(); + + const activeWallet = wallets.active; + if (activeWallet) { + // go to transaction screen + wallets.goToWalletRoute(activeWallet.id); + } + + this._reset(); + Logger.info('SUCCESS: ADA sent using Ledger SignTx'); + } + + /** Converts error(from API or Ledger API) to LocalizableError */ + _convertToLocalizableError = (error: any): LocalizableError => { + let localizableError: ?LocalizableError = null; + + if (error instanceof LocalizableError) { + // It means some API Error has been thrown + localizableError = error; + } else if (error && error.message) { + // Ledger device related error happend, convert then to LocalizableError + switch (error.message) { + case 'TransportError: Failed to sign with Ledger device: U2F TIMEOUT': + localizableError = new LocalizableError(globalMessages.ledgerError101); + break; + case 'TransportStatusError: Ledger device: Action rejected by user': + localizableError = new LocalizableError(messages.signTxError101); + break; + default: + /** we are not able to figure out why Error is thrown + * make it, Something unexpected happened */ + Logger.error(`LedgerSendStore::_convertToLocalizableError::error: ${error.message}`); + localizableError = new UnexpectedError(); + break; + } + } + + if (!localizableError) { + /** we are not able to figure out why Error is thrown + * make it, Something unexpected happened */ + localizableError = new UnexpectedError(); + } + + return localizableError; + } + + _cancel = (): void => { + if (!this.isActionProcessing) { + this.actions.dialogs.closeActiveDialog.trigger(); + this._reset(); + } + } + + @action _setActionProcessing = (processing: boolean): void => { + this.isActionProcessing = processing; + } + + @action _setError = (error: ?LocalizableError): void => { + this.error = error; + } +} diff --git a/app/stores/ada/TrezorConnectStore.js b/app/stores/ada/TrezorConnectStore.js index 7dd6908f7e..391cf07f71 100644 --- a/app/stores/ada/TrezorConnectStore.js +++ b/app/stores/ada/TrezorConnectStore.js @@ -1,12 +1,10 @@ // @flow - // Handles Connect to Trezor Hardware Wallet dialog import { observable, action } from 'mobx'; -import { defineMessages } from 'react-intl'; import TrezorConnect, { UI_EVENT, DEVICE_EVENT } from 'trezor-connect'; -import type { DeviceMessage, Features, UiMessage } from 'trezor-connect'; +import type { DeviceMessage, UiMessage } from 'trezor-connect'; import Config from '../../config'; import environment from '../../environment'; @@ -19,48 +17,28 @@ import globalMessages from '../../i18n/global-messages'; import LocalizableError, { UnexpectedError } from '../../i18n/LocalizableError'; import { CheckAdressesInUseApiError } from '../../api/ada/errors'; +// This is actually just an interface +import { + HWConnectStoreTypes, + StepState, + ProgressStep, + ProgressInfo, + HWDeviceInfo +} from '../../types/HWConnectStoreTypes'; + import { Logger, stringifyError } from '../../utils/logging'; -import type { CreateTrezorWalletResponse } from '../../api/common'; - -const messages = defineMessages({ - saveError101: { - id: 'wallet.trezor.dialog.step.save.error.101', - defaultMessage: '!!!Failed to save. Please check your Internet connection and retry.', - description: ' on the Connect to Trezor Hardware Wallet dialog.' - }, -}); - -type ProgressStepEnum = 0 | 1 | 2; -export const ProgressStep = { - ABOUT: 0, - CONNECT: 1, - SAVE: 2, -}; - -type StepStateEnum = 0 | 1 | 9; -export const StepState = { - LOAD: 0, - PROCESS: 1, - ERROR: 9, -}; - -export type ProgressInfo = { - currentStep: ProgressStepEnum, - stepState: StepStateEnum, -}; - -type TrezorDeviceInfo = { - valid: boolean, - publicKey: ?string, - // Trezor device Features object - features: ?Features -}; - -export default class TrezorConnectStore extends Store { +import type { + CreateHardwareWalletRequest, + CreateHardwareWalletResponse, +} from '../../api/common'; + +/** TODO: TrezorConnectStore and LedgerConnectStore has many common methods + * try to make a common base class */ +export default class TrezorConnectStore extends Store implements HWConnectStoreTypes { // =================== VIEW RELATED =================== // /** the only observable which manages state change */ @@ -73,62 +51,72 @@ export default class TrezorConnectStore extends Store { return this.progressInfo.stepState === StepState.PROCESS; } - /** device info which will be used to create wallet (except wallet name) - * also it holds Trezor device label which is used as default wallet name - * final wallet name will be fetched from the user */ - trezorDeviceInfo: ?TrezorDeviceInfo; - // Trezor device label get defaultWalletName(): string { let defaultWalletName = ''; - if (this.trezorDeviceInfo && this.trezorDeviceInfo.features) { - defaultWalletName = this.trezorDeviceInfo.features.label; + if (this.hwDeviceInfo && this.hwDeviceInfo.hwFeatures) { + defaultWalletName = this.hwDeviceInfo.hwFeatures.label; } return defaultWalletName; } + /** device info which will be used to create wallet (except wallet name) + * also it holds Trezor device label which is used as default wallet name + * final wallet name will be fetched from the user */ + hwDeviceInfo: ?HWDeviceInfo; + /** holds Trezor device DeviceMessage event object, device features will be fetched - * from this object and will be cloned to TrezorDeviceInfo object */ + * from this object and will be converted to TrezorDeviceInfo object */ trezorEventDevice: ?DeviceMessage; // =================== VIEW RELATED =================== // // =================== API RELATED =================== // - createTrezorWalletRequest: LocalizedRequest = - new LocalizedRequest(this.api.ada.createTrezorWallet); + createHWRequest: LocalizedRequest = + new LocalizedRequest(this.api.ada.createHardwareWallet); /** While trezor wallet creation is taking place, we need to block users from starting a * trezor wallet creation on a seperate wallet and explain to them why the action is blocked */ - @observable isCreateTrezorWalletActive: boolean = false; + @observable isCreateHWActive: boolean = false; // =================== API RELATED =================== // setup() { this._reset(); const trezorConnectAction = this.actions.ada.trezorConnect; + trezorConnectAction.init.listen(this._init); trezorConnectAction.cancel.listen(this._cancel); trezorConnectAction.submitAbout.listen(this._submitAbout); - trezorConnectAction.goBacktToAbout.listen(this._goBacktToAbout); + trezorConnectAction.goBackToAbout.listen(this._goBackToAbout); trezorConnectAction.submitConnect.listen(this._submitConnect); trezorConnectAction.submitSave.listen(this._submitSave); - /** Preinitialization of TrezorConnect API will result in faster first response */ try { /** Starting from v7 Trezor Connect Manifest has been made mandatory * https://github.com/trezor/connect/blob/develop/docs/index.md#trezor-connect-manifest */ + const trezorTConfig = Config.wallets.hardwareWallet.trezorT; TrezorConnect.manifest({ - email: Config.trezor.manifest.EMAIL, - appUrl: Config.trezor.manifest.APP_URL + email: trezorTConfig.manifest.EMAIL, + appUrl: trezorTConfig.manifest.APP_URL }); + /** Preinitialization of TrezorConnect API will result in faster first response */ + // TODO [TREZOR]: sometimes when user does fast action initialization is still not complete + // try to use same approach as ledger [for now moving this from _init() to setup()] TrezorConnect.init({}); } catch (error) { Logger.error(`TrezorConnectStore::setup:error: ${stringifyError(error)}`); } } + /** setup() is called when stores are being created + * _init() is called when connect dailog is about to show */ + _init = (): void => { + Logger.debug('TrezorConnectStore::_init called'); + } + teardown(): void { - if (!this.createTrezorWalletRequest.isExecuting) { + if (!this.createHWRequest.isExecuting) { // Trezor Connect request should be reset only in case connect is finished/errored - this.createTrezorWalletRequest.reset(); + this.createHWRequest.reset(); } this._removeTrezorConnectEventListeners(); @@ -143,7 +131,7 @@ export default class TrezorConnectStore extends Store { stepState: StepState.LOAD, }; this.error = undefined; - this.trezorDeviceInfo = undefined; + this.hwDeviceInfo = undefined; this.trezorEventDevice = undefined; }; @@ -165,7 +153,7 @@ export default class TrezorConnectStore extends Store { // =================== CONNECT =================== // /** CONNECT dialog goBack button */ - @action _goBacktToAbout = (): void => { + @action _goBackToAbout = (): void => { this.error = undefined; this.progressInfo.currentStep = ProgressStep.ABOUT; this.progressInfo.stepState = StepState.LOAD; @@ -176,7 +164,7 @@ export default class TrezorConnectStore extends Store { this.error = undefined; this.progressInfo.currentStep = ProgressStep.CONNECT; this.progressInfo.stepState = StepState.PROCESS; - this._checkAndStoreTrezorDeviceInfo(); + this._checkAndStoreHWDeviceInfo(); }; @action _goToConnectError = (): void => { @@ -184,46 +172,111 @@ export default class TrezorConnectStore extends Store { this.progressInfo.stepState = StepState.ERROR; }; - _checkAndStoreTrezorDeviceInfo = async (): Promise => { - // TODO: [TREZOR] fix type if possible - let trezorResp: any; + _checkAndStoreHWDeviceInfo = async (): Promise => { try { - trezorResp = await TrezorConnect.cardanoGetPublicKey({ - path: Config.trezor.DEFAULT_CARDANO_PATH + this.hwDeviceInfo = undefined; + + // TODO: [TREZOR] fix type if possible + const trezorResp = await TrezorConnect.cardanoGetPublicKey({ + path: Config.wallets.BIP44_CARDANO_FIRST_ACCOUNT_SUB_PATH }); + + const trezorEventDevice: DeviceMessage = { ...this.trezorEventDevice }; + + /** Converts a valid hardware wallet response to a common storable format + * later the same format will be used to create wallet */ + this.hwDeviceInfo = this._normalizeHWResponse(trezorResp, trezorEventDevice); + + // It's a valid trezor device, go to Save Load state + this._goToSaveLoad(); + + /** TODO: [TREZOR] handle when user forcefully close Connect to Trezor Hardware Wallet + * while connection in is progress */ + this._removeTrezorConnectEventListeners(); + Logger.info('Trezor device OK'); } catch (error) { - Logger.error(`TrezorConnectStore::_checkAndStoreTrezorDeviceInfo ${stringifyError(error)}`); - } finally { + this._handleConnectError(error); + } + }; - const trezorEventDevice = { ...this.trezorEventDevice }; - const trezorValidity = this._validateTrezor(trezorResp, trezorEventDevice); - - this.trezorDeviceInfo = {}; - this.trezorDeviceInfo.valid = trezorValidity.valid; - if (this.trezorDeviceInfo.valid) { - // It's a valid trezor device, go to Save Load state - if (trezorResp && trezorResp.payload) { - this.trezorDeviceInfo.publicKey = trezorResp.payload.publicKey; - } - if (trezorEventDevice.payload && trezorEventDevice.payload.features) { - this.trezorDeviceInfo.features = trezorEventDevice.payload.features; - } - - this._goToSaveLoad(); - Logger.info('Trezor device OK'); - - // TODO: [TREZOR] handle when user forcefully close Connect to Trezor Hardware Wallet - // while connection in in progress - this._removeTrezorConnectEventListeners(); - } else { - // It's an invalid trezor device, go to Connect Error state - this.error = trezorValidity.error; - this.trezorDeviceInfo.publicKey = undefined; - this.trezorDeviceInfo.features = undefined; - this._goToConnectError(); - Logger.error(`TrezorConnectStore::_checkAndStoreTrezorDeviceInfo ${stringifyError(this.error)}`); + _normalizeHWResponse = ( + trezorResp: any, + trezorEventDevice: DeviceMessage + ): HWDeviceInfo => { + this._validateHWResponse(trezorResp, trezorEventDevice); + + /** This check aready done in _validateHWResponse but flow needs this */ + if (trezorEventDevice == null + || trezorEventDevice.payload == null + || trezorEventDevice.payload.features == null) { + throw new Error('Trezor device hardware info not valid'); + } + + const deviceFeatures = trezorEventDevice.payload.features; + return { + publicMasterKey: trezorResp.payload.publicKey, + hwFeatures: { + vendor: deviceFeatures.vendor, + model: deviceFeatures.model, + deviceId: deviceFeatures.device_id, + label: deviceFeatures.label, + majorVersion: deviceFeatures.major_version, + minorVersion: deviceFeatures.minor_version, + patchVersion: deviceFeatures.patch_version, + language: deviceFeatures.language, + } + }; + } + + /** Validates the compatibility of data which we have received from Trezor device */ + _validateHWResponse = ( + trezorResp: any, + trezorEventDevice: DeviceMessage + ): boolean => { + + if (trezorResp && !trezorResp.success) { + switch (trezorResp.payload.error) { + case 'Iframe timeout': + throw new LocalizableError(globalMessages.trezorError101); + case 'Permissions not granted': + throw new LocalizableError(globalMessages.hwError101); + case 'Cancelled': + case 'Popup closed': + throw new LocalizableError(globalMessages.trezorError103); + default: + throw new Error(trezorResp.payload.error); } } + + if (trezorResp == null + || trezorResp.payload == null + || trezorResp.payload.publicKey == null + || trezorResp.payload.publicKey.length <= 0) { + throw new Error('Invalid public key received from Trezor device'); + } + + if (trezorEventDevice == null + || trezorEventDevice.payload == null + || trezorEventDevice.payload.type !== 'acquired' + || trezorEventDevice.payload.features == null) { + throw new Error('Invalid trezor device event'); + } + + return true; + }; + + _handleConnectError = (error): void => { + Logger.error(`TrezorConnectStore::_handleConnectError ${stringifyError(error)}`); + + this.hwDeviceInfo = undefined; + + if (error instanceof LocalizableError) { + this.error = error; + } else { + this.error = new UnexpectedError(); + } + + this._goToConnectError(); }; _addTrezorConnectEventListeners = (): void => { @@ -258,61 +311,6 @@ export default class TrezorConnectStore extends Store { // this._updateState(); // } }; - - /** Validates the compatibility of data which we have received from Trezor */ - _validateTrezor = ( - trezorResp: any, - trezorEventDevice: DeviceMessage - ): { - valid: boolean, - error: ?LocalizableError - } => { - const trezorValidity = {}; - trezorValidity.valid = false; - - if (!trezorResp.success) { - switch (trezorResp.payload.error) { - case 'Iframe timeout': - trezorValidity.error = new LocalizableError(globalMessages.trezorError101); - break; - case 'Permissions not granted': - trezorValidity.error = new LocalizableError(globalMessages.trezorError102); - break; - case 'Cancelled': - case 'Popup closed': - trezorValidity.error = new LocalizableError(globalMessages.trezorError103); - break; - default: - // Something unexpected happened - Logger.error(`TrezorConnectStore::_validateTrezor::error: ${trezorResp.payload.error}`); - trezorValidity.error = new UnexpectedError(); - break; - } - } - - if (!trezorValidity.error - && trezorResp.payload.publicKey.length <= 0) { - // Something unexpected happened - Logger.error(`TrezorConnectStore::_validateTrezor::error: invalid public key`); - trezorValidity.error = new UnexpectedError(); - } - - if (!trezorValidity.error - && (trezorEventDevice == null - || trezorEventDevice.payload == null - || trezorEventDevice.payload.type !== 'acquired' - || trezorEventDevice.payload.features == null)) { - // Something unexpected happened - Logger.error(`TrezorConnectStore::_validateTrezor::error: invalid device event`); - trezorValidity.error = new UnexpectedError(); - } - - if (!trezorValidity.error) { - trezorValidity.valid = true; - } - - return trezorValidity; - }; // =================== CONNECT =================== // // =================== SAVE =================== // @@ -328,58 +326,58 @@ export default class TrezorConnectStore extends Store { this.progressInfo.currentStep = ProgressStep.SAVE; this.progressInfo.stepState = StepState.PROCESS; - if (this.trezorDeviceInfo - && this.trezorDeviceInfo.publicKey - && this.trezorDeviceInfo.features) { - const walletData = { - walletName, - publicMasterKey: this.trezorDeviceInfo.publicKey, - deviceFeatures: this.trezorDeviceInfo.features - }; - this._saveTrezor(walletData); - } - }; - - @action _goToSaveError = (): void => { - this.progressInfo.currentStep = ProgressStep.SAVE; - this.progressInfo.stepState = StepState.ERROR; + this._saveHW(walletName); }; /** creates new wallet and loads it */ - _saveTrezor = async (params: { - publicMasterKey: string, - walletName: string, - deviceFeatures: Features, - }): Promise => { + _saveHW = async (walletName: string): Promise => { try { - Logger.debug('TrezorConnectStore::_saveTrezor:: stated'); - this._setIsCreateTrezorWalletActive(true); - this.createTrezorWalletRequest.reset(); + Logger.debug('TrezorConnectStore::_saveHW:: stated'); + this._setIsCreateHWActive(true); + this.createHWRequest.reset(); - const trezorWallet: Wallet = await this.createTrezorWalletRequest.execute(params).promise; - if (trezorWallet) { - await this._onSaveSucess(trezorWallet); - } else { - // this Error will be converted to LocalizableError() - throw new Error(); - } + const reqParams = this._prepareCreateHWReqParams(walletName); + const trezorWallet: Wallet = + await this.createHWRequest.execute(reqParams).promise; + + await this._onSaveSucess(trezorWallet); } catch (error) { + Logger.error(`TrezorConnectStore::_saveHW::error ${stringifyError(error)}`); + if (error instanceof CheckAdressesInUseApiError) { - // redirecting CheckAdressesInUseApiError -> saveError101 - // because for user saveError101 is more meaningful in this context - this.error = new LocalizableError(messages.saveError101); + // redirecting CheckAdressesInUseApiError -> hwConnectDialogSaveError101 + // because for user hwConnectDialogSaveError101 is more meaningful in this context + this.error = new LocalizableError(globalMessages.hwConnectDialogSaveError101); } else if (error instanceof LocalizableError) { this.error = error; } else { // some unknow error - this.error = new LocalizableError(messages.error999); + this.error = new UnexpectedError(); } this._goToSaveError(); - Logger.error(`TrezorConnectStore::_saveTrezor::error ${stringifyError(error)}`); } finally { - this.createTrezorWalletRequest.reset(); - this._setIsCreateTrezorWalletActive(false); + this.createHWRequest.reset(); + this._setIsCreateHWActive(false); + } + }; + + _prepareCreateHWReqParams = (walletName: string): CreateHardwareWalletRequest => { + if (this.hwDeviceInfo == null + || this.hwDeviceInfo.publicMasterKey == null + || this.hwDeviceInfo.hwFeatures == null) { + throw new Error('Trezor device hardware info not valid'); } + + return { + walletName, + publicMasterKey: this.hwDeviceInfo.publicMasterKey, + hwFeatures: this.hwDeviceInfo.hwFeatures + }; + } + + @action _goToSaveError = (): void => { + this.progressInfo.currentStep = ProgressStep.SAVE; + this.progressInfo.stepState = StepState.ERROR; }; _onSaveSucess = async (trezorWallet: Wallet) => { @@ -404,15 +402,14 @@ export default class TrezorConnectStore extends Store { // show success notification wallets.showTrezorTWalletIntegratedNotification(); - // TODO: [TREZOR] not sure if it actully destroying this Store ?? this.teardown(); Logger.info('SUCCESS: Trezor Connected Wallet created and loaded'); }; // =================== SAVE =================== // // =================== API =================== // - @action _setIsCreateTrezorWalletActive = (active: boolean): void => { - this.isCreateTrezorWalletActive = active; + @action _setIsCreateHWActive = (active: boolean): void => { + this.isCreateHWActive = active; }; // =================== API =================== // } diff --git a/app/stores/ada/TrezorSendStore.js b/app/stores/ada/TrezorSendStore.js index 47ce65faac..1c8d6b61b5 100644 --- a/app/stores/ada/TrezorSendStore.js +++ b/app/stores/ada/TrezorSendStore.js @@ -13,9 +13,9 @@ import globalMessages from '../../i18n/global-messages'; import type { CreateTrezorSignTxDataRequest, CreateTrezorSignTxDataResponse, - SendTrezorSignedTxRequest, + BroadcastTrezorSignedTxRequest, } from '../../api/ada'; -import type { SendTrezorSignedTxResponse } from '../../api/common'; +import type { BroadcastTrezorSignedTxResponse } from '../../api/common'; import { Logger, @@ -41,8 +41,8 @@ export default class TrezorSendStore extends Store { createTrezorSignTxDataRequest: LocalizedRequest = new LocalizedRequest(this.api.ada.createTrezorSignTxData); - sendTrezorSignedTxRequest: LocalizedRequest = - new LocalizedRequest(this.api.ada.sendTrezorSignedTx); + broadcastTrezorSignedTxRequest: LocalizedRequest = + new LocalizedRequest(this.api.ada.broadcastTrezorSignedTx); // =================== API RELATED =================== // setup() { @@ -59,10 +59,12 @@ export default class TrezorSendStore extends Store { /** Generates a payload with Trezor format and tries Send ADA using Trezor signing */ _sendUsingTrezor = async (params: CreateTrezorSignTxDataRequest): Promise => { try { + this.createTrezorSignTxDataRequest.reset(); + this.broadcastTrezorSignedTxRequest.reset(); if (this.isActionProcessing) { // this Error will be converted to LocalizableError() - throw new Error('Can\'t send another transaction if one transaction is in progress.'); + throw new Error('Can’t send another transaction if one transaction is in progress.'); } this._setError(null); @@ -74,58 +76,61 @@ export default class TrezorSendStore extends Store { // this Error will be converted to LocalizableError() throw new Error('Active wallet required before sending.'); } + const accountId = addresses._getAccountIdByWalletId(activeWallet.id); if (!accountId) { // this Error will be converted to LocalizableError() throw new Error('Active account required before sending.'); } - this.createTrezorSignTxDataRequest.reset(); - const trezorSignTxDataResp = await this.createTrezorSignTxDataRequest.execute(params).promise; // TODO: [TREZOR] fix type if possible - const trezorResp = await TrezorConnect.cardanoSignTransaction({ + const trezorSignTxResp = await TrezorConnect.cardanoSignTransaction({ ...trezorSignTxDataResp.trezorSignTxPayload }); - if (trezorResp && trezorResp.payload && trezorResp.payload.error) { + if (trezorSignTxResp && trezorSignTxResp.payload && trezorSignTxResp.payload.error) { // this Error will be converted to LocalizableError() - throw new Error(trezorResp.payload.error); + throw new Error(trezorSignTxResp.payload.error); } - await this._sendTrezorSignedTx(trezorSignTxDataResp, trezorResp); + await this._brodcastSignedTx(trezorSignTxResp, trezorSignTxDataResp); } catch (error) { Logger.error('TrezorSendStore::_sendUsingTrezor error: ' + stringifyError(error)); this._setError(this._convertToLocalizableError(error)); } finally { this.createTrezorSignTxDataRequest.reset(); - this.sendTrezorSignedTxRequest.reset(); + this.broadcastTrezorSignedTxRequest.reset(); this._setActionProcessing(false); } }; - _sendTrezorSignedTx = async (trezorSignTxDataResp: CreateTrezorSignTxDataResponse, - trezorResp: any): Promise => { + _brodcastSignedTx = async ( + trezorSignTxResp: any, + trezorSignTxDataResp: CreateTrezorSignTxDataResponse + ): Promise => { // TODO: [TREZOR] fix type if possible - const payload: any = trezorResp.payload; - this.sendTrezorSignedTxRequest.reset(); - const reqParams: SendTrezorSignedTxRequest = { + const payload: any = trezorSignTxResp.payload; + const reqParams: BroadcastTrezorSignedTxRequest = { signedTxHex: payload.body, changeAdaAddr: trezorSignTxDataResp.changeAddress }; - // TODO: [TREZOR] add error check - await this.sendTrezorSignedTxRequest.execute(reqParams).promise; + + await this.broadcastTrezorSignedTxRequest.execute(reqParams).promise; + this.actions.dialogs.closeActiveDialog.trigger(); const { wallets } = this.stores.substores[environment.API]; wallets.refreshWalletsData(); + const activeWallet = wallets.active; if (activeWallet) { // go to transaction screen wallets.goToWalletRoute(activeWallet.id); } + Logger.info('SUCCESS: ADA sent using Trezor SignTx'); } @@ -138,13 +143,12 @@ export default class TrezorSendStore extends Store { localizableError = error; } else if (error && error.message) { // Trezor device related error happend, convert then to LocalizableError - // TODO: [TREZOR] check for device not supported if needed switch (error.message) { case 'Iframe timeout': localizableError = new LocalizableError(globalMessages.trezorError101); break; case 'Permissions not granted': - localizableError = new LocalizableError(globalMessages.trezorError102); + localizableError = new LocalizableError(globalMessages.hwError101); break; case 'Cancelled': case 'Popup closed': diff --git a/app/stores/ada/index.js b/app/stores/ada/index.js index 188c962ec0..e38d6d19f4 100644 --- a/app/stores/ada/index.js +++ b/app/stores/ada/index.js @@ -11,6 +11,8 @@ import DaedalusTransferStore from './DaedalusTransferStore'; import TrezorConnectStore from './TrezorConnectStore'; import TrezorSendStore from './TrezorSendStore'; import AdaRedemptionStore from './AdaRedemptionStore'; +import LedgerConnectStore from './LedgerConnectStore'; +import LedgerSendStore from './LedgerSendStore'; export const adaStoreClasses = { adaRedemption: AdaRedemptionStore, @@ -20,7 +22,9 @@ export const adaStoreClasses = { addresses: AddressesStore, daedalusTransfer: DaedalusTransferStore, trezorConnect: TrezorConnectStore, - trezorSend: TrezorSendStore + trezorSend: TrezorSendStore, + ledgerConnect: LedgerConnectStore, + ledgerSend: LedgerSendStore, }; export type AdaStoresMap = { @@ -32,6 +36,8 @@ export type AdaStoresMap = { daedalusTransfer: DaedalusTransferStore, trezorConnect: TrezorConnectStore, trezorSend: TrezorSendStore, + ledgerConnect: LedgerConnectStore, + ledgerSend: LedgerSendStore, }; const adaStores = observable({ @@ -43,6 +49,8 @@ const adaStores = observable({ daedalusTransfer: null, trezorConnect: null, trezorSend: null, + ledgerConnect: null, + ledgerSend: null, }); /** See `stores` index for description of this weird behavior diff --git a/app/stores/toplevel/TopbarStore.js b/app/stores/toplevel/TopbarStore.js index a0e80b5cb1..415074c358 100644 --- a/app/stores/toplevel/TopbarStore.js +++ b/app/stores/toplevel/TopbarStore.js @@ -3,7 +3,10 @@ import { observable, action } from 'mobx'; import Store from '../base/Store'; import resolver from '../../utils/imports'; import environment from '../../environment'; -import { WITH_TREZOR_T_CATEGORIE } from '../../config/topbarConfig'; +import { + WITH_LEDGER_NANO_S_CATEGORIE as WITH_LEDGER_NANO_S, + WITH_TREZOR_T_CATEGORIE as WITH_TREZOR_T, +} from '../../config/topbarConfig'; const topbarConfig = resolver('config/topbarConfig'); @@ -26,13 +29,17 @@ export default class TopbarStore extends Store { @action initCategories() { this.CATEGORIES = topbarConfig.CATEGORIES; - // If active wallet is TrezorTWallet then show with Trezor Icon const { wallets } = this.stores.substores[environment.API]; - if (wallets - && wallets.first - && wallets.first.isTrezorTWallet - && !this.CATEGORIES.find(category => category.name === WITH_TREZOR_T_CATEGORIE.name)) { - this.CATEGORIES.push(WITH_TREZOR_T_CATEGORIE); + // If active wallet is TrezorTWallet then show with Trezor T Icon + if (wallets && wallets.first && wallets.first.isTrezorTWallet + && !this.CATEGORIES.find(category => category.name === WITH_TREZOR_T.name)) { + this.CATEGORIES.push(WITH_TREZOR_T); + } + + // If active wallet is LedgerNanoSWallet then show with Ledger Nano S Icon + if (wallets && wallets.first && wallets.first.isLedgerNanoSWallet + && !this.CATEGORIES.find(category => category.name === WITH_LEDGER_NANO_S.name)) { + this.CATEGORIES.push(WITH_LEDGER_NANO_S); } this.activeTopbarCategory = this.CATEGORIES[0].route; diff --git a/app/stores/toplevel/WalletBackupStore.js b/app/stores/toplevel/WalletBackupStore.js index 05bdb539b0..d69f34dca9 100644 --- a/app/stores/toplevel/WalletBackupStore.js +++ b/app/stores/toplevel/WalletBackupStore.js @@ -42,6 +42,7 @@ class WalletBackupStore extends Store { a.restartWalletBackup.listen(this._restartWalletBackup); a.cancelWalletBackup.listen(this._cancelWalletBackup); a.finishWalletBackup.listen(this._finishWalletBackup); + a.removeOneMnemonicWord.listen(this._removeOneWord); } @action _initiateWalletBackup = (params: { recoveryPhrase: Array }) => { @@ -86,7 +87,7 @@ class WalletBackupStore extends Store { @action _addWordToWalletBackupVerification = (params: { word: string, index: number }) => { const { word, index } = params; - this.enteredPhrase.push({ word }); + this.enteredPhrase.push({ word, index }); const pickedWord = this.recoveryPhraseSorted[index]; if (pickedWord && pickedWord.word === word) pickedWord.isActive = false; }; @@ -98,6 +99,14 @@ class WalletBackupStore extends Store { ); }; + @action _removeOneWord = () => { + if (!this.enteredPhrase) { + return; + } + const poppedWord = this.enteredPhrase.pop(); + this.recoveryPhraseSorted[poppedWord.index].isActive = true; + }; + @computed get isRecoveryPhraseValid(): boolean { return ( this.recoveryPhraseWords.reduce((words, { word }) => words + word, '') === diff --git a/app/themes/prebuilt/YoroiClassic.js b/app/themes/prebuilt/YoroiClassic.js index 4c030cee76..3cb09cf5b5 100644 --- a/app/themes/prebuilt/YoroiClassic.js +++ b/app/themes/prebuilt/YoroiClassic.js @@ -228,7 +228,7 @@ export default { '--theme-receive-qr-code-foreground-color': '#121327', '--theme-send-confirmation-dialog-send-values-color': '#ea4c5b', - '--theme-trezor-send-confirmation-info-block-background-color': '#f3f3f5', + '--theme-hw-send-confirmation-info-block-background-color': '#f3f3f5', '--theme-settings-body-background-color': '#ffffff', '--theme-settings-pane-background-color': '#f3f3f5', @@ -342,8 +342,8 @@ export default { '--theme-wallet-password-switch-label-color': '#121327', '--theme-banner-warning-background-color': '#b54b4b', - '--theme-trezor-connect-dialog-middle-block-common-background-color': '#f3f3f5', - '--theme-trezor-connect-dialog-middle-block-common-error-background-color': '#fdf1f0', + '--theme-hw-connect-dialog-middle-block-common-background-color': '#f3f3f5', + '--theme-hw-connect-dialog-middle-block-common-error-background-color': '#fdf1f0', '--theme-widgets-progress-step-common-color': '#daa49a', diff --git a/app/types/HWConnectStoreTypes.js b/app/types/HWConnectStoreTypes.js new file mode 100644 index 0000000000..9b36c9adf1 --- /dev/null +++ b/app/types/HWConnectStoreTypes.js @@ -0,0 +1,130 @@ +// @flow +// Interface of Hardware Wallet Connect dialog + +import Wallet from '../domain/Wallet'; +import LocalizedRequest from '../stores/lib/LocalizedRequest'; + +import LocalizableError from '../i18n/LocalizableError'; + +import type { CreateHardwareWalletRequest } from '../api/common'; + +export type ProgressStepEnum = 0 | 1 | 2; +export const ProgressStep = { + ABOUT: 0, + CONNECT: 1, + SAVE: 2, +}; + +export type StepStateEnum = 0 | 1 | 9; +export const StepState = { + LOAD: 0, + PROCESS: 1, + ERROR: 9, +}; + +export interface ProgressInfo { + currentStep: ProgressStepEnum, + stepState: StepStateEnum, +} + +// Hardware wallet device Features object +export interface HWFeatures { + vendor: string, + model: string, + label: string, + deviceId: string, + language: string, + majorVersion: number, + minorVersion: number, + patchVersion: number, +} + +export interface HWDeviceInfo { + publicMasterKey: string, + hwFeatures: HWFeatures +} + +export interface HWConnectStoreTypes { + // =================== VIEW RELATED =================== // + /** the only observable which manages state change */ + progressInfo: ProgressInfo; + + /** only in ERROR state it will hold LocalizableError object */ + error: ?LocalizableError; + + /** device info which will be used to create wallet (except wallet name) + * it also holds hardware device label which can used as default wallet name + * although final wallet name will be fetched from the user */ + hwDeviceInfo: ?HWDeviceInfo; + + /** Hardware device label to be used as default wallet name + * although user can opt to use user give name */ + get defaultWalletName(): string; + + get isActionProcessing(): boolean; + // =================== VIEW RELATED =================== // + + // =================== API RELATED =================== // + createHWRequest: LocalizedRequest; + + /** While hardware wallet creation is taking place, we need to block users from starting a + * hardware wallet creation on a seperate wallet and explain to them why the action is blocked */ + isCreateHWActive: boolean; + // =================== API RELATED =================== // + + setup(): void; + + /** setup() is called when stores are being created + * _init() is called when connect dailog is about to show */ + _init(): void; + + teardown(): void; + + _reset(): void; + + _cancel(): void; + + // =================== ABOUT =================== // + /** ABOUT dialog submit(Next button) */ + _submitAbout(): void; + // =================== ABOUT =================== // + + // =================== CONNECT =================== // + /** CONNECT dialog goBack button */ + _goBackToAbout(): void; + + /** CONNECT dialog submit (Connect button) */ + _submitConnect(): void; + + _goToConnectError(): void; + + _checkAndStoreHWDeviceInfo(): Promise; + + /** Validates the compatibility of data which we have received from hardware wallet */ + _validateHWResponse(any, any): boolean; + + /** Converts a valid hardware wallet response to a common storable format + * later the same format will be used to create wallet */ + _normalizeHWResponse(any, any): HWDeviceInfo; + // =================== CONNECT =================== // + + // =================== SAVE =================== // + _goToSaveLoad(): void; + + /** SAVE dialog submit (Save button) */ + _submitSave(walletName: string): void; + + _goToSaveError(): void; + + /** creates new wallet and loads it */ + _saveHW(walletName: string): Promise; + + _prepareCreateHWReqParams(walletName: string): CreateHardwareWalletRequest; + + _onSaveSucess(hwWallet: Wallet): Promise; + // =================== SAVE =================== // + + // =================== API =================== // + _setIsCreateHWActive(active: boolean): void; + // =================== API =================== // +} diff --git a/app/types/TransferTypes.js b/app/types/TransferTypes.js index 95d41c6407..64f78354d6 100644 --- a/app/types/TransferTypes.js +++ b/app/types/TransferTypes.js @@ -5,6 +5,7 @@ export type TransferStatus = 'uninitialized' | 'gettingMnemonics' | 'gettingPaperMnemonics' + | 'gettingMasterKey' | 'restoringAddresses' | 'checkingAddresses' | 'generatingTx' diff --git a/app/types/WalletType.js b/app/types/WalletType.js index 0acf9c277d..808925f736 100644 --- a/app/types/WalletType.js +++ b/app/types/WalletType.js @@ -12,3 +12,11 @@ export type WalletHardwareInfo = { language: string, publicMasterKey: string, }; + +export const WalletTypeOption : { + WEB_WALLET: WalletType, + HARDWARE_WALLET: WalletType +} = { + WEB_WALLET: 'CWTWeb', + HARDWARE_WALLET: 'CWTHardware' +}; diff --git a/app/utils/iframeHandler.js b/app/utils/iframeHandler.js new file mode 100644 index 0000000000..0bce087cce --- /dev/null +++ b/app/utils/iframeHandler.js @@ -0,0 +1,50 @@ +// @flow +import { + LedgerBridge, + YOROI_LEDGER_BRIDGE_IFRAME_NAME +} from 'yoroi-extension-ledger-bridge'; + +const LEDGER_BRIDGE_CHECK_INTERVAL = 500; // in ms (1000ms = 1sec) +const LEDGER_BRIDGE_CHECK_COUNT = 10; + +export async function prepareLedgerBridger(ledgerBridge: LedgerBridge): Promise { + if (ledgerBridge == null) { + throw new Error(`LedgerBridge Error: LedgerBridge is undefined`); + } + + return new Promise((resolve, reject) => { + let checkCounter = 0; + const checkInterval = setInterval(() => { + if (ledgerBridge.isReady) { + clearInterval(checkInterval); + resolve(); + } else if (checkCounter > LEDGER_BRIDGE_CHECK_COUNT) { + clearInterval(checkInterval); + const timeSpentInSec = LEDGER_BRIDGE_CHECK_INTERVAL * LEDGER_BRIDGE_CHECK_COUNT / 1000; + reject(new Error(`LedgerBridge Error: Timeout. Couldn't connect to bridge in less than ${timeSpentInSec}seconds`)); + } + checkCounter++; + }, LEDGER_BRIDGE_CHECK_INTERVAL); + }); +} + +// TODO: not used, can remove getIFrame +export function getIFrame(id: string): ?HTMLIFrameElement { + const element = document.getElementById(id); + if (element instanceof HTMLIFrameElement) { + return element; + } +} + +/** In order to keep all iframe related logic in iframeHandler + * softly restricting YOROI_LEDGER_BRIDGE_IFRAME_NAME use from outside */ +export function disposeLedgerBridgeIFrame() { + disposeIFrame(YOROI_LEDGER_BRIDGE_IFRAME_NAME); +} + +export function disposeIFrame(id: string): void { + const element = document.getElementById(id); + if (element instanceof HTMLIFrameElement) { + element.remove(); + } +} diff --git a/chrome/manifest.development.json b/chrome/manifest.development.json index d166211f0a..76d18701b3 100644 --- a/chrome/manifest.development.json +++ b/chrome/manifest.development.json @@ -35,6 +35,6 @@ "js": ["js/trezor-content-script.js"] } ], - "content_security_policy": "default-src 'self' http://localhost:3000 https://localhost:3000 http://localhost:8097; frame-src https://connect.trezor.io/; script-src 'self' 'unsafe-eval' http://localhost:3000 https://localhost:3000 http://localhost:8097 blob:; object-src 'self'; connect-src https://iohk-mainnet.yoroiwallet.com wss://iohk-mainnet.yoroiwallet.com:443 http://localhost:3000 https://localhost:3000 http://localhost:8080 https://localhost:8080 http://localhost:8097 ws://localhost:8080 ws://localhost:8097 wss://localhost:8080 wss://iohk-staging.yoroiwallet.com:443 https://iohk-staging.yoroiwallet.com; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' http://localhost:3000 data:;", + "content_security_policy": "default-src 'self' http://localhost:3000 https://localhost:3000 http://localhost:8097; frame-src https://connect.trezor.io/ https://emurgo.github.io/yoroi-extension-ledger-bridge; script-src 'self' 'unsafe-eval' http://localhost:3000 https://localhost:3000 http://localhost:8097 blob:; object-src 'self'; connect-src https://iohk-mainnet.yoroiwallet.com wss://iohk-mainnet.yoroiwallet.com:443 http://localhost:3000 https://localhost:3000 http://localhost:8080 https://localhost:8080 http://localhost:8097 ws://localhost:8080 ws://localhost:8097 wss://localhost:8080 wss://iohk-staging.yoroiwallet.com:443 https://iohk-staging.yoroiwallet.com; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' http://localhost:3000 data:;", "key": "pojejnpjgcacmnpkdiklhlnlbkjechfh" } diff --git a/chrome/manifest.mainnet.json b/chrome/manifest.mainnet.json index fd520715ec..86517d399a 100644 --- a/chrome/manifest.mainnet.json +++ b/chrome/manifest.mainnet.json @@ -35,5 +35,5 @@ "js": ["js/trezor-content-script.js"] } ], - "content_security_policy": "default-src 'self'; frame-src https://connect.trezor.io/; script-src 'self' 'unsafe-eval' blob:; connect-src https://iohk-mainnet.yoroiwallet.com wss://iohk-mainnet.yoroiwallet.com:443; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;" + "content_security_policy": "default-src 'self'; frame-src https://connect.trezor.io/ https://emurgo.github.io/yoroi-extension-ledger-bridge; script-src 'self' 'unsafe-eval' blob:; connect-src https://iohk-mainnet.yoroiwallet.com wss://iohk-mainnet.yoroiwallet.com:443; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;" } diff --git a/chrome/manifest.staging.json b/chrome/manifest.staging.json index b31de3d10b..a4fab61a4f 100644 --- a/chrome/manifest.staging.json +++ b/chrome/manifest.staging.json @@ -36,5 +36,5 @@ "js": ["js/trezor-content-script.js"] } ], - "content_security_policy": "default-src 'self'; frame-src https://connect.trezor.io/; script-src 'self' 'unsafe-eval' blob:; connect-src wss://iohk-staging.yoroiwallet.com:443 https://iohk-staging.yoroiwallet.com; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;" + "content_security_policy": "default-src 'self'; frame-src https://connect.trezor.io/ https://emurgo.github.io/yoroi-extension-ledger-bridge; script-src 'self' 'unsafe-eval' blob:; connect-src wss://iohk-staging.yoroiwallet.com:443 https://iohk-staging.yoroiwallet.com; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;" } diff --git a/chrome/manifest.test.json b/chrome/manifest.test.json index f5918fa216..d8bc72d0de 100644 --- a/chrome/manifest.test.json +++ b/chrome/manifest.test.json @@ -35,5 +35,5 @@ "js": ["js/trezor-content-script.js"] } ], - "content_security_policy": "default-src 'self'; frame-src https://connect.trezor.io/; script-src 'self' 'unsafe-eval' blob:; connect-src http://localhost:8080 https://localhost:8080 ws://localhost:8080 wss://localhost:8080; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;" + "content_security_policy": "default-src 'self'; frame-src https://connect.trezor.io/ https://emurgo.github.io/yoroi-extension-ledger-bridge; script-src 'self' 'unsafe-eval' blob:; connect-src http://localhost:8080 https://localhost:8080 ws://localhost:8080 wss://localhost:8080; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;" } diff --git a/chrome/manifest.testnet.json b/chrome/manifest.testnet.json index 978ade4e26..6641613ba1 100644 --- a/chrome/manifest.testnet.json +++ b/chrome/manifest.testnet.json @@ -36,5 +36,5 @@ "js": ["js/trezor-content-script.js"] } ], - "content_security_policy": "default-src 'self'; frame-src https://connect.trezor.io/; script-src 'self' 'unsafe-eval'; connect-src https://18.207.22.26:8080 wss://18.207.22.26:8080; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;" + "content_security_policy": "default-src 'self'; frame-src https://connect.trezor.io/ https://emurgo.github.io/yoroi-extension-ledger-bridge; script-src 'self' 'unsafe-eval'; connect-src https://18.207.22.26:8080 wss://18.207.22.26:8080; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;" } diff --git a/docs/specs/code/ledger-integration/BOLOS_SDK_SETUP.md b/docs/specs/code/ledger-integration/BOLOS_SDK_SETUP.md new file mode 100644 index 0000000000..ae516815a3 --- /dev/null +++ b/docs/specs/code/ledger-integration/BOLOS_SDK_SETUP.md @@ -0,0 +1,4 @@ +#### BOLOS SDK is neeed for app installation +- [Ledger docs](https://ledger.readthedocs.io/en/latest/) +- [BOLOS Features](https://ledger.readthedocs.io/en/latest/bolos/features.html) +- [Developing and / or compiling BOLOS applications](https://ledger.readthedocs.io/en/latest/userspace/getting_started.html) diff --git a/docs/specs/code/ledger-integration/FIRMWARE_UPDATE.md b/docs/specs/code/ledger-integration/FIRMWARE_UPDATE.md new file mode 100644 index 0000000000..2479fefc92 --- /dev/null +++ b/docs/specs/code/ledger-integration/FIRMWARE_UPDATE.md @@ -0,0 +1,61 @@ +#### Install python-dev (python 2.7) and pip
    +`sudo apt install python-dev`
    +`sudo apt install python-pip` + + +#### Install [Python tools for Ledger Blue and Nano S](https://github.com/LedgerHQ/blue-loader-python)
    +`pip uninstall ledgerblue`
    +`pip install ledgerblue`
    +`sudo apt-get install libudev-dev libusb-1.0-0-dev`
    +udev rules setup: [download and run](https://github.com/LedgerHQ/udev-rules/blob/master/add_udev_rules.sh) +`wget -q -O - https://raw.githubusercontent.com/LedgerHQ/udev-rules/master/add_udev_rules.sh | sudo bash` + +#### Ledger's target ID +``` ++-----------------+------------+ +| FirmWare | Target ID | ++-----------------+------------+ +| Nano S <= 1.3.1 | 0x31100002 | +| Nano S 1.4.x | 0x31100003 | +| Nano S 1.5.x | 0x31100004 | +| | | +| Blue 2.0.x | 0x31000002 | +| Blue 2.1.x | 0x31000004 | +| | | +| MCU,any version | 0x01000001 | ++-----------------+------------+ +``` + +#### FIRMWARE update
    +CURRENT_FIRMWARE_VERSION(aka Secure Element) = 1.4.2
    +CURRENT_MCU_VERSION = 1.6
    + +UPDATE_TO_FIRMWARE_VERSION = 1.5.5
    +UPDATE_TO_MCU_VERSION = 1.7
    + +DEVICE_TARGET_ID = 0x31100003 (if CURRENT_FIRMWARE_VERSION < 1.5.x ) other wise 0x31100004
    + +**Reboot the ledger device in recovery mode by pressing right button when starting(unlock with pin if prompted)**
    +**SYNTAX**:
    +python -m ledgerblue.updateFirmware
    +--target DEVICE_TARGET_ID
    +--url https://hsmprod.hardwarewallet.com/hsm/process
    +--perso perso_11
    +--firmware nanos/UPDATE_TO_FIRMWARE_VERSION/fw_CURRENT_FIRMWARE_VERSION/upgrade_osu_UPDATE_TO_FIRMWARE_VERSION
    +--firmwareKey nanos/UPDATE_TO_FIRMWARE_VERSION/fw_CURRENT_FIRMWARE_VERSION/upgrade_osu_UPDATE_TO_FIRMWARE_VERSION_key
    +run:
    +`python -m ledgerblue.updateFirmware --target 0x31100003 --url https://hsmprod.hardwarewallet.com/hsm/process --perso perso_11 --firmware nanos/1.5.5/fw_1.4.2/upgrade_osu_1.5.5 --firmwareKey nanos/1.5.5/fw_1.4.2/upgrade_osu_1.5.5_key`
    +and:
    +`python -m ledgerblue.updateFirmware --target 0x31100004 --url https://hsmprod.hardwarewallet.com/hsm/process --perso perso_11 --firmware nanos/1.5.5/fw_1.4.2/upgrade_1.5.5 --firmwareKey nanos/1.5.5/fw_1.4.2/upgrade_1.5.5_key`
    + +Device will eventually display "Follow device repair instructions", then you should proceed with the following:
    + +**ONLY AFTER SUCCESS OF PREVIOUS STEPS, ONLY IF YOU'RE UPDATING FROM A FIRMWARE < 1.5.3:**
    +Reboot device in bootloader mode by pressing left button when starting
    +Download and unzip the MCU firmware and bootloader updater from link below and run:
    +https://drive.google.com/file/d/1hyvdFhBA6FRCHOTuPB1O14q9Q3wnsER0/view?usp=sharing
    +`python -m ledgerblue.loadMCU --targetId 0x01000001 --fileName blup_0.9_misc_m1.hex --nocrc`
    +`python -m ledgerblue.loadMCU --targetId 0x01000001 --fileName mcu_1.7_over_0.9.hex --nocrc`
    + +#### NEW SDK to compile apps
    +https://drive.google.com/file/d/1VKwl5LI1Qc0zF2Z2FELFIOakUkTLFYm4/view?usp=sharing diff --git a/docs/specs/code/ledger-integration/README.md b/docs/specs/code/ledger-integration/README.md new file mode 100644 index 0000000000..a7a2236f7c --- /dev/null +++ b/docs/specs/code/ledger-integration/README.md @@ -0,0 +1,221 @@ +# Abstract + +1. Users would be able to use a Ledger Hardware Wallet with Yoroi Wallet. +2. Hardware wallet can be integrated by creating new Yoroi wallet, by using `Connect to Ledger Hardware Wallet` on `Add Wallet` page. +3. [yoroi-extension-ledger-bridge](https://github.com/Emurgo/yoroi-extension-ledger-bridge) API will be used to communicate with hardware device. + +# Motivation + +1. Since private key never leaves the hardware wallet so it's considered as one of the most secuired way to use cryptocurrency wallets. +2. No need to remember spending passowrd, so its easy to use(although need to remember a pincode for screen unlocking). + +# Background + +As Yorio wallet has Trezor Hardware wallet support, Ledger Hardware wallet support will add up to the scope of user reach. + +# Iteration-1 + +## Proposal + +User will be able to: +1. Setup a new Yoroi Wallet without exposing its private key/ mnemonics. +2. Send ADA using the Ledger Wallet Security. + +## Prerequisite + +1. [Only Ledger Nano S model is supported for now.](https://www.ledger.com/products/ledger-nano-s) +2. Cardano ADA app must be installed on Ledger device.
    +![image](https://user-images.githubusercontent.com/19986226/53296899-4b1d4e00-3858-11e9-9bf4-3829498676c2.png) +3. [Additional setting may be need depending on your OS.](https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues) + + +## Ledger Integrated Wallet Creation + +1. Install or update to a supported version of Yoroi. +2. Select `Connect to Ledger Hardware Wallet` in the `Add Wallet` page - where Restore Wallet and Create Wallet also appears. +4. Connect the Ledger device to the computer and follow the steps to export the master public key for a Cardano Wallet. +5. Default wallet name will be provided to be used as wallet name, but it can be modified by the user. + +## Send ADA using Ledger Sign Transaction + +1. Go to the Send Tab as usual, fill with the receiver's address and desire amount. +2. Press NEXT button and select option to Send using Ledger. +3. Approve the transaction on the Ledger device. + +## Low Level Implementation Design + +For communication with device We will be using [Emurgo/yoroi-extension-ledger-bridge](https://github.com/Emurgo/yoroi-extension-ledger-bridge), which is a wrapper of https://github.com/cardano-foundation/ledgerjs-hw-app-cardano.
    + +NPM [package](https://www.npmjs.com/package/@cardano-foundation/ledgerjs-hw-app-cardano). + +Transport layer can be: +- [hw-transport-u2f](https://www.npmjs.com/package/@ledgerhq/hw-transport-u2f) +- [hw-transport-webusb](https://www.npmjs.com/package/@ledgerhq/hw-transport-webusb) + +Manual Ledger device Cardano ADA app installation: +- BOLOS development environment [set up](./BOLOS_SDK_SETUP.md) +- Update [ledger device FIRMWARE](./FIRMWARE_UPDATE.md) to `FIRMWARE_VERSION = 1.5.5` and `MCU_VERSION = 1.7`. +- [Clone](https://github.com/cardano-foundation/ledger-app-cardano) and `make load` should do the job assuming your BOLOS env is correct. + +We will use following API: +* For Ledger Integrated Wallet Creation: + - [Ada.getExtendedPublicKey(hdPath: BIP32Path)](https://github.com/Emurgo/yoroi-extension-ledger-bridge/blob/4d573b50825d81927aca76b9b2a552e322647e4e/src/index.js#L66) + - [Ada.getVersion()](https://github.com/Emurgo/yoroi-extension-ledger-bridge/blob/4d573b50825d81927aca76b9b2a552e322647e4e/src/index.js#L49) + +* For Send ADA using Ledger Sign Transaction: + - [Ada.signTransaction(inputs: Array, outputs: Array)](https://github.com/Emurgo/yoroi-extension-ledger-bridge/blob/4d573b50825d81927aca76b9b2a552e322647e4e/src/index.js#L106) + +### Ledger Integrated Wallet Creation +* [Ada.getExtendedPublicKey(hdPath: BIP32Path)](https://github.com/Emurgo/yoroi-extension-ledger-bridge/blob/4d573b50825d81927aca76b9b2a552e322647e4e/src/index.js#L66) will return
    +``` +{ + publicKeyHex: string, + chainCodeHex: string +} + // we will use this as [root_cached_key: ( publicKeyHex + chainCodeHex )master public key] +``` + [BIP32Path](https://github.com/cardano-foundation/ledgerjs-hw-app-cardano/blob/511a674a0801e4fdbf503bea6cfd96d565d2223a/src/Ada.js#L38) = [2147483692, 2147485463, 2147483648] + +* [Ada.getVersion()](https://github.com/Emurgo/yoroi-extension-ledger-bridge/blob/4d573b50825d81927aca76b9b2a552e322647e4e/src/index.js#L49) will return
    +``` +{ + major: string, + minor: string, + patch: string +} +``` +we will store everthing in `localStorage` at `WALLET.cwHardwareInfo` + +* For a new Yoroi Wallet, we need to create: `adaWallet` and `cryptoAccount` objects. **[this part is almost similar to Trezor Integration]**
    +`adaWallet` = no change on how we were using it before +`cryptoAccount` = we need to create it manually, because to create it with rust-cardano we would need the master private key, which we don’t have. This object is created in the following way: +``` +const cryptoAccount = { + root_cached_key: 'master public key', + derivation_scheme: 'V2', + account: 0 // we currently only support one account in Yoroi. +}; +``` + +* `localStorage` structure **[this part is almost similar to Trezor Integration]** +``` +ACCOUNT = { +"account": 0, +"root_cached_key": "master public key", // root_cached_key => Ada.getExtendedPublicKey() +"derivation_scheme": "V2" +} +``` +``` +LAST_BLOCK_NUMBER = "last_block_number" +``` +``` +WALLET = { + "adaWallet": { + "cwAccountsNumber": 1, + "cwAmount": { + "getCCoin": "1000000" + }, + "cwId": "1", + "cwMeta": { + "cwAssurance": "CWANormal", + "cwName": "TEST-ADA", + "cwUnit": 0 + }, + "cwType": "CWTHardware", + "cwHardwareInfo": { + "vendor": "ledger.com", + "deviceId": "device id" // presently there is no way get deviceId, but if possible will try to figure out + "majorVersion": 1, // majorVersion => getVersion().major + "minorVersion": 0, // minorVersion => getVersion().minor + "patchVersion": 0, // patchVersion => getVersion().patch + "model": "NanoS", // presently there is no way get model, but if possible will try to figure out + "publicMasterKey": "master public key" // publicMasterKey => Ada.getExtendedPublicKey() + } + } +} +``` + +### Send ADA using Ledger Sign Transaction + +* Amount and Reciever's wallet address will be fetched from user(and passed to API) and need to prepare data as following + - `inputs: Array` ([InputTypeUTxO](https://github.com/cardano-foundation/ledgerjs-hw-app-cardano/blob/511a674a0801e4fdbf503bea6cfd96d565d2223a/src/Ada.js#L40)) + ``` + Example: + inputs = [ + { + txDataHex: + "839f8200d8185824825820918c11e1c041a0cb04baea651b9fb1bdef7ee5295f" + + "032307e2e57d109de118b8008200d81858248258208f34e4f719effe82c28c8f" + + "f45e426233651fc03686130cb7e1d4bc6de20e689c01ff9f8282d81858218358" + + "1cb6f4b193e083530aca83ff03de4a60f4e7a6732b68b4fa6972f42c11a0001a" + + "907ab5c71a000f42408282d818584283581cb5bacd405a2dcedce19899f8647a" + + "8c4f45d84c06fb532c63f9479a40a101581e581c6b8487e9d22850b7539db255" + + "e27dd48dc0a50c7994d678696be64f21001ac5000d871a03dc396fffa0", + outputIndex: 0, + path: utils.str_to_path("44'/1815'/0'/0/0") // Ledger API call Ada.utils.str_to_path("44'/1815'/0'/1/0") + } + ]; + ``` + - `outputs: Array` ([OutputTypeAddress](https://github.com/cardano-foundation/ledgerjs-hw-app-cardano/blob/511a674a0801e4fdbf503bea6cfd96d565d2223a/src/Ada.js#L46) , [OutputTypeChange](https://github.com/cardano-foundation/ledgerjs-hw-app-cardano/blob/511a674a0801e4fdbf503bea6cfd96d565d2223a/src/Ada.js#L51)) + ``` + Example: + outputs = [ + { + amountStr: "700000", + address58: + "DdzFFzCqrhsoarXqLakMBEiURCGPCUL7qRvPf2oGknKN2nNix5b9SQKj2YckgXZK" + + "6q1Ym7BNLxgEX3RQFjS2C41xt54yJHeE1hhMUfSG" + }, + { + amountStr: "100000", + path: utils.str_to_path("44'/1815'/0'/1/0") // Ledger API call Ada.utils.str_to_path("44'/1815'/0'/1/0") + } + ]; + ``` + + - By calling [Ada.signTransaction(inputs: Array< InputTypeUTxO >, outputs: Array)](https://github.com/Emurgo/yoroi-extension-ledger-bridge/blob/4d573b50825d81927aca76b9b2a552e322647e4e/src/index.js#L106) will return + ``` + Example: + { + txHashHex: '01f54c866c778568c01b9e4c0a2cbab29e0af285623404e0ef922c6b63f9b222', + witnesses: [ + { + path: [Array], + witnessHex: 'f89f0d3e2ad34a29c36d9eebdceb951088b52d33638d0f55d49ba2f8baff6e29056720be55fd2eb7198c05b424ce4308eaeed7195310e5879c41c1743245b000' + } + ] + } + ``` + Response will be serialized(different from Trezor) and passed to backend API through [signTx](https://github.com/Emurgo/yoroi-frontend/blob/bbbdad033b567f0298f61e59a985c1c26f30ee07/app/api/ada/lib/yoroi-backend-api.js#L126) for broadcasting the transaction. + +### other changes + +we need to change following modules similar to Trezor integration implementation. +``` +app/api => Wallet creation and Send SignedTx +``` +``` +app/action => Action for Connect to Trezor +``` +``` +app/store => Pass Action from View to API +``` +``` +app/containers => View containers(dialog) +``` +``` +app/components => View base components +``` + +# Iteration-2 + +TBD + +# Reference + +1. https://github.com/cardano-foundation/ledgerjs-hw-app-cardano +2. https://github.com/cardano-foundation/ledger-app-cardano +4. https://github.com/LedgerHQ/ledgerjs/tree/master/packages/hw-app-ada +5. http://ledgerhq.github.io/ledgerjs/docs/#ada +6. https://ledger-dev.slack.com/ +7. https://www.npmjs.com/package/@cardano-foundation/ledgerjs-hw-app-cardano diff --git a/features/daedalus-transfer.feature b/features/daedalus-transfer.feature index 2fc617d298..45cdb268a2 100644 --- a/features/daedalus-transfer.feature +++ b/features/daedalus-transfer.feature @@ -43,7 +43,7 @@ Feature: Transfer Daedalus Wallet funds | gadget | @withWebSocketConnection @it-45 - Scenario: User can transfer Daedalus funds to Icarus using 12-word mnemonic phrase (IT-45) + Scenario: User can transfer Daedalus funds to Yoroi using 12-word mnemonic phrase (IT-45) Given I am testing "Daedalus transfer funds Screen" When There is a wallet stored named Test And My Daedalus wallet has funds @@ -99,3 +99,23 @@ Feature: Transfer Daedalus Wallet funds Then I see all necessary elements on "TRANSFER FUNDS FROM DAEDALUS" screen: |instructionMessage | attentionMessage| |transfer.instructions.instructions.text | daedalusTransfer.instructions.attention.text| + + @withWebSocketConnection @it-19 + Scenario: User can transfer Daedalus funds to Yoroi using master key (IT-19) + Given I am testing "Daedalus transfer funds Screen" + When There is a wallet stored named Test + And My Daedalus wallet has funds + And I am on the Daedalus Transfer instructions screen + When I click on the transfer funds from Daedalus master key button + # enter private key for following mnemonic + # leaf immune metal phrase river cool domain snow year below result three + And I enter the master key: + | masterKey | + | 50d1b52581adefa3e99025ade8f7189318e1e9ac2f0a1d66d9a1c86f3908ca5fe1a5e08866b500a9a0e11d48c41dbb4957c550b418e7b5c6c9a531ab37037c35d0e9ecaab457c8dea556bb2ef43ec59cc943b12adb39c9d38d4d90563b9014a7 | + And I proceed with the recovery + Then I should wait until funds are recovered: + | daedalusAddress | amount | + | DdzFFzCqrhstBgE23pfNLvukYhpTPUKgZsXWLN5GsawqFZd4Fq3aVuGEHk11LhfMfmfBCFCBGrdZHVExjiB4FY5Jkjj1EYcqfTTNcczb | 500000 | + | DdzFFzCqrht74dr7DYmiyCobGFQcfLCsHJCCM6nEBTztrsEk5kwv48EWKVMFU9pswAkLX9CUs4yVhVxqZ7xCVDX1TdatFwX5W39cohvm | 500000 | + When I confirm Daedalus transfer funds + Then I should see the summary screen \ No newline at end of file diff --git a/features/step_definitions/daedalus-transfer-steps.js b/features/step_definitions/daedalus-transfer-steps.js index f492e039a7..cc91ecfd7c 100644 --- a/features/step_definitions/daedalus-transfer-steps.js +++ b/features/step_definitions/daedalus-transfer-steps.js @@ -66,6 +66,10 @@ When(/^I click on the transfer funds from Daedalus button$/, async function () { await this.click('.confirmButton'); }); +When(/^I click on the transfer funds from Daedalus master key button$/, async function () { + await this.click('.masterKey'); +}); + When(/^I proceed with the recovery$/, async function () { await this.click('.proceedTransferButtonClasses'); }); diff --git a/features/step_definitions/wallet-restoration-steps.js b/features/step_definitions/wallet-restoration-steps.js index 95904ccfbd..5fb9e44ddc 100644 --- a/features/step_definitions/wallet-restoration-steps.js +++ b/features/step_definitions/wallet-restoration-steps.js @@ -23,6 +23,11 @@ When(/^I enter the recovery phrase:$/, async function (table) { } }); +When(/^I enter the master key:$/, async function (table) { + const fields = table.hashes()[0]; + await this.input('input[name="masterKey"]', fields.masterKey); +}); + When(/^I enter one more word to the recovery phrase field:$/, async function (table) { const words = table.hashes()[0]; await this.input('.AutocompleteOverrides_autocompleteWrapper input', words.word); diff --git a/flow/declarations/CardanoCrypto.js b/flow/declarations/CardanoCrypto.js index 25c206c8d3..203b9d5dfc 100644 --- a/flow/declarations/CardanoCrypto.js +++ b/flow/declarations/CardanoCrypto.js @@ -63,6 +63,11 @@ declare module 'rust-cardano-crypto' { failed: boolean, msg: ?string }, + fromDaedalusMasterKey(masterKey: Uint8Array): { + result: CryptoWallet, + failed: boolean, + msg: ?string + }, fromSeed(seed: Array): { result: CryptoWallet, failed: boolean, diff --git a/js-cardano-wasm b/js-cardano-wasm index 2833341059..bd40ab0f8d 160000 --- a/js-cardano-wasm +++ b/js-cardano-wasm @@ -1 +1 @@ -Subproject commit 2833341059426b0aefc60e60b7d281f7ff6bde26 +Subproject commit bd40ab0f8d6ae27998c7ef5125c8c5cbd109a285 diff --git a/package-lock.json b/package-lock.json index c2a1fc7f8e..33ff06ea18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1331,6 +1331,58 @@ } } }, + "@cardano-foundation/ledgerjs-hw-app-cardano": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cardano-foundation/ledgerjs-hw-app-cardano/-/ledgerjs-hw-app-cardano-1.0.3.tgz", + "integrity": "sha512-pF6pIde9b6OycwVlR0PBW/XSpI+W6mG4fdKAtUuhXK2ldQi440oxPo1CCVcAirN1/s6vNOyt3P/DrdL572BNdQ==", + "requires": { + "@ledgerhq/hw-transport": "4.45.0", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "base-x": "3.0.5", + "node-int64": "0.4.0" + }, + "dependencies": { + "base-x": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.5.tgz", + "integrity": "sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "@ledgerhq/devices": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-4.45.0.tgz", + "integrity": "sha512-SrQKF1Goa5HbounjX4J1MZv8V4kQ28AhfIz22SjnEw/Hnsd6lXh3U4bUs+wGdbNlEKcLnqIWwS2Nrd4jc+6G6A==", + "requires": { + "@ledgerhq/errors": "4.45.0" + } + }, + "@ledgerhq/errors": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-4.45.0.tgz", + "integrity": "sha512-+QwmaY9tDnENFu13PploZlmobYZ6jcsqxghLkcHosVDiRZJYSdYkEpydmBQgHtzyBizl/CGz9TNb/XdAAZfHjw==" + }, + "@ledgerhq/hw-transport": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-4.45.0.tgz", + "integrity": "sha512-3lpQgt6kCzTUDW8otB5XZVnPBRGlj32kq8DvWKearVNGIuxReN63tB8GJBahdi0m+kc+TNlmH/2AX4m8B+aDeg==", + "requires": { + "@ledgerhq/devices": "4.45.0", + "@ledgerhq/errors": "4.45.0", + "events": "3.0.0" + }, + "dependencies": { + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" + } + } + }, "@octokit/rest": { "version": "15.18.1", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.18.1.tgz", @@ -2242,7 +2294,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "dev": true, "requires": { "babel-runtime": "6.26.0", "core-js": "2.6.3", @@ -2252,14 +2303,12 @@ "core-js": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.3.tgz", - "integrity": "sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ==", - "dev": true + "integrity": "sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ==" }, "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" } } }, @@ -2354,7 +2403,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.6.3", "regenerator-runtime": "0.11.1" @@ -2363,8 +2411,7 @@ "core-js": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.3.tgz", - "integrity": "sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ==", - "dev": true + "integrity": "sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ==" } } }, @@ -2616,6 +2663,15 @@ "unorm": "1.4.1" } }, + "bip39-light": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/bip39-light/-/bip39-light-1.0.7.tgz", + "integrity": "sha512-WDTmLRQUsiioBdTs9BmSEmkJza+8xfJmptsNJjxnoq3EydSa/ZBXT6rm66KoT3PJIRYMnhSKNR7S9YL1l7R40Q==", + "requires": { + "create-hash": "1.1.3", + "pbkdf2": "3.0.17" + } + }, "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", @@ -2688,6 +2744,25 @@ "type-is": "1.6.16" } }, + "borc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.0.tgz", + "integrity": "sha512-hKTxeYt3AIzIG45epJHv8xJYSF0ktp7nZgFsqi5cPzoL3T8qKMPeUlqydORy6j3NWZvRDANx30PjpTmGho69Gw==", + "requires": { + "bignumber.js": "8.1.1", + "commander": "2.15.0", + "ieee754": "1.1.12", + "iso-url": "0.4.6", + "json-text-sequence": "0.1.1" + }, + "dependencies": { + "bignumber.js": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-8.1.1.tgz", + "integrity": "sha512-QD46ppGintwPGuL1KqmwhR0O+N2cZUg8JG/VzwI2e28sM9TqHjQB10lI4QAaMHVbLzwVLLAwEglpKPViWX+5NQ==" + } + } + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -3114,6 +3189,16 @@ "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", "dev": true }, + "cardano-crypto.js": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cardano-crypto.js/-/cardano-crypto.js-5.0.1.tgz", + "integrity": "sha512-BagGgSSjf0+cFrJYX41A7yGEtdUr8crvYbfMFIubWqrB9HA0ZcXTqEXK5TgyDiASwjNkNoL0naNuHWB5PJur+w==", + "requires": { + "bip39-light": "1.0.7", + "borc": "2.1.0", + "pbkdf2": "3.0.17" + } + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -3615,8 +3700,7 @@ "commander": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.0.tgz", - "integrity": "sha512-7B1ilBwtYSbetCgTY1NJFg+gVpestg0fdA1MhC1Vs4ssyfSXnCAjFr+QcQM9/RedXC0EaUx1sG8Smgw2VfgKEg==", - "dev": true + "integrity": "sha512-7B1ilBwtYSbetCgTY1NJFg+gVpestg0fdA1MhC1Vs4ssyfSXnCAjFr+QcQM9/RedXC0EaUx1sG8Smgw2VfgKEg==" }, "commondir": { "version": "1.0.1", @@ -7721,8 +7805,7 @@ "ieee754": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", - "dev": true + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" }, "ignore": { "version": "4.0.6", @@ -8297,6 +8380,11 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "iso-url": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.6.tgz", + "integrity": "sha512-YQO7+aIe6l1aSJUKOx+Vrv08DlhZeLFIVfehG2L29KLSEb9RszqPXilxJRVpp57px36BddKR5ZsebacO5qG0tg==" + }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -11283,8 +11371,7 @@ "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" }, "node-libs-browser": { "version": "2.1.0", @@ -22448,6 +22535,20 @@ "fd-slicer": "1.0.1" } }, + "yoroi-extension-ledger-bridge": { + "version": "git+https://github.com/Emurgo/yoroi-extension-ledger-bridge.git#80508b83325cf835a716a851ae6c25d546e56096", + "requires": { + "@cardano-foundation/ledgerjs-hw-app-cardano": "1.0.3", + "events": "2.1.0" + }, + "dependencies": { + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" + } + } + }, "zip-stream": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", diff --git a/package.json b/package.json index 78e2f0546b..ae145b75d4 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,8 @@ "blakejs": "1.1.0", "bluebird": "3.3.4", "bs58": "4.0.1", + "cardano-crypto.js": "5.0.1", + "@cardano-foundation/ledgerjs-hw-app-cardano": "1.0.3", "cbor": "4.1.4", "classnames": "2.1.3", "crypto-random-string": "1.0.0", @@ -155,7 +157,8 @@ "trezor-connect": "7.0.1", "validator": "6.3.0", "unorm": "1.4.1", - "pbkdf2": "3.0.17" + "pbkdf2": "3.0.17", + "yoroi-extension-ledger-bridge": "git+https://github.com/Emurgo/yoroi-extension-ledger-bridge.git" }, "engineStrict": true, "engine": { diff --git a/setup_cardano_crypto.sh b/setup_cardano_crypto.sh index 139b7fbe69..fb402010a9 100755 --- a/setup_cardano_crypto.sh +++ b/setup_cardano_crypto.sh @@ -2,7 +2,7 @@ git submodule update --init --recursive && \ cd js-cardano-wasm && \ -git checkout 2833341059426b0aefc60e60b7d281f7ff6bde26 && \ +git checkout bd40ab0f8d6ae27998c7ef5125c8c5cbd109a285 && \ git submodule update cd rust && \ git checkout 9bef10d1bbd1321d98aa6b30ba030631806ad153 && \