Merge pull request #3390 from matiu/feat/trezor48

Feat/trezor48
This commit is contained in:
Gustavo Maximiliano Cortez 2015-11-10 20:23:20 -03:00
commit 8b1130d66b
31 changed files with 958 additions and 979 deletions

View File

@ -37,8 +37,7 @@ module.exports = function(grunt) {
'src/js/routes.js', 'src/js/routes.js',
'src/js/services/*.js', 'src/js/services/*.js',
'src/js/models/*.js', 'src/js/models/*.js',
'src/js/controllers/*.js', 'src/js/controllers/*.js'
'src/js/trezor.js'
], ],
tasks: ['concat:js'] tasks: ['concat:js']
} }
@ -79,7 +78,8 @@ module.exports = function(grunt) {
'src/js/translations.js', 'src/js/translations.js',
'src/js/version.js', 'src/js/version.js',
'src/js/init.js', 'src/js/init.js',
'src/js/trezor.js' 'src/js/trezor-url.js',
'bower_components/trezor-connect/login.js'
], ],
dest: 'public/js/copay.js' dest: 'public/js/copay.js'
}, },

28
baseDerivation.md Normal file
View File

@ -0,0 +1,28 @@
Copay accepts three base derivation paths:
* m/44'
* m/48' (only used for MULTISIGNATURE, HARDWARE Wallets)
* m/45' (deprecated and it is only supported for old wallets)
Both m/44 and m/48 follow the BIP44 standard:
m/XX'/<coin_type>'/<account'>
Supported cointypes are: 0: Livenet, and 1: Testnet
If you need to import a wallet from a mnemonic using an account different
from the default (0), use, for example:
m/44'/0'/11'
to import account 11.
In case you have a multisignature wallet originally created from a hardware device, and you had loose access to the device, you will need to enter the 24 mnemonic backup (from the device) and a path like:
m/48'/0'/8'
for a multisignature wallet, account 8.
Finally, note that TREZOR use 1-based account numbers, so if your are trying for example to recover TREZOR multisig account #8, you should enter `m/48'/0'/7'`.

View File

@ -8,7 +8,7 @@
], ],
"dependencies": { "dependencies": {
"angular": "1.4.6", "angular": "1.4.6",
"angular-bitcore-wallet-client": "1.1.2", "angular-bitcore-wallet-client": "1.1.6",
"angular-foundation": "0.7.0", "angular-foundation": "0.7.0",
"angular-gettext": "2.1.0", "angular-gettext": "2.1.0",
"angular-moment": "0.10.1", "angular-moment": "0.10.1",
@ -21,6 +21,7 @@
"foundation-icon-fonts": "*", "foundation-icon-fonts": "*",
"moment": "2.10.3", "moment": "2.10.3",
"ng-lodash": "0.2.3", "ng-lodash": "0.2.3",
"qrcode-decoder-js": "*" "qrcode-decoder-js": "*",
"trezor-connect": "~1.0.1"
} }
} }

View File

@ -55,6 +55,12 @@ echo $CMD
$CMD $CMD
checkOK checkOK
cd $BUILDDIR/../..
CMD="rsync -rLRv ./bower_components/trezor-connect/chrome/* $APPDIR"
echo $CMD
$CMD
checkOK
# Zipping chrome-extension # Zipping chrome-extension
echo "${OpenColor}${Green}* Zipping all chrome-extension files...${CloseColor}" echo "${OpenColor}${Green}* Zipping all chrome-extension files...${CloseColor}"
cd $BUILDDIR cd $BUILDDIR

View File

@ -6,7 +6,8 @@
"permissions": [ "permissions": [
"storage", "storage",
"notifications", "notifications",
"videoCapture" "videoCapture",
"webview"
], ],
"app": { "app": {
"background": { "background": {

View File

@ -56,21 +56,19 @@
</span> </span>
</div> </div>
<div class="row"> <div ng-hide="create.hideWalletName">
<div class="large-12 columns" ng-hide="create.hideWalletName"> <label><span translate>Wallet name</span>
<label><span translate>Wallet name</span> <div class="input">
<div class="input"> <input type="text" placeholder="{{'Family vacation funds'|translate}}" class="form-control" name="walletName" ng-model="walletName" ng-required="true" ng-focus="create.formFocus('wallet-name')" ng-blur="create.formFocus(false)">
<input type="text" placeholder="{{'Family vacation funds'|translate}}" class="form-control" name="walletName" ng-model="walletName" ng-required="true" ng-focus="create.formFocus('wallet-name')" ng-blur="create.formFocus(false)"> </div>
</div> </label>
</label> </div>
</div> <div ng-show="totalCopayers != 1">
<div class="large-12 columns" ng-show="totalCopayers != 1"> <label><span translate>Your nickname</span>
<label><span translate>Your nickname</span> <div class="input">
<div class="input"> <input type="text" placeholder="{{'John'|translate}}" class="form-control" name="myName" ng-model="myName" ng-required="totalCopayers != 1" ng-disabled="totalCopayers == 1" ng-focus="create.formFocus('my-name')" ng-blur="create.formFocus(false)">
<input type="text" placeholder="{{'John'|translate}}" class="form-control" name="myName" ng-model="myName" ng-required="totalCopayers != 1" ng-disabled="totalCopayers == 1" ng-focus="create.formFocus('my-name')" ng-blur="create.formFocus(false)"> </div>
</div> </label>
</label>
</div>
</div> </div>
<div class="row" ng-show="totalCopayers != 1"> <div class="row" ng-show="totalCopayers != 1">
<div class="large-6 medium-6 columns"> <div class="large-6 medium-6 columns">
@ -98,74 +96,80 @@
<i ng-if="!hideAdv" class="icon-arrow-up4"></i> <i ng-if="!hideAdv" class="icon-arrow-up4"></i>
</a> </a>
</div> </div>
<div ng-hide="hideAdv" class="row"> <div ng-hide="hideAdv" class="row">
<div class="large-12 columns"> <div class="large-12 columns">
<div>
<label ng-show="index.isChromeApp && totalCopayers > 1 " for="hw-ledger" class="oh"> <label for="bws" class="oh">
<span translate>Use Ledger hardware wallet</span> <span>Wallet Service URL</span>
<switch id="hw-ledger" name="hwLedger" ng-model="hwLedger" ng-change="isTestnet=false" class="green right m5t m10b"></switch> <input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
</label>
<!-- -->
<label ng-show="!index.isCordova && 0" for="hw-trezor" class="oh">
<span translate>Use TREZOR hardware wallet</span>
<switch id="hw-trezor" name="hwTrezor" ng-model="hwTrezor" class="green right m5t m10b"></switch>
</label>
<!-- TODO account
<div ng-show="hwLedger">
<label class="oh"><span translate>Ledger Slot</span>
<select class="m10t" ng-model="externalIndex" ng-options="externalIndex as externalIndex for externalIndex in create.externalIndexValues">
</select>
</label> </label>
<div class="oh text-gray line-b size-12 p10b m20b"><span translate>Ledger supports up to 20 Copay wallets simultaneously. Select which slot should be used to host this wallet</div>
</div> </div>
-->
<label for="network-name" class="oh" ng-show="!hwLedger && !hwTrezor">
<span translate>Testnet</span>
<switch id="network-name" name="isTestnet" ng-model="isTestnet" class="green right m5t m10b"></switch>
</label>
<label ng-show="!hwLedger && !hwTrezor" for="seed" class="oh"> <div>
<span translate>Specify your wallet seed</span> <label><span translate>Wallet Seed</span>
<switch id="seed" name="setSeed" ng-model="setSeed" class="green right m5t m10b"></switch> <select class="m10t" ng-model="seedSource"
</label> ng-options="seed as seed.label for seed in create.seedOptions"
ng-change="create.setSeedSource()">
</select>
</label>
</div>
<label for="createPassphrase" class="oh" ng-hide="setSeed || hwLedger || hwTrezor" ><span translate>Add a Seed Passphrase</span> <small translate>Add an optional passphrase to secure the seed</small> <div ng-show="create.seedSourceId == 'trezor' || create.seedSourceId == 'ledger'">
<div class="input"> <label class="oh"><span translate>Account Number</span>
<input type="text" class="form-control" <input type="number" id="account" ng-model="account">
name="createPassphrase" ng-model="createPassphrase"> </label>
</div> </div>
</label>
<label for="ext-master" class="m10t" ng-show="setSeed"> <div class="box-notification" ng-show="create.seedSourceId=='new' && createPassphrase">
<span translate>Wallet Seed</span> <span class="text-warning size-14">
<small translate>Enter the seed words (BIP39)</small> <i class="fi-alert"></i>
<input id="ext-master" <span translate>
type="text" WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase.
name="privateKey" ng-model="privateKey"> </span>
</label> </span>
<label for="passphrase" class="oh" ng-show="setSeed"><span translate>Seed Passphrase</span> <small translate>The seed could require a passphrase to be imported</small> </div>
<div class="input">
<input type="text" class="form-control" name="passphrase" ng-model="passphrase">
</div>
</label>
<label for="bws" class="oh"> <div ng-show="create.seedSourceId=='new' ">
<span>Wallet Service URL</span> <label for="createPassphrase" ><span translate>Add a Seed Passphrase</span> <small translate>Add an optional passphrase to secure the seed</small>
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl"> <div class="input">
</label> <input type="text" class="form-control"
</div> name="createPassphrase" ng-model="createPassphrase">
</div> </div>
</label>
</div>
<div class="box-notification" ng-show="!setSeed && createPassphrase"> <div ng-show="create.seedSourceId=='set'">
<span class="text-warning size-14"> <label for="ext-master">
<i class="fi-alert"></i> <span translate>Wallet Seed</span>
<span translate> <small translate>Enter the seed words (BIP39)</small>
WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase. <input id="ext-master"
</span> type="text"
</span> name="privateKey" ng-model="privateKey">
</div> </label>
</div>
<div ng-show="create.seedSourceId=='set'">
<label for="passphrase"> <span translate>Seed Passphrase</span> <small translate>The seed could require a passphrase to be imported</small>
<div class="input">
<input type="text" class="form-control" name="passphrase" ng-model="passphrase">
</div>
</label>
</div>
<div ng-show="create.seedSourceId == 'set'">
<label class="oh"><span translate>Derivation Path</span> <small translate>BIP32 path for address derivation</small>
<input type="text" class="form-control" name="derivationPath" ng-model="derivationPath">
</label>
</div>
<div class="oh" ng-show="create.seedSourceId == 'new'">
<label for="network-name" >
<span translate>Testnet</span>
<switch id="network-name" name="isTestnet" ng-model="isTestnet" class="green right m5t m10b"></switch>
</label>
</div>
</div> <!-- columns -->
</div> <!-- advanced -->
<button type="submit" class="button round black expand m0" ng-show="totalCopayers != 1" ng-disabled="setupForm.$invalid || create.loading || create.hwWallet"> <button type="submit" class="button round black expand m0" ng-show="totalCopayers != 1" ng-disabled="setupForm.$invalid || create.loading || create.hwWallet">
<span translate>Create {{requiredCopayers}}-of-{{totalCopayers}} wallet</span> <span translate>Create {{requiredCopayers}}-of-{{totalCopayers}} wallet</span>
@ -174,8 +178,9 @@
<button type="submit" class="button round black expand m0" ng-show="totalCopayers == 1" ng-disabled="setupForm.$invalid || create.loading || create.hwWallet"> <button type="submit" class="button round black expand m0" ng-show="totalCopayers == 1" ng-disabled="setupForm.$invalid || create.loading || create.hwWallet">
<span translate>Create new wallet</span> <span translate>Create new wallet</span>
</button> </button>
</div>
</div> </div> <!-- large-12 columns -->
</div> <!-- row -->
</form> </form>
</div> </div>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>

View File

@ -45,7 +45,7 @@
</form> </form>
<h4></h4> <h4></h4>
<ul class="no-bullet m0" ng-hide="index.isPrivKeyExternal"> <ul class="no-bullet m0" ng-show="index.canSign">
<li> <li>
<switch id="no-sign" name="noSign" ng-model="noSign" class="green right"></switch> <switch id="no-sign" name="noSign" ng-model="noSign" class="green right"></switch>
<span translate>Do not include private key</span> <span translate>Do not include private key</span>
@ -53,7 +53,7 @@
</ul> </ul>
<div class="box-notification" ng-show="index.isPrivKeyExternal"> <div class="box-notification" ng-show="!index.canSign">
<span class="text-warning size-14"> <span class="text-warning size-14">
<i class="fi-alert"></i> <i class="fi-alert"></i>
<span translate> <span translate>

View File

@ -30,8 +30,6 @@
</div> </div>
</div> </div>
<div class="create-tab pr small-only-text-center" ng-hide="create.hideTabs"> <div class="create-tab pr small-only-text-center" ng-hide="create.hideTabs">
<div class="row"> <div class="row">
<div class="tab-container small-4 medium-4 large-4" ng-class="{'selected': type =='12'}"> <div class="tab-container small-4 medium-4 large-4" ng-class="{'selected': type =='12'}">
@ -46,21 +44,21 @@
</div> </div>
</div> </div>
<div class="row" ng-show="type == '12' "> <div class="row" ng-show="type == '12' ">
<div class="large-12 columns"> <div class="large-12 columns">
<form name="importForm12" ng-submit="import.importMnemonic(importForm12)" novalidate> <form name="importForm12" ng-submit="import.importMnemonic(importForm12)" novalidate>
<div class="box-notification" ng-show="import.error"> <div class="box-notification" ng-show="import.error">
<span class="text-warning size-14"> <span class="text-warning size-14">
{{import.error|translate}} {{import.error|translate}}
</span> </span>
</div> </div>
<div > <div >
<label for="words"> <label for="words">
<span translate>Type the Seed Word (usually 12 words)</span>: <span translate>Type the Seed Word (usually 12 words)</span>:
</label> </label>
<textarea class="form-control" name="words" ng-model="import.words" rows="2"></textarea> <textarea class="form-control" name="words" ng-model="import.words" rows="2"></textarea>
</div> </div>
<div class="m10t oh" ng-init="hideAdv=true"> <div class="m10t oh" ng-init="hideAdv=true">
<a class="button outline light-gray expand tiny" ng-click="hideAdv=!hideAdv"> <a class="button outline light-gray expand tiny" ng-click="hideAdv=!hideAdv">
@ -73,18 +71,78 @@
</div> </div>
<div ng-hide="hideAdv" class="row"> <div ng-hide="hideAdv" class="row">
<div class="large-12 columns"> <div class="large-12 columns">
<label for="network-name" class=" oh">
<span translate>Testnet</span>
<switch id="network-name" name="isTestnet" ng-model="isTestnet" class="green right m5t m10b"></switch>
</label>
<label for="passphrase" class="oh"><span translate>Passphrase</span> <small translate>Wallet Seed could require a passphrase to be imported</small> <label for="passphrase" class="oh"><span translate>Passphrase</span> <small translate>Wallet Seed could require a passphrase to be imported</small>
<div class="input"> <div class="input">
<input type="password" class="form-control" placeholder="{{'Seed passphrase'|translate}}" <input type="password" class="form-control" placeholder="{{'Seed passphrase'|translate}}"
name="passphrase" ng-model="import.passphrase"> name="passphrase" ng-model="import.passphrase">
</div> </div>
</label> </label>
<div>
<label class="oh"><span translate>Derivation Path</span> <small translate>BIP32 path for address derivation</small>
<input type="text" class="form-control" name="derivationPath" ng-model="derivationPath">
</label>
</div>
<label for="bws" class="oh">
<span>Wallet Service URL</span>
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
</label>
</div>
</div>
<button translate type="submit" class="button round expand black m10t"
ng-disabled="importForm12.$invalid || import.loading">
Import
</button>
</form>
</div>
</div>
<div class="row" ng-show="type == 'file' ">
<div class="large-12 columns">
<form name="importForm" ng-submit="import.importBlob(importForm)" novalidate>
<div class="box-notification" ng-show="import.error">
<span class="text-warning size-14">
{{import.error|translate}}
</span>
</div>
<div ng-show="!index.isSafari && !index.isCordova" class="line-b m10b">
<label for="backupFile">
<span translate>Choose a backup file from your computer</span> <i class="fi-laptop"></i>
</label>
<input type="file" class="form-control" placeholder="{{'Select a backup file'|translate}}"
name="backupFile" ng-model="import.backupFile" ng-file-select>
</div>
<div ng-show="index.isSafari || index.isCordova">
<label for="backupText">
<span translate>Paste the backup plain text code</span> <i class="fi-clipboard"></i>
</label>
<textarea class="form-control" name="backupText" ng-model="import.backupText" rows="5"></textarea>
</div>
<label for="password"><span translate>Password</span>
</label>
<div class="input">
<input type="password" class="form-control" placeholder="{{'Your backup password'|translate}}"
name="password" ng-model="import.password">
</div>
<div class="m10t oh" ng-init="hideAdv=true">
<a class="button outline light-gray expand tiny" ng-click="hideAdv=!hideAdv">
<i class="fi-widget m3r"></i>
<span translate ng-hide="!hideAdv">Show advanced options</span>
<span translate ng-hide="hideAdv">Hide advanced options</span>
<i ng-if="hideAdv" class="icon-arrow-down4"></i>
<i ng-if="!hideAdv" class="icon-arrow-up4"></i>
</a>
</div>
<div ng-hide="hideAdv" class="row">
<div class="large-12 columns">
<label for="bws" class="oh"> <label for="bws" class="oh">
<span>Wallet Service URL</span> <span>Wallet Service URL</span>
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl"> <input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
@ -92,45 +150,57 @@
</div> </div>
</div> </div>
<button translate type="submit" class="button round expand black m10t" <button translate type="submit" class="button round expand black"
ng-disabled="importForm12.$invalid || import.loading"> ng-disabled="importForm.$invalid || !import.password || import.loading">
Import Import backup
</button> </button>
</form> </form>
</div>
<div class="text-center text-gray p20v" ng-click="$root.go('importLegacy')">
<p class="text-gray m5b size-14" translate> Have a Backup from Copay v0.9?</p>
<button class=" outline dark-gray tiny round"> <span translate>Import here</span>
<i class="icon-arrow-right3 size-14"></i>
</button>
</div>
</div> </div>
</div>
<div class="row" ng-show="type == 'hwWallet'">
<div class="large-12 columns">
<div class="box-notification" ng-show="import.error">
<span class="text-warning size-14">
{{import.error|translate}}
</span>
</div>
<form name="importForm3" ng-submit="import.importHW(importForm3)" novalidate>
<div class="row" ng-show="type == 'file' "> <div class="large-12 columns">
<div class="large-12 columns"> <div ng-show="!import.seedOptions[0]">
<form name="importForm" ng-submit="import.importBlob(importForm)" novalidate> <span translate>No harware wallets supported on this device</span>
<div class="box-notification" ng-show="import.error"> </div>
<span class="text-warning size-14"> <div ng-show="import.seedOptions[0]">
{{import.error|translate}} <div>
</span> <label><span translate>Wallet Seed</span>
</div> <select class="m10t" ng-model="seedSource"
ng-options="seed as seed.label for seed in import.seedOptions"
<div ng-show="!index.isSafari && !index.isCordova" class="line-b m10b"> ng-change="import.setSeedSource()">
<label for="backupFile"> </select>
<span translate>Choose a backup file from your computer</span> <i class="fi-laptop"></i>
</label> </label>
<input type="file" class="form-control" placeholder="{{'Select a backup file'|translate}}"
name="backupFile" ng-model="import.backupFile" ng-file-select>
</div> </div>
<div ng-show="index.isSafari || index.isCordova"> <div ng-show="import.seedSourceId == 'trezor' || import.seedSourceId == 'ledger'">
<label for="backupText">
<span translate>Paste the backup plain text code</span> <i class="fi-clipboard"></i> <label class="oh"><span translate>Account Number</span>
<input type="number" id="account" ng-model="account">
</label> </label>
<textarea class="form-control" name="backupText" ng-model="import.backupText" rows="5"></textarea>
</div> </div>
<label for="password"><span translate>Password</span> <div ng-show="import.seedSourceId == 'trezor'">
</label> <label for="isMultisig" class="oh">
<div class="input"> <span translate>Shared Wallet</span>
<input type="password" class="form-control" placeholder="{{'Your backup password'|translate}}" <switch id="isMultisig" name="isMultisig" ng-model="isMultisig" class="green right m5t m10b"></switch>
name="password" ng-model="import.password"> </label>
</div> </div>
<div class="m10t oh" ng-init="hideAdv=true"> <div class="m10t oh" ng-init="hideAdv=true">
@ -152,100 +222,16 @@
</div> </div>
</div> </div>
<button translate type="submit" class="button round expand black"
ng-disabled="importForm.$invalid || !import.password || import.loading">
Import backup
</button>
</form>
<div class="text-center text-gray p20v" ng-click="$root.go('importLegacy')">
<p class="text-gray m5b size-14" translate> Have a Backup from Copay v0.9?</p>
<button class=" outline dark-gray tiny round"> <span translate>Import here</span>
<i class="icon-arrow-right3 size-14"></i>
</button>
</div>
</div>
</div>
<div class="row" ng-show="type == 'hwWallet'">
<div class="large-12 columns">
<div class="box-notification" ng-show="import.error">
<span class="text-warning size-14">
{{import.error|translate}}
</span>
</div>
<form name="importForm3" ng-submit="import.importLedger(importForm3)" ng-show="index.isChromeApp" novalidate>
<div class="large-12 columns">
<!-- TODO: account
<label class=" oh">
<span translate>Ledger Slot</span>
<select class="m10t" ng-model="externalIndex" ng-options="externalIndex as externalIndex for externalIndex in import.externalIndexValues">
</select>
</label>
<div class="oh text-gray line-b size-12 p10b m20b"><span translate>Ledger supports up to 20 Copay wallets simultaneously. Select which slot to import</div>
-->
<button translate type="submit" class="button round expand black" <button translate type="submit" class="button round expand black"
ng-disabled="import.loading || import.ledger"> ng-disabled="import.loading || import.ledger">
Import from Ledger Import
</button> </button>
</div> <!-- seedoptions show -->
<div class="m10t oh" ng-init="hideAdvLedger=true"> </div>
<a class="button outline light-gray expand tiny" ng-click="hideAdvLedger=!hideAdvLedger"> </form>
<i class="fi-widget m3r"></i>
<span translate ng-hide="!hideAdvLedger">Show advanced options</span>
<span translate ng-hide="hideAdvLedger">Hide advanced options</span>
<i ng-if="hideAdvLedger" class="icon-arrow-down4"></i>
<i ng-if="!hideAdvLedger" class="icon-arrow-up4"></i>
</a>
</div>
<div ng-hide="hideAdvLedger" class="row">
<div class="large-12 columns">
<label for="bws" class="oh">
<span>Wallet Service URL</span>
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
</label>
</div>
</div>
</div>
</form>
<form name="importForm4" ng-submit="import.importTrezor(importForm4)" novalidate>
<div class="large-12 columns">
<div class="m10t oh" ng-init="hideAdvTrezor=true">
<a class="button outline light-gray expand tiny" ng-click="hideAdvTrezor=!hideAdvTrezor">
<i class="fi-widget m3r"></i>
<span translate ng-hide="!hideAdvTrezor">Show advanced options</span>
<span translate ng-hide="hideAdvTrezor">Hide advanced options</span>
<i ng-if="hideAdvTrezor" class="icon-arrow-down4"></i>
<i ng-if="!hideAdvTrezor" class="icon-arrow-up4"></i>
</a>
</div>
<div ng-hide="hideAdvTrezor" class="row">
<div class="large-12 columns">
<label for="bws" class="oh">
<span>Wallet Service URL</span>
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
</label>
</div>
</div>
<!-- ng-disabled="import.loading || import.ledger" -->
<button translate type="submit" class="button round expand black" ng-disabled="true">
Import from TREZOR
</button>
</div>
</form>
</div>
</div> </div>
</div>
</div> </div>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>

View File

@ -1,6 +1,16 @@
<span ng-show="index.network != 'livenet'"> Testnet </span>
<span ng-show="!index.canSign && !index.isPrivKeyExternal" translate>No Private key</span> <span ng-show="index.isShared"><span translate>{{index.m}}-of-{{index.n}}</span></span>
<div ng-show="index.isPrivKeyExternal" style="text-transform: capitalize"> <img style="height:1em" ng-show="index.network != 'livenet'" src="img/icon-testnet.svg">
<span translate>External Private Key:</span> <img style="height:1em" ng-show="!index.canSign && !index.isPrivKeyExternal" src="img/icon-read-only.svg">
{{index.externalSource}}
</div> <img style="height:1em" ng-show="index.externalSource == 'trezor'" src="img/icon-trezor.svg">
<img style="height:1em" ng-show="index.externalSource == 'ledger'" src="img/icon-ledger.svg">
<span style="height:1em" ng-show="index.account">#{{index.account || 0}} </span>
<img style="height:1em" ng-show="index.isPrivKeyEncrypted" src="img/icon-lock.svg">
<!-- <img style="height:1em" ng&#45;show="index.preferences.email" src="img/icon&#45;email.svg"> -->
<img style="height:1em" ng-show="index.usingCustomBWS" src="img/icon-bws.svg">
<img style="height:1em" class="animated flash infinite" ng-show="index.updatingTxHistory[index.walletId]" src="img/icon-sync.svg">

View File

@ -32,118 +32,128 @@
</div> </div>
</div> </div>
<div class="row"> <form name="joinForm" ng-submit="join.join(joinForm)" novalidate>
<div class="large-12 columns"> <div class="row">
<div class="large-12 columns">
<div class="box-notification" ng-show="join.error ">
<span class="text-warning size-14">
{{join.error|translate}}
</span>
</div>
<div class="box-notification" ng-show="join.error "> <div>
<span class="text-warning size-14">
{{join.error|translate}}
</span>
</div>
<form name="joinForm" ng-submit="join.join(joinForm)" novalidate>
<label><span translate>Your nickname</span> <label><span translate>Your nickname</span>
<div class="input"> <div class="input">
<input type="text" placeholder="{{'John'|translate}}" class="form-control" name="myName" ng-model="myName" ng-required="true"> <input type="text" placeholder="{{'John'|translate}}" class="form-control" name="myName" ng-model="myName" ng-required="true">
</div> </div>
</label> </label>
</div>
<div class="row collapse"> <div class="row collapse">
<label for="secret" class="left"><span translate>Wallet Invitation</span> <label for="secret" class="left"><span translate>Wallet Invitation</span>
<small translate ng-show="joinForm.secret.$pristine">Required</small> <small translate ng-show="joinForm.secret.$pristine">Required</small>
</label> </label>
<span class="has-error right size-12" ng-show="joinForm.secret.$invalid <span class="has-error right size-12" ng-show="joinForm.secret.$invalid
&& !joinForm.secret.$pristine"> && !joinForm.secret.$pristine">
<span class="icon-input"><i class="fi-x"></i></span> <span class="icon-input"><i class="fi-x"></i></span>
<span translate>Wallet Invitation is not valid!</span> <span translate>Wallet Invitation is not valid!</span>
</span> </span>
<small class="icon-input right" ng-show="joinForm.secret.$valid <small class="icon-input right" ng-show="joinForm.secret.$valid
&& !joinForm.secret.$pristine"><i class="fi-check"></i></small> && !joinForm.secret.$pristine"><i class="fi-check"></i></small>
</div>
<div class="input">
<input id="secret" type="text" placeholder="{{'Paste invitation here'|translate}}" name="secret" ng-model="secret" wallet-secret required>
<div class="qr-scanner-input">
<qr-scanner on-scan="join.onQrCodeScanned(data)"></qr-scanner>
</div> </div>
</div>
<div class="input"> <div class="m10t oh" ng-init="hideAdv=true">
<input id="secret" type="text" placeholder="{{'Paste invitation here'|translate}}" name="secret" ng-model="secret" wallet-secret required> <a class="button outline light-gray expand tiny" ng-click="hideAdv=!hideAdv">
<div class="qr-scanner-input">
<qr-scanner on-scan="join.onQrCodeScanned(data)"></qr-scanner>
</div>
</div>
<a class="button outline light-gray tiny expand" ng-click="join.hideAdv=!join.hideAdv">
<i class="fi-widget m3r"></i> <i class="fi-widget m3r"></i>
<span translate ng-show="!join.hideAdv">Show advanced options</span> <span translate ng-hide="!hideAdv">Show advanced options</span>
<span translate ng-show="join.hideAdv">Hide advanced options</span> <span translate ng-hide="hideAdv">Hide advanced options</span>
<i ng-show="!join.hideAdv" class="icon-arrow-down4"></i> <i ng-if="hideAdv" class="icon-arrow-down4"></i>
<i ng-show="join.hideAdv" class="icon-arrow-up4"></i> <i ng-if="!hideAdv" class="icon-arrow-up4"></i>
</a> </a>
<div ng-show="join.hideAdv" class="row"> </div>
<div class="large-12 columns">
<label for="hw-ledger" class="oh" ng-show="index.isChromeApp"> <div ng-hide="hideAdv" class="row">
<span translate>Use Ledger hardware wallet</span> <div class="large-12 columns">
<switch id="hw-ledger" name="hwLedger" ng-model="hwLedger" class="green right m5t m10b"></switch> <div>
</label> <label for="bws" class="oh">
<span>Wallet Service URL</span>
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
</label>
</div>
<label ng-show="!index.isCordova && 0" for="hw-trezor" class="oh"> <div>
<span translate>Use TREZOR hardware wallet</span> <label><span translate>Wallet Seed</span>
<switch id="hw-trezor" name="hwTrezor" ng-model="hwTrezor" class="green right m5t m10b"></switch> <select class="m10t" ng-model="seedSource"
</label> ng-options="seed as seed.label for seed in join.seedOptions"
ng-change="join.setSeedSource()">
</select>
</label>
</div>
<!-- TODO account <div ng-show="join.seedSourceId == 'trezor' || join.seedSourceId == 'ledger'">
<div class="large-12 columns" ng-hide="!hwLedger">
<label class="oh"> <label class="oh"><span translate>Account</span>
<span translate>Ledger Slot</span> <select class="m10t" ng-model="account" ng-options="externalIndex as externalIndex for externalIndex in join.accountValues">
<select class="m10t" ng-model="externalIndex" ng-options="externalIndex as externalIndex for externalIndex in join.externalIndexValues">
</select> </select>
</label> </label>
<div class="oh text-gray line-b size-12 p10b m20b"><span translate>Ledger supports up to 20 Copay wallets simultaneously. Select which slot should be used to host this wallet</div> <div class="oh text-gray line-b size-12 p10b m20b"><span translate>Multiple wallets accounts are supported on the device simultaneously. Select which account should be used for this wallet</div>
</div> </div>
-->
<label ng-show="!hwLedger && !hwTrezor" for="seed" class="oh">
<span translate>Specify your wallet seed</span>
<switch id="seed" name="setSeed" ng-model="setSeed" class="green right m5t m10b"></switch>
</label>
<label for="createPassphrase" class="oh" ng-show="!setSeed && !hwLedger && !hwTrezor" ><span translate>Add a Seed Passphrase</span> <small translate>Add an optional passphrase to secure the seed</small> <div class="box-notification" ng-show="join.seedSourceId=='new' && createPassphrase">
<div class="input"> <span class="text-warning size-14">
<input type="text" class="form-control" <i class="fi-alert"></i>
name="createPassphrase" ng-model="createPassphrase"> <span translate>
</div> WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase.
</label> </span>
</span>
</div>
<label for="ext-master" class="m10t" ng-show="setSeed"> <div ng-show="join.seedSourceId=='new' ">
<span translate>Wallet Seed</span> <label for="createPassphrase" ><span translate>Add a Seed Passphrase</span> <small translate>Add an optional passphrase to secure the seed</small>
<small translate>Enter the seed words (BIP39)</small> <div class="input">
<input id="ext-master" <input type="text" class="form-control"
type="text" name="createPassphrase" ng-model="createPassphrase">
name="privateKey" ng-model="privateKey"> </div>
</label> </label>
<label for="passphrase" class="oh" ng-show="setSeed"><span translate>Seed Passphrase</span> <small translate>The seed could require a passphrase to be imported</small> </div>
<div class="input">
<input type="text" class="form-control" name="passphrase" ng-model="passphrase">
</div>
</label>
<label for="bws" class="oh"> <div ng-show="join.seedSourceId=='set'">
<span>Wallet Service URL</span> <label for="ext-master">
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl"> <span translate>Wallet Seed</span>
</label> <small translate>Enter the seed words (BIP39)</small>
</div> <input id="ext-master"
</div> type="text"
name="privateKey" ng-model="privateKey">
</label>
</div>
<div class="box-notification" ng-show="!setSeed && createPassphrase"> <div ng-show="join.seedSourceId=='set'">
<span class="text-warning size-14"> <label for="passphrase"> <span translate>Seed Passphrase</span> <small translate>The seed could require a passphrase to be imported</small>
<i class="fi-alert"></i> <div class="input">
<span translate> <input type="text" class="form-control" name="passphrase" ng-model="passphrase">
WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase. </div>
</span> </label>
</span> </div>
</div> <div ng-show="join.seedSourceId == 'set'">
<label class="oh"><span translate>Derivation Path</span> <small translate>BIP32 path for address derivation</small>
<input type="text" class="form-control" name="derivationPath" ng-model="derivationPath">
</label>
</div>
</div> <!-- columns -->
</div> <!-- advanced -->
<button translate type="submit" class="button expand black m0 round" <button translate type="submit" class="button expand black m0 round"
ng-disabled="joinForm.$invalid || join.loading">Join</button> ng-disabled="joinForm.$invalid || join.loading">Join</button>
</form> </div> <!-- large-12 columns -->
</div> </div> <!-- row -->
</div> </form>
</div> </div>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>

View File

@ -1,28 +0,0 @@
<nav class="tab-bar">
<section class="left-small">
<a ng-click="cancel()" class="p10">
<span class="text-close" translate>Close</span>
</a>
</section>
<section class="middle tab-bar-section">
<h1 class="title ellipsis" ng-style="{'color':color}" translate>
Copayers
</h1>
</section>
</nav>
<div class="modal-content fix-modals-touch">
<ul class="no-bullet">
<li class="line-b p10" ng-repeat="copayer in copayers">
<span class="size-12" ng-show="copayer.id == copayerId">
<i class="icon-contact size-24 m10r"></i> {{copayer.name}} ({{'Me'|translate}}) <i class="fi-check m5 right"></i>
</span>
<span class="size-12 text-gray" ng-show="copayer.id != copayerId">
<i class="icon-contact size-24 m10r"></i> {{copayer.name}}<i class="fi-check m5 right"></i>
</span>
</li>
</ul>
<div class="extra-margin-bottom"></div>
</div>

View File

@ -62,12 +62,12 @@
<div translate>Advanced</div> <div translate>Advanced</div>
</li> </li>
</ul> </ul>
<div ng-show="!index.noFocusedWallet && (!index.isPrivKeyExternal || preferences.touchidAvailable)"> <div ng-show="!index.noFocusedWallet && index.canSign">
<h4 translate> <h4 translate ng-show="index.canSign">
Spending Restrictions Spending Restrictions
</h4> </h4>
<ul class="no-bullet m0 "> <ul class="no-bullet m0">
<li ng-show="!index.isPrivKeyExternal"> <li>
<switch id="network-name" name="encrypt" ng-model="encrypt" class="green right"></switch> <switch id="network-name" name="encrypt" ng-model="encrypt" class="green right"></switch>
<div translate>Request Password</div> <div translate>Request Password</div>
</li> </li>

View File

@ -15,6 +15,7 @@
</span> </span>
</li> </li>
<li class="line-b p20 oh"> <li class="line-b p20 oh">
<span translate>Wallet Id</span> <span translate>Wallet Id</span>
<span class="right text-gray enable_text_select"> <span class="right text-gray enable_text_select">
@ -29,6 +30,8 @@
</span> </span>
</li> </li>
<li class="line-b p20 oh"> <li class="line-b p20 oh">
<span translate>Wallet Network</span> <span translate>Wallet Network</span>
<span class="right text-gray"> <span class="right text-gray">
@ -52,6 +55,39 @@
</span> </span>
</li> </li>
<li class="line-b p20 oh" ng-show="index.externalSource">
<span translate>Hardware Wallet</span>
<span class="right text-gray capitalize">
{{index.externalSource}}
</span>
</li>
<li class="line-b p20 oh" ng-show="!index.externalSource && !index.canSign">
<span translate></span>
<span class="right text-gray capitalize">
No private key
</span>
</li>
<li class="line-b p20 oh" ng-show="index.account">
<span translate>Account ({{derivationStrategy}})</span>
<span class="right text-gray">
#{{index.account}}
</span>
</li>
<h4 class="title m0" translate>Copayers</h4>
<li ng-repeat="copayer in index.copayers">
<span class="size-12" ng-show="copayer.id == index.copayerId">
<i class="icon-contact size-24 m10r"></i> {{copayer.name}} ({{'Me'|translate}}) <i class="fi-check m5 right"></i>
</span>
<span class="size-12 text-gray" ng-show="copayer.id != index.copayerId">
<i class="icon-contact size-24 m10r"></i> {{copayer.name}}<i class="fi-check m5 right"></i>
</span>
</li>
<h4 class="title m0" translate>Extended Public Keys</h4> <h4 class="title m0" translate>Extended Public Keys</h4>
<li ng-repeat="pk in pubKeys"> <li ng-repeat="pk in pubKeys">
<div class="row collapse"> <div class="row collapse">

View File

@ -118,13 +118,11 @@
</a> </a>
</div> </div>
<div class="wallet-info"> <div class="wallet-info">
<div ng-show="index.isShared" ng-click="openCopayersModal(index.copayers, index.copayerId)"> <div ng-show="index.isShared">
<p class="m0"> <p class="m0">
{{(index.alias || index.walletName)}} {{(index.alias || index.walletName)}}
</p> </p>
<div class="size-12 text-gray"> <div class="size-12 text-gray">
<span translate>Multisignature wallet</span>
(<span translate>{{index.m}}-of-{{index.n}}</span>)
<span ng-include="'views/includes/walletInfo.html'"></span> <span ng-include="'views/includes/walletInfo.html'"></span>
</div> </div>
</div> </div>
@ -444,7 +442,7 @@
--> -->
<div id="history" class="history tab-view"> <div id="history" class="history tab-view">
<div class="row m20t" ng-show="!index.txHistory[0] && !index.updatingTxHistory"> <div class="row m20t" ng-show="!index.txHistory[0] && !index.updatingTxHistory[index.walletId]">
<div class="large-12 columns"> <div class="large-12 columns">
<div class="oh text-center"> <div class="oh text-center">
<span ng-show="index.txHistoryError && !index.notAuthorized" ng-click='index.updateTxHistory()'> <span ng-show="index.txHistoryError && !index.notAuthorized" ng-click='index.updateTxHistory()'>
@ -457,7 +455,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row m20t text-center" ng-show="index.updatingTxHistory"> <div class="row m20t text-center" ng-show="index.updatingTxHistory[index.walletId]">
<div class="columns large-12 medium-12 small-12"> <div class="columns large-12 medium-12 small-12">
<div class="spinner"> <div class="spinner">
<div class="rect1"></div> <div class="rect1"></div>
@ -467,18 +465,16 @@
<div class="rect5"></div> <div class="rect5"></div>
</div> </div>
</div> </div>
<p ng-show="index.showWaitingSign" translate class="size-12 text-gray"> <div ng-show="index.txProgress > 6" translate class="size-12 text-gray m20t">
Initial transaction history synchronization can take some minutes for wallets with many transactions.</br> Initial transaction history synchronization can take some minutes for wallets with many transactions.</br>
Please stand by. Please stand by.
</p> </div>
</div> <div ng-show="index.txProgress > 6" translate class="size-14 text-gray m20t">
<div ng-if="!index.isCordova && index.txHistory[0] && !index.updatingTxHistory" class="m20t text-center"> <b>{{index.txProgress}}</b> Transactions<br>
<input id="export_file" type="file" nwsaveas="Copay-{{index.alias || index.walletName}}.csv" accept=".csv" style="display:none"> Downloaded
<a class="text-gray size-12" ng-click="index.csvHistory();"> </div>
<i class="fi-page-export-csv"></i>
<span translate>Download CSV file</span>
</a>
</div> </div>
<div ng-show="index.txHistory[0]"> <div ng-show="index.txHistory[0]">
<div ng-repeat="btx in index.txHistory" <div ng-repeat="btx in index.txHistory"
ng-click="home.openTxModal(btx)" ng-click="home.openTxModal(btx)"
@ -522,6 +518,33 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row m20t text-center" ng-show="index.historyRendering">
<div class="columns large-12 medium-12 small-12">
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
</div>
<div class="row m20t">
<div class="large-6 medium-6 small-6 columns">
<button type="submit" class="button black round expand" ng-show="index.historyShowShowAll" ng-click="index.showAllHistory()" ng-style="{'background-color':index.backgroundColor}" translate>
<span translate>Show All</span>
</button>
</div>
<div class="large-6 medium-6 small-6 columns" ng-show="!index.isCordova">
<input id="export_file" type="file" nwsaveas="Copay-{{index.alias || index.walletName}}.csv" accept=".csv" style="display:none">
<a class="button outline dark-gray round" ng-click="index.csvHistory();">
<i class="fi-page-export-csv"></i>
<span translate>Download CSV file</span>
</a>
</div>
</div>
</div> </div>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>
</div> <!-- END History --> </div> <!-- END History -->

View File

@ -1,11 +1,12 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('createController', angular.module('copayApp.controllers').controller('createController',
function($scope, $rootScope, $location, $timeout, $log, lodash, go, profileService, configService, isCordova, gettext, ledger, trezor, isMobile) { function($scope, $rootScope, $location, $timeout, $log, lodash, go, profileService, configService, isCordova, gettext, ledger, trezor, isMobile, isChromeApp, isDevel, derivationPathHelper) {
var self = this; var self = this;
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
this.isWindowsPhoneApp = isMobile.Windows() && isCordova; this.isWindowsPhoneApp = isMobile.Windows() && isCordova;
$scope.account = 1;
/* For compressed keys, m*73 + n*34 <= 496 */ /* For compressed keys, m*73 + n*34 <= 496 */
var COPAYER_PAIR_LIMITS = { var COPAYER_PAIR_LIMITS = {
@ -25,6 +26,7 @@ angular.module('copayApp.controllers').controller('createController',
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
$scope.bwsurl = defaults.bws.url; $scope.bwsurl = defaults.bws.url;
$scope.derivationPath = derivationPathHelper.default;
// ng-repeat defined number of times instead of repeating over array? // ng-repeat defined number of times instead of repeating over array?
this.getNumber = function(num) { this.getNumber = function(num) {
@ -38,11 +40,47 @@ angular.module('copayApp.controllers').controller('createController',
$scope.requiredCopayers = Math.min(parseInt(n / 2 + 1), maxReq); $scope.requiredCopayers = Math.min(parseInt(n / 2 + 1), maxReq);
}; };
var updateSeedSourceSelect = function(n) {
self.seedOptions = [{
id: 'new',
label: gettext('New Random Seed'),
}, {
id: 'set',
label: gettext('Specify Seed...'),
}];
$scope.seedSource = self.seedOptions[0];
if (n > 1 && isChromeApp)
self.seedOptions.push({
id: 'ledger',
label: gettext('Ledger Hardware Wallet'),
});
if (isChromeApp || isDevel) {
self.seedOptions.push({
id: 'trezor',
label: gettext('Trezor Hardware Wallet'),
});
}
};
this.TCValues = lodash.range(2, defaults.limits.totalCopayers + 1); this.TCValues = lodash.range(2, defaults.limits.totalCopayers + 1);
$scope.totalCopayers = defaults.wallet.totalCopayers; $scope.totalCopayers = defaults.wallet.totalCopayers;
this.setTotalCopayers = function(tc) { this.setTotalCopayers = function(tc) {
updateRCSelect(tc); updateRCSelect(tc);
updateSeedSourceSelect(tc);
self.seedSourceId = $scope.seedSource.id;
};
this.setSeedSource = function(src) {
self.seedSourceId = $scope.seedSource.id;
$timeout(function() {
$rootScope.$apply();
});
}; };
this.create = function(form) { this.create = function(form) {
@ -50,23 +88,36 @@ angular.module('copayApp.controllers').controller('createController',
this.error = gettext('Please enter the required fields'); this.error = gettext('Please enter the required fields');
return; return;
} }
var opts = { var opts = {
m: $scope.requiredCopayers, m: $scope.requiredCopayers,
n: $scope.totalCopayers, n: $scope.totalCopayers,
name: form.walletName.$modelValue, name: form.walletName.$modelValue,
myName: $scope.totalCopayers > 1 ? form.myName.$modelValue : null, myName: $scope.totalCopayers > 1 ? form.myName.$modelValue : null,
networkName: form.isTestnet.$modelValue ? 'testnet' : 'livenet', networkName: form.isTestnet.$modelValue ? 'testnet' : 'livenet',
bwsurl: $scope.bwsurl bwsurl: $scope.bwsurl,
}; };
var setSeed = form.setSeed.$modelValue; var setSeed = self.seedSourceId == 'set';
if (setSeed) { if (setSeed) {
var words = form.privateKey.$modelValue;
var words = form.privateKey.$modelValue || '';
if (words.indexOf(' ') == -1 && words.indexOf('prv') == 1 && words.length > 108) { if (words.indexOf(' ') == -1 && words.indexOf('prv') == 1 && words.length > 108) {
opts.extendedPrivateKey = words; opts.extendedPrivateKey = words;
} else { } else {
opts.mnemonic = words; opts.mnemonic = words;
} }
opts.passphrase = form.passphrase.$modelValue; opts.passphrase = form.passphrase.$modelValue;
var pathData = derivationPathHelper.parse($scope.derivationPath);
if (!pathData) {
this.error = gettext('Invalid derivation path');
return;
}
opts.account = pathData.account;
opts.networkName = pathData.networkName;
opts.derivationStrategy = pathData.derivationStrategy;
} else { } else {
opts.passphrase = form.createPassphrase.$modelValue; opts.passphrase = form.createPassphrase.$modelValue;
} }
@ -76,14 +127,21 @@ angular.module('copayApp.controllers').controller('createController',
return; return;
} }
if (form.hwLedger.$modelValue || form.hwTrezor.$modelValue) { if (self.seedSourceId == 'ledger' || self.seedSourceId == 'trezor') {
self.hwWallet = form.hwLedger.$modelValue ? 'Ledger' : 'TREZOR'; var account = $scope.account;
if (!account || account < 1) {
this.error = gettext('Invalid account number');
return;
}
var src = form.hwLedger.$modelValue ? ledger : trezor; if ( self.seedSourceId == 'trezor')
account = account - 1;
// TODO : account opts.account = account;
var account = 0; self.hwWallet = self.seedSourceId == 'ledger' ? 'Ledger' : 'Trezor';
src.getInfoForNewWallet(account, function(err, lopts) { var src = self.seedSourceId == 'ledger' ? ledger : trezor;
src.getInfoForNewWallet(opts.n > 1, account, function(err, lopts) {
self.hwWallet = false; self.hwWallet = false;
if (err) { if (err) {
self.error = err; self.error = err;
@ -141,4 +199,7 @@ angular.module('copayApp.controllers').controller('createController',
$scope.$on("$destroy", function() { $scope.$on("$destroy", function() {
$rootScope.hideWalletNavigation = false; $rootScope.hideWalletNavigation = false;
}); });
updateSeedSourceSelect(1);
self.setSeedSource('new');
}); });

View File

@ -1,12 +1,14 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('importController', angular.module('copayApp.controllers').controller('importController',
function($scope, $rootScope, $location, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, lodash, ledger, trezor) { function($scope, $rootScope, $location, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, lodash, ledger, trezor, isChromeApp, isDevel, derivationPathHelper) {
var self = this; var self = this;
var reader = new FileReader(); var reader = new FileReader();
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
$scope.bwsurl = defaults.bws.url; $scope.bwsurl = defaults.bws.url;
$scope.derivationPath = derivationPathHelper.default;
$scope.account = 1;
window.ignoreMobilePause = true; window.ignoreMobilePause = true;
$scope.$on('$destroy', function() { $scope.$on('$destroy', function() {
@ -15,6 +17,27 @@ angular.module('copayApp.controllers').controller('importController',
}, 100); }, 100);
}); });
var updateSeedSourceSelect = function() {
self.seedOptions = [];
if (isChromeApp) {
self.seedOptions.push({
id: 'ledger',
label: gettext('Ledger Hardware Wallet'),
});
}
if (isChromeApp || isDevel) {
self.seedOptions.push({
id: 'trezor',
label: gettext('Trezor Hardware Wallet'),
});
$scope.seedSource = self.seedOptions[0];
}
};
this.setType = function(type) { this.setType = function(type) {
$scope.type = type; $scope.type = type;
this.error = null; this.error = null;
@ -173,23 +196,23 @@ angular.module('copayApp.controllers').controller('importController',
} }
opts.passphrase = form.passphrase.$modelValue || null; opts.passphrase = form.passphrase.$modelValue || null;
opts.networkName = form.isTestnet.$modelValue ? 'testnet' : 'livenet';
var pathData = derivationPathHelper.parse($scope.derivationPath);
if (!pathData) {
this.error = gettext('Invalid derivation path');
return;
}
opts.account = pathData.account;
opts.networkName = pathData.networkName;
opts.derivationStrategy = pathData.derivationStrategy;
_importMnemonic(words, opts); _importMnemonic(words, opts);
}; };
this.importTrezor = function(form) { this.importTrezor = function(account, isMultisig) {
var self = this; var self = this;
if (form.$invalid) { trezor.getInfoForNewWallet(isMultisig, account, function(err, lopts) {
this.error = gettext('There is an error in the form');
$timeout(function() {
$scope.$apply();
});
return;
}
self.hwWallet = 'Trezor';
// TODO account
trezor.getInfoForNewWallet(0, function(err, lopts) {
self.hwWallet = false; self.hwWallet = false;
if (err) { if (err) {
self.error = err; self.error = err;
@ -217,18 +240,53 @@ angular.module('copayApp.controllers').controller('importController',
}, 100); }, 100);
}; };
this.importLedger = function(form) { this.importHW = function(form) {
var self = this; if (form.$invalid || $scope.account < 0 ) {
if (form.$invalid) {
this.error = gettext('There is an error in the form'); this.error = gettext('There is an error in the form');
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
return; return;
} }
self.hwWallet = 'Ledger'; this.error = '';
// TODO account
ledger.getInfoForNewWallet(0, function(err, lopts) { var account = + $scope.account;
if (self.seedSourceId == 'trezor') {
if ( account < 1) {
this.error = gettext('Invalid account number');
return;
}
account = account - 1;
}
var isMultisig = form.isMultisig.$modelValue;
switch (self.seedSourceId) {
case ('ledger'):
self.hwWallet = 'Ledger';
self.importLedger(account);
break;
case ('trezor'):
self.hwWallet = 'Trezor';
self.importTrezor(account, isMultisig);
break;
default:
throw ('Error: bad source id');
};
};
this.setSeedSource = function() {
if (!$scope.seedSource) return;
self.seedSourceId = $scope.seedSource.id;
$timeout(function() {
$rootScope.$apply();
});
};
this.importLedger = function(account) {
var self = this;
ledger.getInfoForNewWallet(true, account, function(err, lopts) {
self.hwWallet = false; self.hwWallet = false;
if (err) { if (err) {
self.error = err; self.error = err;
@ -255,4 +313,6 @@ angular.module('copayApp.controllers').controller('importController',
}, 100); }, 100);
}; };
updateSeedSourceSelect();
self.setSeedSource('new');
}); });

View File

@ -7,7 +7,8 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.isChromeApp = isChromeApp; self.isChromeApp = isChromeApp;
self.isSafari = isMobile.Safari(); self.isSafari = isMobile.Safari();
self.onGoingProcess = {}; self.onGoingProcess = {};
self.limitHistory = 6; self.historyShowLimit = 10;
self.updatingTxHistory = {};
function strip(number) { function strip(number) {
return (parseFloat(number.toPrecision(12))); return (parseFloat(number.toPrecision(12)));
@ -83,7 +84,9 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.currentFeeLevel = null; self.currentFeeLevel = null;
self.notAuthorized = false; self.notAuthorized = false;
self.txHistory = []; self.txHistory = [];
self.txHistoryUnique = {}; self.completeHistory = [];
self.txProgress = 0;
self.historyShowShowAll = false;
self.balanceByAddress = null; self.balanceByAddress = null;
self.pendingTxProposalsCountForUs = null; self.pendingTxProposalsCountForUs = null;
self.setSpendUnconfirmed(); self.setSpendUnconfirmed();
@ -106,7 +109,13 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.isComplete = fc.isComplete(); self.isComplete = fc.isComplete();
self.canSign = fc.canSign(); self.canSign = fc.canSign();
self.isPrivKeyExternal = fc.isPrivKeyExternal(); self.isPrivKeyExternal = fc.isPrivKeyExternal();
self.isPrivKeyEncrypted = fc.isPrivKeyEncrypted();
self.externalSource = fc.getPrivKeyExternalSourceName(); self.externalSource = fc.getPrivKeyExternalSourceName();
self.account = fc.credentials.account;
if (self.externalSource == 'trezor')
self.account++;
self.txps = []; self.txps = [];
self.copayers = []; self.copayers = [];
self.updateColor(); self.updateColor();
@ -115,6 +124,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.initGlidera(); self.initGlidera();
self.setCustomBWSFlag();
if (fc.isPrivKeyExternal()) { if (fc.isPrivKeyExternal()) {
self.needsBackup = false; self.needsBackup = false;
self.openWallet(); self.openWallet();
@ -127,6 +137,13 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
}; };
self.setCustomBWSFlag = function() {
var defaults = configService.getDefaults();
var config = configService.getSync();
self.usingCustomBWS = config.bwsFor && (config.bwsFor[self.walletId] != defaults.bws.url);
};
self.setTab = function(tab, reset, tries, switchState) { self.setTab = function(tab, reset, tries, switchState) {
tries = tries || 0; tries = tries || 0;
@ -485,12 +502,13 @@ angular.module('copayApp.controllers').controller('indexController', function($r
var SAFE_CONFIRMATIONS = 6; var SAFE_CONFIRMATIONS = 6;
self.setTxHistory = function(txs) { self.processNewTxs = function(txs) {
var config = configService.getSync().wallet.settings; var config = configService.getSync().wallet.settings;
var now = Math.floor(Date.now() / 1000); var now = Math.floor(Date.now() / 1000);
self.txHistoryUnique = {}; var txHistoryUnique = {};
var ret = [];
self.hasUnsafeConfirmed = false; self.hasUnsafeConfirmed = false;
lodash.each(txs, function(tx) { lodash.each(txs, function(tx) {
tx = txFormatService.processTx(tx); tx = txFormatService.processTx(tx);
@ -505,13 +523,15 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.hasUnsafeConfirmed = true; self.hasUnsafeConfirmed = true;
} }
if (!self.txHistoryUnique[tx.txid]) { if (!txHistoryUnique[tx.txid]) {
self.txHistory.push(tx); ret.push(tx);
self.txHistoryUnique[tx.txid] = true; txHistoryUnique[tx.txid] = true;
} else { } else {
$log.debug('Ignoring duplicate TX in history: ' + tx.txid) $log.debug('Ignoring duplicate TX in history: ' + tx.txid)
} }
}); });
return ret;
}; };
self.updateAlias = function() { self.updateAlias = function() {
@ -735,13 +755,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
}; };
self.stopSync = function(remoteTx, localTx) {
if (remoteTx.txid == localTx.txid)
return true;
else
return false;
}
self.removeSoftConfirmedTx = function(txs) { self.removeSoftConfirmedTx = function(txs) {
return lodash.map(txs, function(tx) { return lodash.map(txs, function(tx) {
if (tx.confirmations >= SOFT_CONFIRMATION_LIMIT) if (tx.confirmations >= SOFT_CONFIRMATION_LIMIT)
@ -749,17 +762,14 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
} }
self.getConfirmedTxs = function(cb) { self.getConfirmedTxs = function(walletId, cb) {
var fc = profileService.focusedClient;
var c = fc.credentials;
storageService.getTxHistory(c.walletId, function(err, txs) { storageService.getTxHistory(walletId, function(err, txs) {
if (err) return cb(err); if (err) return cb(err);
var localTxs = []; var localTxs = [];
if (!txs) { if (!txs) {
self.showWaitingSign = true;
return cb(null, localTxs); return cb(null, localTxs);
} }
@ -772,72 +782,107 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
} }
self.updateLocalTxHistory = function(cb) { self.updateLocalTxHistory = function(client, cb) {
self.getConfirmedTxs(function(err, txsFromLocal) { var requestLimit = 6;
var walletId = client.credentials.walletId;
self.getConfirmedTxs(walletId, function(err, txsFromLocal) {
if (err) return cb(err); if (err) return cb(err);
var endingTxid = txsFromLocal[0] ? txsFromLocal[0].txid : null;
var fc = profileService.focusedClient; function getNewTxs(newTxs, skip, i_cb) {
var c = fc.credentials;
fillTxsObject();
function fillTxsObject(txsResult, index) { self.getTxsFromServer(client, skip, endingTxid, requestLimit, function(err, res, shouldContinue) {
txsResult = txsResult || []; if (err) return i_cb(err);
index = index || 0;
self.makeTxHistoryRequest(txsResult, index, txsFromLocal[0], function(err, newIndex, exitLoop) {
if (err) return cb(err); newTxs = newTxs.concat(lodash.compact(res));
if (exitLoop) { skip = skip + requestLimit;
self.txHistory = [];
self.setTxHistory(lodash.compact(txsResult.concat(txsFromLocal))); $log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue);
return storageService.setTxHistory(JSON.stringify(self.txHistory), c.walletId, function() {
return cb(null); if (!shouldContinue) {
}); newTxs = self.processNewTxs(newTxs);
$log.debug('Finish Sync: New Txs: ' + newTxs.length);
return i_cb(null, newTxs);
} }
fillTxsObject(txsResult, newIndex);
if (walletId == profileService.focusedClient.credentials.walletId)
self.txProgress = newTxs.length;
$timeout(function() {
$rootScope.$apply();
});
getNewTxs(newTxs, skip, i_cb);
}); });
}; };
getNewTxs([], 0, function(err, txs) {
if (err) return cb(err);
var newHistory = lodash.compact(txs.concat(txsFromLocal));
$log.debug('Tx History synced. Total Txs: ' + newHistory.length);
if (walletId == profileService.focusedClient.credentials.walletId) {
self.completeHistory = newHistory;
self.txHistory = newHistory.slice(0, self.historyShowLimit);
self.historyShowShowAll = newHistory.length >= self.historyShowLimit;
}
return storageService.setTxHistory(JSON.stringify(newHistory), walletId, function() {
return cb();
});
});
}); });
} }
self.showAllHistory = function() {
self.historyShowShowAll = false;
self.historyRendering = true;
$timeout(function() {
$rootScope.$apply();
$timeout(function() {
self.historyRendering = false;
self.txHistory = self.completeHistory;
}, 100);
}, 100);
};
self.makeTxHistoryRequest = function(txsResult, index, endingTx, cb) { self.getTxsFromServer = function(client, skip, endingTxid, limit, cb) {
var fc = profileService.focusedClient; var res = [];
var c = fc.credentials;
var exitLoop = false;
fc.getTxHistory({ client.getTxHistory({
skip: index, skip: skip,
limit: self.limitHistory + 1 limit: limit
}, function(err, txsFromBWC) { }, function(err, txsFromServer) {
if (err) return cb(err); if (err) return cb(err);
if (!txsFromBWC[0]) if (!txsFromServer.length)
exitLoop = true; return cb();
lodash.each(txsFromBWC, function(t) { var res = lodash.takeWhile(txsFromServer, function(tx) {
if (!endingTx) txsResult.push(t); return tx.txid != endingTxid;
else {
if (!self.stopSync(t, endingTx) && !exitLoop) {
txsResult.push(t);
} else {
exitLoop = true;
}
}
}); });
index = index + self.limitHistory;
return cb(null, index, exitLoop); return cb(null, res, res.length == limit);
}); });
} };
self.updateHistory = function() { self.updateHistory = function() {
var fc = profileService.focusedClient;
var walletId = fc.credentials.walletId;
if (!fc.isComplete() || self.updatingTxHistory[walletId]) return;
$log.debug('Updating Transaction History'); $log.debug('Updating Transaction History');
self.txHistoryError = false; self.txHistoryError = false;
self.updatingTxHistory = true; self.updatingTxHistory[walletId] = true;
$timeout(function() { $timeout(function() {
self.updateLocalTxHistory(function(err) { self.updateLocalTxHistory(fc, function(err) {
if (err) self.txHistoryError = true; self.updatingTxHistory[walletId] = false;
self.updatingTxHistory = false; if (err)
self.showWaitingSign = false; self.txHistoryError = true;
$rootScope.$apply(); $rootScope.$apply();
}); });
}); });
@ -1288,4 +1333,12 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.setFocusedWallet(); self.setFocusedWallet();
}); });
}); });
$rootScope.$on('Local/NewEncryptionSetting', function() {
var fc = profileService.focusedClient;
self.isPrivKeyEncrypted = fc.isPrivKeyEncrypted();
$timeout(function() {
$rootScope.$apply();
});
});
}); });

View File

@ -1,11 +1,12 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('joinController', angular.module('copayApp.controllers').controller('joinController',
function($scope, $rootScope, $timeout, go, notification, profileService, configService, isCordova, storageService, applicationService, $modal, gettext, lodash, ledger, trezor) { function($scope, $rootScope, $timeout, go, notification, profileService, configService, isCordova, storageService, applicationService, $modal, gettext, lodash, ledger, trezor, isChromeApp, isDevel,derivationPathHelper) {
var self = this; var self = this;
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
$scope.bwsurl = defaults.bws.url; $scope.bwsurl = defaults.bws.url;
$scope.derivationPath = derivationPathHelper.default;
this.onQrCodeScanned = function(data) { this.onQrCodeScanned = function(data) {
$scope.secret = data; $scope.secret = data;
@ -13,6 +14,42 @@ angular.module('copayApp.controllers').controller('joinController',
$scope.joinForm.secret.$render(); $scope.joinForm.secret.$render();
}; };
var updateSeedSourceSelect = function() {
self.seedOptions = [{
id: 'new',
label: gettext('New Random Seed'),
}, {
id: 'set',
label: gettext('Specify Seed...'),
}];
$scope.seedSource = self.seedOptions[0];
if (isChromeApp) {
self.seedOptions.push({
id: 'ledger',
label: gettext('Ledger Hardware Wallet'),
});
}
if (isChromeApp || isDevel) {
self.seedOptions.push({
id: 'trezor',
label: gettext('Trezor Hardware Wallet'),
});
}
};
this.setSeedSource = function(src) {
self.seedSourceId = $scope.seedSource.id;
self.accountValues = lodash.range(1, 100);
$timeout(function() {
$rootScope.$apply();
});
};
this.join = function(form) { this.join = function(form) {
if (form && form.$invalid) { if (form && form.$invalid) {
self.error = gettext('Please enter the required fields'); self.error = gettext('Please enter the required fields');
@ -23,10 +60,10 @@ angular.module('copayApp.controllers').controller('joinController',
var opts = { var opts = {
secret: form.secret.$modelValue, secret: form.secret.$modelValue,
myName: form.myName.$modelValue, myName: form.myName.$modelValue,
bwsurl: $scope.bwsurl bwsurl: $scope.bwsurl,
} }
var setSeed = form.setSeed.$modelValue; var setSeed = self.seedSourceId =='set';
if (setSeed) { if (setSeed) {
var words = form.privateKey.$modelValue; var words = form.privateKey.$modelValue;
if (words.indexOf(' ') == -1 && words.indexOf('prv') == 1 && words.length > 108) { if (words.indexOf(' ') == -1 && words.indexOf('prv') == 1 && words.length > 108) {
@ -35,6 +72,15 @@ angular.module('copayApp.controllers').controller('joinController',
opts.mnemonic = words; opts.mnemonic = words;
} }
opts.passphrase = form.passphrase.$modelValue; opts.passphrase = form.passphrase.$modelValue;
var pathData = derivationPathHelper.parse($scope.derivationPath);
if (!pathData) {
this.error = gettext('Invalid derivation path');
return;
}
opts.account = pathData.account;
opts.networkName = pathData.networkName;
opts.derivationStrategy = pathData.derivationStrategy;
} else { } else {
opts.passphrase = form.createPassphrase.$modelValue; opts.passphrase = form.createPassphrase.$modelValue;
} }
@ -44,12 +90,21 @@ angular.module('copayApp.controllers').controller('joinController',
return; return;
} }
if (form.hwLedger.$modelValue || form.hwTrezor.$modelValue) { if (self.seedSourceId == 'ledger' || self.seedSourceId == 'trezor') {
self.hwWallet = form.hwLedger.$modelValue ? 'Ledger' : 'TREZOR'; var account = $scope.account;
var src = form.hwLedger.$modelValue ? ledger : trezor; if (!account || account < 1) {
this.error = gettext('Invalid account number');
return;
}
var account = 0; if ( self.seedSourceId == 'trezor')
src.getInfoForNewWallet(account, function(err, lopts) { account = account - 1;
opts.account = account;
self.hwWallet = self.seedSourceId == 'ledger' ? 'Ledger' : 'Trezor';
var src = self.seedSourceId == 'ledger' ? ledger : trezor;
src.getInfoForNewWallet(true, account, function(err, lopts) {
self.hwWallet = false; self.hwWallet = false;
if (err) { if (err) {
self.error = err; self.error = err;
@ -82,4 +137,7 @@ angular.module('copayApp.controllers').controller('joinController',
}); });
}, 100); }, 100);
}; };
updateSeedSourceSelect();
self.setSeedSource('new');
}); });

View File

@ -55,6 +55,7 @@ angular.module('copayApp.controllers').controller('preferencesController',
return; return;
} }
profileService.setPrivateKeyEncryptionFC(password, function() { profileService.setPrivateKeyEncryptionFC(password, function() {
$rootScope.$emit('Local/NewEncryptionSetting');
$scope.encrypt = true; $scope.encrypt = true;
}); });
}); });
@ -66,6 +67,7 @@ angular.module('copayApp.controllers').controller('preferencesController',
return; return;
} }
profileService.disablePrivateKeyEncryptionFC(function(err) { profileService.disablePrivateKeyEncryptionFC(function(err) {
$rootScope.$emit('Local/NewEncryptionSetting');
if (err) { if (err) {
$scope.encrypt = true; $scope.encrypt = true;
$log.error(err); $log.error(err);

View File

@ -13,7 +13,7 @@ angular.module('copayApp.controllers').controller('preferencesBwsUrlController',
this.bwsurl = (config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url; this.bwsurl = (config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url;
this.resetDefaultUrl = function() { this.resetDefaultUrl = function() {
this.bwsurl = 'https://bws.bitpay.com/bws/api'; this.bwsurl = defaults.bws.url;
}; };
this.save = function() { this.save = function() {
@ -50,4 +50,4 @@ angular.module('copayApp.controllers').controller('preferencesBwsUrlController',
}); });
}); });
}; };
}); });

View File

@ -7,7 +7,7 @@ angular.module('copayApp.controllers').controller('preferencesInformation',
var c = fc.credentials; var c = fc.credentials;
this.init = function() { this.init = function() {
var basePath = profileService.getUtils().getBaseAddressDerivationPath(c.derivationStrategy, c.network, 0); var basePath = c.getBaseAddressDerivationPath();
$scope.walletName = c.walletName; $scope.walletName = c.walletName;
$scope.walletId = c.walletId; $scope.walletId = c.walletId;

View File

@ -106,36 +106,6 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
var cancel_msg = gettextCatalog.getString('Cancel'); var cancel_msg = gettextCatalog.getString('Cancel');
var confirm_msg = gettextCatalog.getString('Confirm'); var confirm_msg = gettextCatalog.getString('Confirm');
$scope.openCopayersModal = function(copayers, copayerId) {
$rootScope.modalOpened = true;
var fc = profileService.focusedClient;
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.copayers = copayers;
$scope.copayerId = copayerId;
$scope.color = fc.backgroundColor;
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
var modalInstance = $modal.open({
templateUrl: 'views/modals/copayers.html',
windowClass: animationService.modalAnimated.slideUp,
controller: ModalInstanceCtrl,
});
var disableCloseModal = $rootScope.$on('closeModal', function() {
modalInstance.dismiss('cancel');
});
modalInstance.result.finally(function() {
$rootScope.modalOpened = false;
disableCloseModal();
var m = angular.element(document.getElementsByClassName('reveal-modal'));
m.addClass(animationService.modalAnimated.slideOutDown);
});
};
$scope.openDestinationAddressModal = function(wallets, address) { $scope.openDestinationAddressModal = function(wallets, address) {
$rootScope.modalOpened = true; $rootScope.modalOpened = true;
var fc = profileService.focusedClient; var fc = profileService.focusedClient;

View File

@ -21,13 +21,14 @@ angular
$logProvider.debugEnabled(true); $logProvider.debugEnabled(true);
$provide.decorator('$log', ['$delegate', $provide.decorator('$log', ['$delegate',
function($delegate) { function($delegate, isDevel) {
var historicLog = historicLogProvider.$get(); var historicLog = historicLogProvider.$get();
['debug', 'info', 'warn', 'error', 'log'].forEach(function(level) { ['debug', 'info', 'warn', 'error', 'log'].forEach(function(level) {
if (isDevel && level == 'error') return;
var orig = $delegate[level]; var orig = $delegate[level];
$delegate[level] = function() { $delegate[level] = function() {
if (level == 'error') if (level == 'error')
console.log(arguments); console.log(arguments);

View File

@ -0,0 +1,46 @@
'use strict';
angular.module('copayApp.services').factory('derivationPathHelper', function(lodash) {
var root = {};
root.default = "m/44'/0'/0'"
root.parse = function(str) {
var arr = str.split('/');
var ret = {};
if (arr[0] != 'm')
return false;
switch (arr[1]) {
case "44'":
ret.derivationStrategy = 'BIP44';
break;
case "48'":
ret.derivationStrategy = 'BIP48';
break;
default:
return false;
};
switch (arr[2]) {
case "0'":
ret.networkName = 'livenet';
break;
case "1'":
ret.networkName = 'testnet';
break;
default:
return false;
};
var match = arr[3].match(/(\d+)'/);
if (!match)
return false;
ret.account = + match[1]
return ret;
};
return root;
});

View File

@ -0,0 +1,50 @@
'use strict';
angular.module('copayApp.services')
.factory('hwWallet', function($log, bwcService) {
var root = {};
// Ledger magic number to get xPub without user confirmation
root.ENTROPY_INDEX_PATH = "0xb11e/";
root.UNISIG_ROOTPATH = 44;
root.MULTISIG_ROOTPATH = 48;
root.LIVENET_PATH = 0;
root._err = function(data) {
var msg = 'Hardware Wallet Error: ' + (data.error || data.message || 'unknown');
$log.warn(msg);
return msg;
};
root.getRootPath = function(device, isMultisig, account) {
if (!isMultisig) return root.UNISIG_ROOTPATH;
// Compat
if (device == 'ledger' && account ==0) return root.UNISIG_ROOTPATH;
return root.MULTISIG_ROOTPATH;
};
root.getAddressPath = function(device, isMultisig, account) {
return root.getRootPath(device,isMultisig,account) + "'/" + root.LIVENET_PATH + "'/" + account + "'";
}
root.getEntropyPath = function(device, isMultisig, account) {
var path;
// Old ledger wallet compat
if (device == 'ledger' && account == 0)
return root.ENTROPY_INDEX_PATH + "0'";
return root.ENTROPY_INDEX_PATH + root.getRootPath(device,isMultisig,account) + "'/" + account + "'";
};
root.pubKeyToEntropySource = function(xPubKey) {
var b = bwcService.getBitcore();
var x = b.HDPublicKey(xPubKey);
return x.publicKey.toString();
};
return root;
});

View File

@ -0,0 +1,5 @@
'use strict';
angular.module('copayApp.services').factory('isDevel', function(nodeWebkit, isChromeApp, isMobile) {
return !isMobile.any() && !isChromeApp && !nodeWebkit.isDefined();
});

View File

@ -1,43 +1,27 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('ledger', function($log, bwcService, gettext) { .factory('ledger', function($log, bwcService, gettext, hwWallet) {
var root = {}; var root = {};
var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf"; var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf";
// Ledger magic number to get xPub without user confirmation
root.ENTROPY_INDEX_PATH = "0xb11e/";
root.callbacks = {}; root.callbacks = {};
root.hasSession = function() { root.hasSession = function() {
root._message({ root._message({
command: "has_session" command: "has_session"
}); });
} }
root.getEntropySource = function(account, callback) { root.getEntropySource = function(isMultisig, account, callback) {
var path = root.ENTROPY_INDEX_PATH + account + "'"; root.getXPubKey(hwWallet.getEntropyPath('ledger', isMultisig, account), function(data) {
var xpub = root.getXPubKey(path, function(data) { if (!data.success)
if (!data.success) { return callback(hwWallet._err(data));
$log.warn(data.message);
return callback(data); return callback(null, hwWallet.pubKeyToEntropySource(data.xpubkey));
}
var b = bwcService.getBitcore();
var x = b.HDPublicKey(data.xpubkey);
data.entropySource = x.publicKey.toString();
return callback(data);
}); });
}; };
root.getXPubKeyForAddresses = function(account, callback) {
return root.getXPubKey(root._getPath(account), callback);
};
root.getXPubKey = function(path, callback) { root.getXPubKey = function(path, callback) {
$log.debug('Ledger deriving xPub path:', path); $log.debug('Ledger deriving xPub path:', path);
root.callbacks["get_xpubkey"] = callback; root.callbacks["get_xpubkey"] = callback;
root._messageAfterSession({ root._messageAfterSession({
@ -47,35 +31,36 @@ angular.module('copayApp.services')
}; };
root.getInfoForNewWallet = function(account, callback) { root.getInfoForNewWallet = function(isMultisig, account, callback) {
var opts = {}; var opts = {};
root.getEntropySource(account, function(data) { root.getEntropySource(isMultisig, account, function(err, entropySource) {
if (!data.success) { if (err) return callback(err);
$log.warn(data.message);
return callback(data.message); opts.entropySource = entropySource;
} root.getXPubKey(hwWallet.getAddressPath('ledger', isMultisig, account), function(data) {
opts.entropySource = data.entropySource;
root.getXPubKeyForAddresses(account, function(data) {
if (!data.success) { if (!data.success) {
$log.warn(data.message); $log.warn(data.message);
return callback(data); return callback(data);
} }
opts.extendedPublicKey = data.xpubkey; opts.extendedPublicKey = data.xpubkey;
opts.externalSource = 'ledger'; opts.externalSource = 'ledger';
opts.externalIndex = account; opts.account = account;
// Old ledger compat
opts.derivationStrategy = account ? 'BIP48' : 'BIP44';
return callback(null, opts); return callback(null, opts);
}); });
}); });
}; };
root._signP2SH = function(txp, account, callback) { root._signP2SH = function(txp, account, isMultisig, callback) {
root.callbacks["sign_p2sh"] = callback; root.callbacks["sign_p2sh"] = callback;
var redeemScripts = []; var redeemScripts = [];
var paths = []; var paths = [];
var tx = bwcService.getUtils().buildTx(txp); var tx = bwcService.buildTx(txp);
for (var i = 0; i < tx.inputs.length; i++) { for (var i = 0; i < tx.inputs.length; i++) {
redeemScripts.push(new ByteString(tx.inputs[i].redeemScript.toBuffer().toString('hex'), GP.HEX).toString()); redeemScripts.push(new ByteString(tx.inputs[i].redeemScript.toBuffer().toString('hex'), GP.HEX).toString());
paths.push(root._getPath(account) + txp.inputs[i].path.substring(1)); paths.push(hwWallet.getAddressPath('ledger', isMultisig, account) + txp.inputs[i].path.substring(1));
} }
var splitTransaction = root._splitTransaction(new ByteString(tx.toString(), GP.HEX)); var splitTransaction = root._splitTransaction(new ByteString(tx.toString(), GP.HEX));
var inputs = []; var inputs = [];
@ -98,12 +83,15 @@ angular.module('copayApp.services')
}; };
root.signTx = function(txp, account, callback) { root.signTx = function(txp, account, callback) {
// TODO Compat
var isMultisig = true;
if (txp.addressType == 'P2PKH') { if (txp.addressType == 'P2PKH') {
var msg = 'P2PKH wallets are not supported with ledger'; var msg = 'P2PKH wallets are not supported with ledger';
$log.error(msg); $log.error(msg);
return callback(msg); return callback(msg);
} else { } else {
root._signP2SH(txp, account, callback); root._signP2SH(txp, account, isMultisig, callback);
} }
} }
@ -154,10 +142,6 @@ angular.module('copayApp.services')
} }
} }
root._getPath = function(account) {
return "44'/0'/" + account + "'";
}
root._splitTransaction = function(transaction) { root._splitTransaction = function(transaction) {
var result = {}; var result = {};
var inputs = []; var inputs = [];

View File

@ -46,8 +46,6 @@ angular.module('copayApp.services')
client.setNotificationsInterval(BACKGROUND_UPDATE_PERIOD); client.setNotificationsInterval(BACKGROUND_UPDATE_PERIOD);
}); });
root.focusedClient.setNotificationsInterval(FOREGROUND_UPDATE_PERIOD); root.focusedClient.setNotificationsInterval(FOREGROUND_UPDATE_PERIOD);
console.log('[profileService.js.49] SETTING...'); //TODO
} }
return cb(); return cb();
@ -175,14 +173,17 @@ angular.module('copayApp.services')
var walletClient = bwcService.getClient(); var walletClient = bwcService.getClient();
var network = opts.networkName || 'livenet'; var network = opts.networkName || 'livenet';
if (opts.mnemonic) { if (opts.mnemonic) {
try { try {
opts.mnemonic = root._normalizeMnemonic(opts.mnemonic); opts.mnemonic = root._normalizeMnemonic(opts.mnemonic);
walletClient.seedFromMnemonic(opts.mnemonic, { walletClient.seedFromMnemonic(opts.mnemonic, {
network: network, network: network,
passphrase: opts.passphrase, passphrase: opts.passphrase,
account: 0, account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
}); });
} catch (ex) { } catch (ex) {
$log.info(ex); $log.info(ex);
return cb(gettext('Could not create: Invalid wallet seed')); return cb(gettext('Could not create: Invalid wallet seed'));
@ -197,7 +198,8 @@ angular.module('copayApp.services')
} else if (opts.extendedPublicKey) { } else if (opts.extendedPublicKey) {
try { try {
walletClient.seedFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, { walletClient.seedFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
account: 0 account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
}); });
} catch (ex) { } catch (ex) {
$log.warn("Creating wallet from Extended Public Key Arg:", ex, opts); $log.warn("Creating wallet from Extended Public Key Arg:", ex, opts);
@ -311,7 +313,7 @@ angular.module('copayApp.services')
walletId: walletId walletId: walletId
}); });
delete root.walletClients[walletId]; delete root.walletClients[walletId];
root.focusedClient = null; root.focusedClient = null;
storageService.clearLastAddress(walletId, function(err) { storageService.clearLastAddress(walletId, function(err) {
@ -361,7 +363,7 @@ angular.module('copayApp.services')
root.setWalletClients(); root.setWalletClients();
root.setAndStoreFocus(walletId, function() { root.setAndStoreFocus(walletId, function() {
storageService.storeProfile(root.profile, function(err){ storageService.storeProfile(root.profile, function(err) {
return cb(err, walletId); return cb(err, walletId);
}); });
}); });
@ -420,7 +422,7 @@ angular.module('copayApp.services')
walletClient.importFromMnemonic(words, { walletClient.importFromMnemonic(words, {
network: opts.networkName, network: opts.networkName,
passphrase: opts.passphrase, passphrase: opts.passphrase,
account: 0, account: opts.account || 0,
}, function(err) { }, function(err) {
if (err) if (err)
return bwsError.cb(err, gettext('Could not import'), cb); return bwsError.cb(err, gettext('Could not import'), cb);
@ -437,7 +439,8 @@ angular.module('copayApp.services')
$log.debug('Importing Wallet XPubKey'); $log.debug('Importing Wallet XPubKey');
walletClient.importFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, { walletClient.importFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
account: 0 account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
}, function(err) { }, function(err) {
if (err) { if (err) {
@ -594,7 +597,7 @@ angular.module('copayApp.services')
var fc = root.focusedClient; var fc = root.focusedClient;
$log.info('Requesting Ledger Chrome app to sign the transaction'); $log.info('Requesting Ledger Chrome app to sign the transaction');
ledger.signTx(txp, 0, function(result) { ledger.signTx(txp, fc.credentials.account, function(result) {
$log.debug('Ledger response', result); $log.debug('Ledger response', result);
if (!result.success) if (!result.success)
return cb(result.message || result.error); return cb(result.message || result.error);
@ -612,7 +615,7 @@ angular.module('copayApp.services')
$log.info('Requesting Trezor to sign the transaction'); $log.info('Requesting Trezor to sign the transaction');
var xPubKeys = lodash.pluck(fc.credentials.publicKeyRing, 'xPubKey'); var xPubKeys = lodash.pluck(fc.credentials.publicKeyRing, 'xPubKey');
trezor.signTx(xPubKeys, txp, 0, function(err, result) { trezor.signTx(xPubKeys, txp, fc.credentials.account, function(err, result) {
if (err) return cb(err); if (err) return cb(err);
$log.debug('Trezor response', result); $log.debug('Trezor response', result);

View File

@ -1,39 +1,21 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('trezor', function($log, $timeout, bwcService, gettext, lodash, bitcore) { .factory('trezor', function($log, $timeout, gettext, lodash, bitcore, hwWallet) {
var root = {}; var root = {};
var SETTLE_TIME = 3000; var SETTLE_TIME = 3000;
root.ENTROPY_INDEX_PATH = "0xb11e/";
root.callbacks = {}; root.callbacks = {};
root.getEntropySource = function(isMultisig, account, callback) {
root._err = function(data) { root.getXPubKey(hwWallet.getEntropyPath('trezor', isMultisig, account), function(data) {
var msg = 'TREZOR Error: ' + (data.error || data.message || 'unknown'); if (!data.success)
$log.warn(msg); return callback(hwWallet._err(data));
return msg;
}; return callback(null, hwWallet.pubKeyToEntropySource(data.xpubkey));
root.getEntropySource = function(account, callback) {
var path = root.ENTROPY_INDEX_PATH + account + "'";
var xpub = root.getXPubKey(path, function(data) {
if (!data.success) {
return callback(root._err(data));
}
var b = bwcService.getBitcore();
var x = b.HDPublicKey(data.xpubkey);
data.entropySource = x.publicKey.toString();
return callback(null, data);
}); });
}; };
root.getXPubKeyForAddresses = function(account, callback) {
return root.getXPubKey(root._getPath(account), callback);
};
root.getXPubKey = function(path, callback) { root.getXPubKey = function(path, callback) {
$log.debug('TREZOR deriving xPub path:', path); $log.debug('TREZOR deriving xPub path:', path);
@ -41,20 +23,25 @@ angular.module('copayApp.services')
}; };
root.getInfoForNewWallet = function(account, callback) { root.getInfoForNewWallet = function(isMultisig, account, callback) {
var opts = {}; var opts = {};
root.getEntropySource(account, function(err, data) { root.getEntropySource(isMultisig, account, function(err, data) {
if (err) return callback(err); if (err) return callback(err);
opts.entropySource = data.entropySource; opts.entropySource = data;
$log.debug('Waiting TREZOR to settle...'); $log.debug('Waiting TREZOR to settle...');
$timeout(function() { $timeout(function() {
root.getXPubKeyForAddresses(account, function(data) {
if (!data.success) root.getXPubKey(hwWallet.getAddressPath('trezor', isMultisig, account), function(data) {
return callback(root._err(data)); if (!data.success)
return callback(hwWallet._err(data));
opts.extendedPublicKey = data.xpubkey; opts.extendedPublicKey = data.xpubkey;
opts.externalSource = 'trezor'; opts.externalSource = 'trezor';
opts.externalIndex = account; opts.account = account;
if (isMultisig)
opts.derivationStrategy = 'BIP48';
return callback(null, opts); return callback(null, opts);
}); });
}, SETTLE_TIME); }, SETTLE_TIME);
@ -107,10 +94,13 @@ angular.module('copayApp.services')
if (txp.addressType == 'P2PKH') { if (txp.addressType == 'P2PKH') {
$log.debug("Trezor signing uni-sig p2pkh. Account:", account);
var inAmount = 0; var inAmount = 0;
inputs = lodash.map(txp.inputs, function(i) { inputs = lodash.map(txp.inputs, function(i) {
$log.debug("Trezor TX input path:", i.path);
var pathArr = i.path.split('/'); var pathArr = i.path.split('/');
var n = [44 | 0x80000000, 0 | 0x80000000, account | 0x80000000, parseInt(pathArr[1]), parseInt(pathArr[2])]; var n = [hwWallet.UNISIG_ROOTPATH | 0x80000000, 0 | 0x80000000, account | 0x80000000, parseInt(pathArr[1]), parseInt(pathArr[2])];
inAmount += i.satoshis; inAmount += i.satoshis;
return { return {
address_n: n, address_n: n,
@ -121,8 +111,9 @@ angular.module('copayApp.services')
var change = inAmount - txp.fee - txp.amount; var change = inAmount - txp.fee - txp.amount;
if (change > 0) { if (change > 0) {
$log.debug("Trezor TX change path:", txp.changeAddress.path);
var pathArr = txp.changeAddress.path.split('/'); var pathArr = txp.changeAddress.path.split('/');
var n = [44 | 0x80000000, 0 | 0x80000000, account | 0x80000000, parseInt(pathArr[1]), parseInt(pathArr[2])]; var n = [hwWallet.UNISIG_ROOTPATH | 0x80000000, 0 | 0x80000000, account | 0x80000000, parseInt(pathArr[1]), parseInt(pathArr[2])];
tmpOutputs.push({ tmpOutputs.push({
address_n: n, address_n: n,
@ -133,8 +124,9 @@ angular.module('copayApp.services')
} else { } else {
// P2SH Wallet // P2SH Wallet, multisig wallet
var inAmount = 0; var inAmount = 0;
$log.debug("Trezor signing multi-sig p2sh. Account:", account);
var sigs = xPubKeys.map(function(v) { var sigs = xPubKeys.map(function(v) {
return ''; return '';
@ -142,8 +134,9 @@ angular.module('copayApp.services')
inputs = lodash.map(txp.inputs, function(i) { inputs = lodash.map(txp.inputs, function(i) {
$log.debug("Trezor TX input path:", i.path);
var pathArr = i.path.split('/'); var pathArr = i.path.split('/');
var n = [44 | 0x80000000, 0 | 0x80000000, account | 0x80000000, parseInt(pathArr[1]), parseInt(pathArr[2])]; var n = [hwWallet.MULTISIG_ROOTPATH | 0x80000000, 0 | 0x80000000, account | 0x80000000, parseInt(pathArr[1]), parseInt(pathArr[2])];
var np = n.slice(3); var np = n.slice(3);
inAmount += i.satoshis; inAmount += i.satoshis;
@ -171,8 +164,9 @@ angular.module('copayApp.services')
var change = inAmount - txp.fee - txp.amount; var change = inAmount - txp.fee - txp.amount;
if (change > 0) { if (change > 0) {
$log.debug("Trezor TX change path:", txp.changeAddress.path);
var pathArr = txp.changeAddress.path.split('/'); var pathArr = txp.changeAddress.path.split('/');
var n = [44 | 0x80000000, 0 | 0x80000000, account | 0x80000000, parseInt(pathArr[1]), parseInt(pathArr[2])]; var n = [hwWallet.MULTISIG_ROOTPATH | 0x80000000, 0 | 0x80000000, account | 0x80000000, parseInt(pathArr[1]), parseInt(pathArr[2])];
var np = n.slice(3); var np = n.slice(3);
var orderedPubKeys = root._orderPubKeys(xPubKeys, np); var orderedPubKeys = root._orderPubKeys(xPubKeys, np);
@ -183,18 +177,6 @@ angular.module('copayApp.services')
}; };
})); }));
// 6D
// 6C
// Addr: 3HFkHufeSaqJtqby8G9RiajaL6HdQDypRT
//
//
//(sin reverse)
// 6C
// 6D
// Addr: 3KCPRDXpmovs9nFvJHJjjsyoBDXXUZ2Frg
// "asm" : "2 03e53b2f69e1705b253029aae2591fbd0e799ed8071c8588a545b2d472dd12df88 0379797abc21d6f82c7f0aba78fd3888d8ae75ec56a10509b20feedbeac20285d9 2 OP_CHECKMULTISIG",
//
tmpOutputs.push({ tmpOutputs.push({
address_n: n, address_n: n,
amount: change, amount: change,
@ -226,17 +208,13 @@ angular.module('copayApp.services')
outputs = JSON.parse(JSON.stringify(outputs)); outputs = JSON.parse(JSON.stringify(outputs));
$log.debug('Signing with TREZOR', inputs, outputs); $log.debug('Signing with TREZOR', inputs, outputs);
TrezorConnect.signTx(inputs, outputs, function(result) { TrezorConnect.signTx(inputs, outputs, function(res) {
if (!data.success) if (!res.success)
return callback(root._err(data)); return callback(hwWallet._err(res));
callback(null, result); callback(null, res);
}); });
}; };
root._getPath = function(account) {
return "44'/0'/" + account + "'";
}
return root; return root;
}); });

2
src/js/trezor-url.js Normal file
View File

@ -0,0 +1,2 @@
window.TREZOR_CHROME_URL = './bower_components/trezor-connect/chrome/wrapper.html';

View File

@ -1,372 +0,0 @@
window.TrezorConnect = (function () {
'use strict';
var CONNECT_ORIGIN = 'https://trezor.github.io';
var CONNECT_PATH = CONNECT_ORIGIN + '/connect';
var CONNECT_POPUP = CONNECT_PATH + '/popup/popup.html';
var ERR_TIMED_OUT = 'Loading timed out';
var ERR_WINDOW_CLOSED = 'Window closed';
var ERR_ALREADY_WAITING = 'Already waiting for a response';
var manager = new PopupManager(
CONNECT_POPUP,
CONNECT_ORIGIN,
'trezor-connect',
function () {
var w = 600;
var h = 500;
var x = (screen.width - w) / 2;
var y = (screen.height - h) / 3;
var params =
'height=' + h +
',width=' + w +
',left=' + x +
',top=' + y +
',menubar=no' +
',toolbar=no' +
',location=no' +
',personalbar=no' +
',status=no';
return params;
}
);
/**
* Public API.
*/
function TrezorConnect() {
/**
* Popup errors.
*/
this.ERR_TIMED_OUT = ERR_TIMED_OUT;
this.ERR_WINDOW_CLOSED = ERR_WINDOW_CLOSED;
this.ERR_ALREADY_WAITING = ERR_ALREADY_WAITING;
/**
* @typedef XPubKeyResult
* @param {boolean} success
* @param {?string} error
* @param {?string} xpubkey serialized extended public key
* @param {?string} path BIP32 serializd path of the key
*/
/**
* Load BIP32 extended public key by path.
*
* Path can be specified either in the string form ("m/44'/1/0") or as
* raw integer array. In case you omit the path, user is asked to select
* a BIP32 account to export, and the result contains m/44'/0'/x' node
* of the account.
*
* @param {?(string|array<number>)} path
* @param {function(XPubKeyResult)} callback
*/
this.getXPubKey = function (path, callback) {
if (typeof path === 'string') {
path = parseHDPath(path);
}
manager.sendWithChannel({
'type': 'xpubkey',
'path': path
}, function (result) {
manager.close();
callback(result);
});
};
/**
* @typedef SignTxResult
* @param {boolean} success
* @param {?string} error
* @param {?string} serialized_tx serialized tx, in hex, including signatures
* @param {?array<string>} signatures array of input signatures, in hex
*/
/**
* Sign a transaction in the device and return both serialized
* transaction and the signatures.
*
* @param {array<TxInputType>} inputs
* @param {array<TxOutputType>} outputs
* @param {function(SignTxResult)} callback
*
* @see https://github.com/trezor/trezor-common/blob/master/protob/types.proto
*/
this.signTx = function (inputs, outputs, callback) {
manager.sendWithChannel({
'type': 'signtx',
'inputs': inputs,
'outputs': outputs
}, function (result) {
manager.close();
callback(result);
});
};
/**
* @typedef RequestLoginResult
* @param {boolean} success
* @param {?string} error
* @param {?string} public_key public key used for signing, in hex
* @param {?string} signature signature, in hex
*/
/**
* Sign a login challenge for active origin.
*
* @param {?string} hosticon
* @param {string} challenge_hidden
* @param {string} challenge_visual
* @param {string|function(RequestLoginResult)} callback
*
* @see https://github.com/trezor/trezor-common/blob/master/protob/messages.proto
*/
this.requestLogin = function (
hosticon,
challenge_hidden,
challenge_visual,
callback
) {
if (typeof callback === 'string') {
// special case for a login through <trezor:login> button.
// `callback` is name of global var
callback = window[callback];
}
if (!callback) {
throw new TypeError('TrezorConnect: login callback not found');
}
manager.sendWithChannel({
'type': 'login',
'icon': hosticon,
'challenge_hidden': challenge_hidden,
'challenge_visual': challenge_visual
}, function (result) {
manager.close();
callback(result);
});
};
var LOGIN_CSS =
'<style>@import url("' + CONNECT_PATH + '/login_buttons.css")</style>';
var LOGIN_ONCLICK =
'TrezorConnect.requestLogin('
+ "'@hosticon@','@challenge_hidden@','@challenge_visual@','@callback@'"
+ ')';
var LOGIN_HTML =
'<div id="trezorconnect-wrapper">'
+ ' <a id="trezorconnect-button" onclick="' + LOGIN_ONCLICK + '">'
+ ' <span id="trezorconnect-icon"></span>'
+ ' <span id="trezorconnect-text">@text@</span>'
+ ' </a>'
+ ' <span id="trezorconnect-info">'
+ ' <a id="trezorconnect-infolink" href="https://www.buytrezor.com/"'
+ ' target="_blank">What is TREZOR?</a>'
+ ' </span>'
+ '</div>';
/**
* Find <trezor:login> elements and replace them with login buttons.
* It's not required to use these special elements, feel free to call
* `TrezorConnect.requestLogin` directly.
*/
this.renderLoginButtons = function () {
var elements = document.getElementsByTagName('trezor:login');
for (var i = 0; i < elements.length; i++) {
var e = elements[i];
var text = e.getAttribute('text') || 'Sign in with TREZOR';
var callback = e.getAttribute('callback') || '';
var hosticon = e.getAttribute('icon') || '';
var challenge_hidden = e.getAttribute('challenge_hidden') || '';
var challenge_visual = e.getAttribute('challenge_visual') || '';
// it's not valid to put markup into attributes, so let users
// supply a raw text and make TREZOR bold
text = text.replace('TREZOR', '<strong>TREZOR</strong>');
e.parentNode.innerHTML =
LOGIN_CSS + LOGIN_HTML
.replace('@text@', text)
.replace('@callback@', callback)
.replace('@hosticon@', hosticon)
.replace('@challenge_hidden@', challenge_hidden)
.replace('@challenge_visual@', challenge_visual);
}
};
}
var exports = new TrezorConnect();
exports.renderLoginButtons();
return exports;
/*
* `getXPubKey()`
*/
function parseHDPath(string) {
return string
.toLowerCase()
.split('/')
.filter(function (p) { return p !== 'm'; })
.map(function (p) {
var n = parseInt(p);
if (p[p.length - 1] === "'") { // hardened index
n = n | 0x80000000;
}
return n;
});
}
/*
* Popup management
*/
function Popup(url, name, params) {
var w = window.open(url, name, params);
var interval;
var iterate = function () {
if (w.closed) {
clearInterval(interval);
if (this.onclose) {
this.onclose();
}
}
}.bind(this);
interval = setInterval(iterate, 100);
this.window = w;
this.onclose = null;
}
function Channel(target, origin, waiting) {
var respond = function (data) {
if (waiting) {
var callback = waiting;
waiting = null;
callback(data);
}
};
var receive = function (event) {
if (event.source === target && event.origin === origin) {
respond(event.data);
}
};
window.addEventListener('message', receive);
this.respond = respond;
this.close = function () {
window.removeEventListener('message', receive);
};
this.send = function (value, callback) {
console.log('[trezor.js.270:value:]',value); //TODO
if (waiting === null) {
waiting = callback;
target.postMessage(value, origin);
} else {
throw new Error(ERR_ALREADY_WAITING);
}
};
}
function ConnectedChannel(url, origin, name, params) {
var ready = function () {
clearTimeout(this.timeout);
this.popup.onclose = null;
this.ready = true;
this.onready();
}.bind(this);
var closed = function () {
clearTimeout(this.timeout);
this.channel.close();
this.onerror(new Error(ERR_WINDOW_CLOSED));
}.bind(this);
var timedout = function () {
this.popup.onclose = null;
this.popup.window.close();
this.channel.close();
this.onerror(new Error(ERR_TIMED_OUT));
}.bind(this);
this.popup = new Popup(url, name, params);
this.channel = new Channel(this.popup.window, origin, ready);
this.timeout = setTimeout(timedout, 5000);
this.popup.onclose = closed;
this.ready = false;
this.onready = null;
this.onerror = null;
}
function PopupManager(url, origin, name, onparams) {
var cc = null;
var closed = function () {
cc.channel.respond(new Error(ERR_WINDOW_CLOSED));
cc.channel.close();
cc = null;
};
var open = function (callback) {
cc = new ConnectedChannel(url, origin, name, onparams());
cc.onready = function () {
cc.popup.onclose = closed;
callback(cc.channel);
};
cc.onerror = function (error) {
cc = null;
callback(error);
};
};
this.close = function () {
if (cc) {
cc.popup.window.close();
}
};
this.waitForChannel = function (callback) {
if (cc) {
if (cc.ready) {
callback(cc.channel);
} else {
callback(new Error(ERR_ALREADY_WAITING));
}
} else {
open(callback);
}
};
this.sendWithChannel = function (message, callback) {
var onresponse = function (response) {
if (response instanceof Error) {
callback({success: false, error: response.message});
} else {
callback(response);
}
};
var onchannel = function (channel) {
if (channel instanceof Error) {
callback({success: false, error: channel.message});
} else {
channel.send(message, onresponse);
}
}
this.waitForChannel(onchannel);
};
}
}());