mirror of https://github.com/BTCPrivate/copay.git
commit
8b1130d66b
|
@ -37,8 +37,7 @@ module.exports = function(grunt) {
|
|||
'src/js/routes.js',
|
||||
'src/js/services/*.js',
|
||||
'src/js/models/*.js',
|
||||
'src/js/controllers/*.js',
|
||||
'src/js/trezor.js'
|
||||
'src/js/controllers/*.js'
|
||||
],
|
||||
tasks: ['concat:js']
|
||||
}
|
||||
|
@ -79,7 +78,8 @@ module.exports = function(grunt) {
|
|||
'src/js/translations.js',
|
||||
'src/js/version.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'
|
||||
},
|
||||
|
|
|
@ -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'`.
|
|
@ -8,7 +8,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"angular": "1.4.6",
|
||||
"angular-bitcore-wallet-client": "1.1.2",
|
||||
"angular-bitcore-wallet-client": "1.1.6",
|
||||
"angular-foundation": "0.7.0",
|
||||
"angular-gettext": "2.1.0",
|
||||
"angular-moment": "0.10.1",
|
||||
|
@ -21,6 +21,7 @@
|
|||
"foundation-icon-fonts": "*",
|
||||
"moment": "2.10.3",
|
||||
"ng-lodash": "0.2.3",
|
||||
"qrcode-decoder-js": "*"
|
||||
"qrcode-decoder-js": "*",
|
||||
"trezor-connect": "~1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,12 @@ echo $CMD
|
|||
$CMD
|
||||
checkOK
|
||||
|
||||
cd $BUILDDIR/../..
|
||||
CMD="rsync -rLRv ./bower_components/trezor-connect/chrome/* $APPDIR"
|
||||
echo $CMD
|
||||
$CMD
|
||||
checkOK
|
||||
|
||||
# Zipping chrome-extension
|
||||
echo "${OpenColor}${Green}* Zipping all chrome-extension files...${CloseColor}"
|
||||
cd $BUILDDIR
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"permissions": [
|
||||
"storage",
|
||||
"notifications",
|
||||
"videoCapture"
|
||||
"videoCapture",
|
||||
"webview"
|
||||
],
|
||||
"app": {
|
||||
"background": {
|
||||
|
|
|
@ -56,21 +56,19 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="large-12 columns" ng-hide="create.hideWalletName">
|
||||
<label><span translate>Wallet name</span>
|
||||
<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)">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="large-12 columns" ng-show="totalCopayers != 1">
|
||||
<label><span translate>Your nickname</span>
|
||||
<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)">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-hide="create.hideWalletName">
|
||||
<label><span translate>Wallet name</span>
|
||||
<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)">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-show="totalCopayers != 1">
|
||||
<label><span translate>Your nickname</span>
|
||||
<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)">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="row" ng-show="totalCopayers != 1">
|
||||
<div class="large-6 medium-6 columns">
|
||||
|
@ -98,74 +96,80 @@
|
|||
<i ng-if="!hideAdv" class="icon-arrow-up4"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div ng-hide="hideAdv" class="row">
|
||||
<div class="large-12 columns">
|
||||
|
||||
<label ng-show="index.isChromeApp && totalCopayers > 1 " for="hw-ledger" class="oh">
|
||||
<span translate>Use Ledger hardware wallet</span>
|
||||
<switch id="hw-ledger" name="hwLedger" ng-model="hwLedger" ng-change="isTestnet=false" class="green right m5t m10b"></switch>
|
||||
</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>
|
||||
<div>
|
||||
<label for="bws" class="oh">
|
||||
<span>Wallet Service URL</span>
|
||||
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
|
||||
</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>
|
||||
-->
|
||||
<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">
|
||||
<span translate>Specify your wallet seed</span>
|
||||
<switch id="seed" name="setSeed" ng-model="setSeed" class="green right m5t m10b"></switch>
|
||||
</label>
|
||||
<div>
|
||||
<label><span translate>Wallet Seed</span>
|
||||
<select class="m10t" ng-model="seedSource"
|
||||
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 class="input">
|
||||
<input type="text" class="form-control"
|
||||
name="createPassphrase" ng-model="createPassphrase">
|
||||
</div>
|
||||
</label>
|
||||
<div ng-show="create.seedSourceId == 'trezor' || create.seedSourceId == 'ledger'">
|
||||
<label class="oh"><span translate>Account Number</span>
|
||||
<input type="number" id="account" ng-model="account">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="ext-master" class="m10t" ng-show="setSeed">
|
||||
<span translate>Wallet Seed</span>
|
||||
<small translate>Enter the seed words (BIP39)</small>
|
||||
<input id="ext-master"
|
||||
type="text"
|
||||
name="privateKey" ng-model="privateKey">
|
||||
</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 class="input">
|
||||
<input type="text" class="form-control" name="passphrase" ng-model="passphrase">
|
||||
</div>
|
||||
</label>
|
||||
<div class="box-notification" ng-show="create.seedSourceId=='new' && createPassphrase">
|
||||
<span class="text-warning size-14">
|
||||
<i class="fi-alert"></i>
|
||||
<span translate>
|
||||
WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<label for="bws" class="oh">
|
||||
<span>Wallet Service URL</span>
|
||||
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="create.seedSourceId=='new' ">
|
||||
<label for="createPassphrase" ><span translate>Add a Seed Passphrase</span> <small translate>Add an optional passphrase to secure the seed</small>
|
||||
<div class="input">
|
||||
<input type="text" class="form-control"
|
||||
name="createPassphrase" ng-model="createPassphrase">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="box-notification" ng-show="!setSeed && createPassphrase">
|
||||
<span class="text-warning size-14">
|
||||
<i class="fi-alert"></i>
|
||||
<span translate>
|
||||
WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-show="create.seedSourceId=='set'">
|
||||
<label for="ext-master">
|
||||
<span translate>Wallet Seed</span>
|
||||
<small translate>Enter the seed words (BIP39)</small>
|
||||
<input id="ext-master"
|
||||
type="text"
|
||||
name="privateKey" ng-model="privateKey">
|
||||
</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">
|
||||
<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">
|
||||
<span translate>Create new wallet</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- large-12 columns -->
|
||||
</div> <!-- row -->
|
||||
</form>
|
||||
</div>
|
||||
<div class="extra-margin-bottom"></div>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</form>
|
||||
|
||||
<h4></h4>
|
||||
<ul class="no-bullet m0" ng-hide="index.isPrivKeyExternal">
|
||||
<ul class="no-bullet m0" ng-show="index.canSign">
|
||||
<li>
|
||||
<switch id="no-sign" name="noSign" ng-model="noSign" class="green right"></switch>
|
||||
<span translate>Do not include private key</span>
|
||||
|
@ -53,7 +53,7 @@
|
|||
</ul>
|
||||
|
||||
|
||||
<div class="box-notification" ng-show="index.isPrivKeyExternal">
|
||||
<div class="box-notification" ng-show="!index.canSign">
|
||||
<span class="text-warning size-14">
|
||||
<i class="fi-alert"></i>
|
||||
<span translate>
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="create-tab pr small-only-text-center" ng-hide="create.hideTabs">
|
||||
<div class="row">
|
||||
<div class="tab-container small-4 medium-4 large-4" ng-class="{'selected': type =='12'}">
|
||||
|
@ -46,21 +44,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="type == '12' ">
|
||||
<div class="large-12 columns">
|
||||
<form name="importForm12" ng-submit="import.importMnemonic(importForm12)" novalidate>
|
||||
<div class="box-notification" ng-show="import.error">
|
||||
<span class="text-warning size-14">
|
||||
{{import.error|translate}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row" ng-show="type == '12' ">
|
||||
<div class="large-12 columns">
|
||||
<form name="importForm12" ng-submit="import.importMnemonic(importForm12)" novalidate>
|
||||
<div class="box-notification" ng-show="import.error">
|
||||
<span class="text-warning size-14">
|
||||
{{import.error|translate}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
<label for="words">
|
||||
<span translate>Type the Seed Word (usually 12 words)</span>:
|
||||
</label>
|
||||
<textarea class="form-control" name="words" ng-model="import.words" rows="2"></textarea>
|
||||
</div>
|
||||
<div >
|
||||
<label for="words">
|
||||
<span translate>Type the Seed Word (usually 12 words)</span>:
|
||||
</label>
|
||||
<textarea class="form-control" name="words" ng-model="import.words" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="m10t oh" ng-init="hideAdv=true">
|
||||
<a class="button outline light-gray expand tiny" ng-click="hideAdv=!hideAdv">
|
||||
|
@ -73,18 +71,78 @@
|
|||
</div>
|
||||
<div ng-hide="hideAdv" class="row">
|
||||
<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">
|
||||
<input type="password" class="form-control" placeholder="{{'Seed passphrase'|translate}}"
|
||||
name="passphrase" ng-model="import.passphrase">
|
||||
</div>
|
||||
</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">
|
||||
<span>Wallet Service URL</span>
|
||||
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
|
||||
|
@ -92,45 +150,57 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button translate type="submit" class="button round expand black m10t"
|
||||
ng-disabled="importForm12.$invalid || import.loading">
|
||||
Import
|
||||
</button>
|
||||
</form>
|
||||
</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>
|
||||
|
||||
|
||||
<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>
|
||||
<form name="importForm3" ng-submit="import.importHW(importForm3)" novalidate>
|
||||
<div class="large-12 columns">
|
||||
<div ng-show="!import.seedOptions[0]">
|
||||
<span translate>No harware wallets supported on this device</span>
|
||||
</div>
|
||||
<div ng-show="import.seedOptions[0]">
|
||||
<div>
|
||||
<label><span translate>Wallet Seed</span>
|
||||
<select class="m10t" ng-model="seedSource"
|
||||
ng-options="seed as seed.label for seed in import.seedOptions"
|
||||
ng-change="import.setSeedSource()">
|
||||
</select>
|
||||
</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>
|
||||
<div ng-show="import.seedSourceId == 'trezor' || import.seedSourceId == 'ledger'">
|
||||
|
||||
<label class="oh"><span translate>Account Number</span>
|
||||
<input type="number" id="account" ng-model="account">
|
||||
</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 ng-show="import.seedSourceId == 'trezor'">
|
||||
<label for="isMultisig" class="oh">
|
||||
<span translate>Shared Wallet</span>
|
||||
<switch id="isMultisig" name="isMultisig" ng-model="isMultisig" class="green right m5t m10b"></switch>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="m10t oh" ng-init="hideAdv=true">
|
||||
|
@ -152,100 +222,16 @@
|
|||
</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"
|
||||
ng-disabled="import.loading || import.ledger">
|
||||
Import from Ledger
|
||||
Import
|
||||
</button>
|
||||
|
||||
<div class="m10t oh" ng-init="hideAdvLedger=true">
|
||||
<a class="button outline light-gray expand tiny" ng-click="hideAdvLedger=!hideAdvLedger">
|
||||
<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> <!-- seedoptions show -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="extra-margin-bottom"></div>
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
<span ng-show="index.network != 'livenet'"> Testnet </span>
|
||||
<span ng-show="!index.canSign && !index.isPrivKeyExternal" translate>No Private key</span>
|
||||
<div ng-show="index.isPrivKeyExternal" style="text-transform: capitalize">
|
||||
<span translate>External Private Key:</span>
|
||||
{{index.externalSource}}
|
||||
</div>
|
||||
|
||||
<span ng-show="index.isShared"><span translate>{{index.m}}-of-{{index.n}}</span></span>
|
||||
<img style="height:1em" ng-show="index.network != 'livenet'" src="img/icon-testnet.svg">
|
||||
<img style="height:1em" ng-show="!index.canSign && !index.isPrivKeyExternal" src="img/icon-read-only.svg">
|
||||
|
||||
<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-show="index.preferences.email" src="img/icon-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">
|
||||
|
||||
|
|
|
@ -32,118 +32,128 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="large-12 columns">
|
||||
<form name="joinForm" ng-submit="join.join(joinForm)" novalidate>
|
||||
<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 ">
|
||||
<span class="text-warning size-14">
|
||||
{{join.error|translate}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form name="joinForm" ng-submit="join.join(joinForm)" novalidate>
|
||||
<div>
|
||||
<label><span translate>Your nickname</span>
|
||||
<div class="input">
|
||||
<input type="text" placeholder="{{'John'|translate}}" class="form-control" name="myName" ng-model="myName" ng-required="true">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row collapse">
|
||||
<label for="secret" class="left"><span translate>Wallet Invitation</span>
|
||||
<small translate ng-show="joinForm.secret.$pristine">Required</small>
|
||||
</label>
|
||||
<span class="has-error right size-12" ng-show="joinForm.secret.$invalid
|
||||
&& !joinForm.secret.$pristine">
|
||||
<span class="icon-input"><i class="fi-x"></i></span>
|
||||
<span translate>Wallet Invitation is not valid!</span>
|
||||
</span>
|
||||
<small class="icon-input right" ng-show="joinForm.secret.$valid
|
||||
&& !joinForm.secret.$pristine"><i class="fi-check"></i></small>
|
||||
<div class="row collapse">
|
||||
<label for="secret" class="left"><span translate>Wallet Invitation</span>
|
||||
<small translate ng-show="joinForm.secret.$pristine">Required</small>
|
||||
</label>
|
||||
<span class="has-error right size-12" ng-show="joinForm.secret.$invalid
|
||||
&& !joinForm.secret.$pristine">
|
||||
<span class="icon-input"><i class="fi-x"></i></span>
|
||||
<span translate>Wallet Invitation is not valid!</span>
|
||||
</span>
|
||||
<small class="icon-input right" ng-show="joinForm.secret.$valid
|
||||
&& !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 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>
|
||||
|
||||
<a class="button outline light-gray tiny expand" ng-click="join.hideAdv=!join.hideAdv">
|
||||
<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-show="!join.hideAdv">Show advanced options</span>
|
||||
<span translate ng-show="join.hideAdv">Hide advanced options</span>
|
||||
<i ng-show="!join.hideAdv" class="icon-arrow-down4"></i>
|
||||
<i ng-show="join.hideAdv" class="icon-arrow-up4"></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 ng-show="join.hideAdv" class="row">
|
||||
<div class="large-12 columns">
|
||||
</div>
|
||||
|
||||
<label for="hw-ledger" class="oh" ng-show="index.isChromeApp">
|
||||
<span translate>Use Ledger hardware wallet</span>
|
||||
<switch id="hw-ledger" name="hwLedger" ng-model="hwLedger" class="green right m5t m10b"></switch>
|
||||
</label>
|
||||
<div ng-hide="hideAdv" class="row">
|
||||
<div class="large-12 columns">
|
||||
<div>
|
||||
<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">
|
||||
<span translate>Use TREZOR hardware wallet</span>
|
||||
<switch id="hw-trezor" name="hwTrezor" ng-model="hwTrezor" class="green right m5t m10b"></switch>
|
||||
</label>
|
||||
<div>
|
||||
<label><span translate>Wallet Seed</span>
|
||||
<select class="m10t" ng-model="seedSource"
|
||||
ng-options="seed as seed.label for seed in join.seedOptions"
|
||||
ng-change="join.setSeedSource()">
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- TODO account
|
||||
<div class="large-12 columns" ng-hide="!hwLedger">
|
||||
<label class="oh">
|
||||
<span translate>Ledger Slot</span>
|
||||
<select class="m10t" ng-model="externalIndex" ng-options="externalIndex as externalIndex for externalIndex in join.externalIndexValues">
|
||||
<div ng-show="join.seedSourceId == 'trezor' || join.seedSourceId == 'ledger'">
|
||||
|
||||
<label class="oh"><span translate>Account</span>
|
||||
<select class="m10t" ng-model="account" ng-options="externalIndex as externalIndex for externalIndex in join.accountValues">
|
||||
</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 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>
|
||||
-->
|
||||
<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="input">
|
||||
<input type="text" class="form-control"
|
||||
name="createPassphrase" ng-model="createPassphrase">
|
||||
</div>
|
||||
</label>
|
||||
<div class="box-notification" ng-show="join.seedSourceId=='new' && createPassphrase">
|
||||
<span class="text-warning size-14">
|
||||
<i class="fi-alert"></i>
|
||||
<span translate>
|
||||
WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<label for="ext-master" class="m10t" ng-show="setSeed">
|
||||
<span translate>Wallet Seed</span>
|
||||
<small translate>Enter the seed words (BIP39)</small>
|
||||
<input id="ext-master"
|
||||
type="text"
|
||||
name="privateKey" ng-model="privateKey">
|
||||
</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 class="input">
|
||||
<input type="text" class="form-control" name="passphrase" ng-model="passphrase">
|
||||
</div>
|
||||
</label>
|
||||
<div ng-show="join.seedSourceId=='new' ">
|
||||
<label for="createPassphrase" ><span translate>Add a Seed Passphrase</span> <small translate>Add an optional passphrase to secure the seed</small>
|
||||
<div class="input">
|
||||
<input type="text" class="form-control"
|
||||
name="createPassphrase" ng-model="createPassphrase">
|
||||
</div>
|
||||
</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>
|
||||
<div ng-show="join.seedSourceId=='set'">
|
||||
<label for="ext-master">
|
||||
<span translate>Wallet Seed</span>
|
||||
<small translate>Enter the seed words (BIP39)</small>
|
||||
<input id="ext-master"
|
||||
type="text"
|
||||
name="privateKey" ng-model="privateKey">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="box-notification" ng-show="!setSeed && createPassphrase">
|
||||
<span class="text-warning size-14">
|
||||
<i class="fi-alert"></i>
|
||||
<span translate>
|
||||
WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-show="join.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="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"
|
||||
ng-disabled="joinForm.$invalid || join.loading">Join</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<button translate type="submit" class="button expand black m0 round"
|
||||
ng-disabled="joinForm.$invalid || join.loading">Join</button>
|
||||
</div> <!-- large-12 columns -->
|
||||
</div> <!-- row -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="extra-margin-bottom"></div>
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -62,12 +62,12 @@
|
|||
<div translate>Advanced</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-show="!index.noFocusedWallet && (!index.isPrivKeyExternal || preferences.touchidAvailable)">
|
||||
<h4 translate>
|
||||
<div ng-show="!index.noFocusedWallet && index.canSign">
|
||||
<h4 translate ng-show="index.canSign">
|
||||
Spending Restrictions
|
||||
</h4>
|
||||
<ul class="no-bullet m0 ">
|
||||
<li ng-show="!index.isPrivKeyExternal">
|
||||
<ul class="no-bullet m0">
|
||||
<li>
|
||||
<switch id="network-name" name="encrypt" ng-model="encrypt" class="green right"></switch>
|
||||
<div translate>Request Password</div>
|
||||
</li>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
</span>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="line-b p20 oh">
|
||||
<span translate>Wallet Id</span>
|
||||
<span class="right text-gray enable_text_select">
|
||||
|
@ -29,6 +30,8 @@
|
|||
</span>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li class="line-b p20 oh">
|
||||
<span translate>Wallet Network</span>
|
||||
<span class="right text-gray">
|
||||
|
@ -52,6 +55,39 @@
|
|||
</span>
|
||||
</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>
|
||||
<li ng-repeat="pk in pubKeys">
|
||||
<div class="row collapse">
|
||||
|
|
|
@ -118,13 +118,11 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="wallet-info">
|
||||
<div ng-show="index.isShared" ng-click="openCopayersModal(index.copayers, index.copayerId)">
|
||||
<div ng-show="index.isShared">
|
||||
<p class="m0">
|
||||
{{(index.alias || index.walletName)}}
|
||||
</p>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -444,7 +442,7 @@
|
|||
|
||||
-->
|
||||
<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="oh text-center">
|
||||
<span ng-show="index.txHistoryError && !index.notAuthorized" ng-click='index.updateTxHistory()'>
|
||||
|
@ -457,7 +455,7 @@
|
|||
</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="spinner">
|
||||
<div class="rect1"></div>
|
||||
|
@ -467,18 +465,16 @@
|
|||
<div class="rect5"></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>
|
||||
Please stand by.
|
||||
</p>
|
||||
</div>
|
||||
<div ng-if="!index.isCordova && index.txHistory[0] && !index.updatingTxHistory" class="m20t text-center">
|
||||
<input id="export_file" type="file" nwsaveas="Copay-{{index.alias || index.walletName}}.csv" accept=".csv" style="display:none">
|
||||
<a class="text-gray size-12" ng-click="index.csvHistory();">
|
||||
<i class="fi-page-export-csv"></i>
|
||||
<span translate>Download CSV file</span>
|
||||
</a>
|
||||
</div>
|
||||
<div ng-show="index.txProgress > 6" translate class="size-14 text-gray m20t">
|
||||
<b>{{index.txProgress}}</b> Transactions<br>
|
||||
Downloaded
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="index.txHistory[0]">
|
||||
<div ng-repeat="btx in index.txHistory"
|
||||
ng-click="home.openTxModal(btx)"
|
||||
|
@ -522,6 +518,33 @@
|
|||
</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 class="extra-margin-bottom"></div>
|
||||
</div> <!-- END History -->
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
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 defaults = configService.getDefaults();
|
||||
this.isWindowsPhoneApp = isMobile.Windows() && isCordova;
|
||||
$scope.account = 1;
|
||||
|
||||
/* For compressed keys, m*73 + n*34 <= 496 */
|
||||
var COPAYER_PAIR_LIMITS = {
|
||||
|
@ -25,6 +26,7 @@ angular.module('copayApp.controllers').controller('createController',
|
|||
|
||||
var defaults = configService.getDefaults();
|
||||
$scope.bwsurl = defaults.bws.url;
|
||||
$scope.derivationPath = derivationPathHelper.default;
|
||||
|
||||
// ng-repeat defined number of times instead of repeating over array?
|
||||
this.getNumber = function(num) {
|
||||
|
@ -38,11 +40,47 @@ angular.module('copayApp.controllers').controller('createController',
|
|||
$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);
|
||||
$scope.totalCopayers = defaults.wallet.totalCopayers;
|
||||
|
||||
this.setTotalCopayers = function(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) {
|
||||
|
@ -50,23 +88,36 @@ angular.module('copayApp.controllers').controller('createController',
|
|||
this.error = gettext('Please enter the required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
var opts = {
|
||||
m: $scope.requiredCopayers,
|
||||
n: $scope.totalCopayers,
|
||||
name: form.walletName.$modelValue,
|
||||
myName: $scope.totalCopayers > 1 ? form.myName.$modelValue : null,
|
||||
networkName: form.isTestnet.$modelValue ? 'testnet' : 'livenet',
|
||||
bwsurl: $scope.bwsurl
|
||||
bwsurl: $scope.bwsurl,
|
||||
};
|
||||
var setSeed = form.setSeed.$modelValue;
|
||||
var setSeed = self.seedSourceId == 'set';
|
||||
if (setSeed) {
|
||||
var words = form.privateKey.$modelValue;
|
||||
|
||||
var words = form.privateKey.$modelValue || '';
|
||||
if (words.indexOf(' ') == -1 && words.indexOf('prv') == 1 && words.length > 108) {
|
||||
opts.extendedPrivateKey = words;
|
||||
} else {
|
||||
opts.mnemonic = words;
|
||||
}
|
||||
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 {
|
||||
opts.passphrase = form.createPassphrase.$modelValue;
|
||||
}
|
||||
|
@ -76,14 +127,21 @@ angular.module('copayApp.controllers').controller('createController',
|
|||
return;
|
||||
}
|
||||
|
||||
if (form.hwLedger.$modelValue || form.hwTrezor.$modelValue) {
|
||||
self.hwWallet = form.hwLedger.$modelValue ? 'Ledger' : 'TREZOR';
|
||||
if (self.seedSourceId == 'ledger' || self.seedSourceId == '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
|
||||
var account = 0;
|
||||
src.getInfoForNewWallet(account, function(err, lopts) {
|
||||
opts.account = account;
|
||||
self.hwWallet = self.seedSourceId == 'ledger' ? 'Ledger' : 'Trezor';
|
||||
var src = self.seedSourceId == 'ledger' ? ledger : trezor;
|
||||
|
||||
src.getInfoForNewWallet(opts.n > 1, account, function(err, lopts) {
|
||||
self.hwWallet = false;
|
||||
if (err) {
|
||||
self.error = err;
|
||||
|
@ -141,4 +199,7 @@ angular.module('copayApp.controllers').controller('createController',
|
|||
$scope.$on("$destroy", function() {
|
||||
$rootScope.hideWalletNavigation = false;
|
||||
});
|
||||
|
||||
updateSeedSourceSelect(1);
|
||||
self.setSeedSource('new');
|
||||
});
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
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 reader = new FileReader();
|
||||
var defaults = configService.getDefaults();
|
||||
$scope.bwsurl = defaults.bws.url;
|
||||
$scope.derivationPath = derivationPathHelper.default;
|
||||
$scope.account = 1;
|
||||
|
||||
window.ignoreMobilePause = true;
|
||||
$scope.$on('$destroy', function() {
|
||||
|
@ -15,6 +17,27 @@ angular.module('copayApp.controllers').controller('importController',
|
|||
}, 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) {
|
||||
$scope.type = type;
|
||||
this.error = null;
|
||||
|
@ -173,23 +196,23 @@ angular.module('copayApp.controllers').controller('importController',
|
|||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
this.importTrezor = function(form) {
|
||||
this.importTrezor = function(account, isMultisig) {
|
||||
var self = this;
|
||||
if (form.$invalid) {
|
||||
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) {
|
||||
trezor.getInfoForNewWallet(isMultisig, account, function(err, lopts) {
|
||||
self.hwWallet = false;
|
||||
if (err) {
|
||||
self.error = err;
|
||||
|
@ -217,18 +240,53 @@ angular.module('copayApp.controllers').controller('importController',
|
|||
}, 100);
|
||||
};
|
||||
|
||||
this.importLedger = function(form) {
|
||||
var self = this;
|
||||
if (form.$invalid) {
|
||||
this.importHW = function(form) {
|
||||
if (form.$invalid || $scope.account < 0 ) {
|
||||
this.error = gettext('There is an error in the form');
|
||||
$timeout(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
return;
|
||||
}
|
||||
self.hwWallet = 'Ledger';
|
||||
// TODO account
|
||||
ledger.getInfoForNewWallet(0, function(err, lopts) {
|
||||
this.error = '';
|
||||
|
||||
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;
|
||||
if (err) {
|
||||
self.error = err;
|
||||
|
@ -255,4 +313,6 @@ angular.module('copayApp.controllers').controller('importController',
|
|||
}, 100);
|
||||
};
|
||||
|
||||
updateSeedSourceSelect();
|
||||
self.setSeedSource('new');
|
||||
});
|
||||
|
|
|
@ -7,7 +7,8 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
self.isChromeApp = isChromeApp;
|
||||
self.isSafari = isMobile.Safari();
|
||||
self.onGoingProcess = {};
|
||||
self.limitHistory = 6;
|
||||
self.historyShowLimit = 10;
|
||||
self.updatingTxHistory = {};
|
||||
|
||||
function strip(number) {
|
||||
return (parseFloat(number.toPrecision(12)));
|
||||
|
@ -83,7 +84,9 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
self.currentFeeLevel = null;
|
||||
self.notAuthorized = false;
|
||||
self.txHistory = [];
|
||||
self.txHistoryUnique = {};
|
||||
self.completeHistory = [];
|
||||
self.txProgress = 0;
|
||||
self.historyShowShowAll = false;
|
||||
self.balanceByAddress = null;
|
||||
self.pendingTxProposalsCountForUs = null;
|
||||
self.setSpendUnconfirmed();
|
||||
|
@ -106,7 +109,13 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
self.isComplete = fc.isComplete();
|
||||
self.canSign = fc.canSign();
|
||||
self.isPrivKeyExternal = fc.isPrivKeyExternal();
|
||||
self.isPrivKeyEncrypted = fc.isPrivKeyEncrypted();
|
||||
self.externalSource = fc.getPrivKeyExternalSourceName();
|
||||
self.account = fc.credentials.account;
|
||||
|
||||
if (self.externalSource == 'trezor')
|
||||
self.account++;
|
||||
|
||||
self.txps = [];
|
||||
self.copayers = [];
|
||||
self.updateColor();
|
||||
|
@ -115,6 +124,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
|
||||
self.initGlidera();
|
||||
|
||||
self.setCustomBWSFlag();
|
||||
if (fc.isPrivKeyExternal()) {
|
||||
self.needsBackup = false;
|
||||
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) {
|
||||
tries = tries || 0;
|
||||
|
||||
|
@ -485,12 +502,13 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
|
||||
var SAFE_CONFIRMATIONS = 6;
|
||||
|
||||
self.setTxHistory = function(txs) {
|
||||
self.processNewTxs = function(txs) {
|
||||
var config = configService.getSync().wallet.settings;
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
self.txHistoryUnique = {};
|
||||
|
||||
var txHistoryUnique = {};
|
||||
var ret = [];
|
||||
self.hasUnsafeConfirmed = false;
|
||||
|
||||
lodash.each(txs, function(tx) {
|
||||
tx = txFormatService.processTx(tx);
|
||||
|
||||
|
@ -505,13 +523,15 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
self.hasUnsafeConfirmed = true;
|
||||
}
|
||||
|
||||
if (!self.txHistoryUnique[tx.txid]) {
|
||||
self.txHistory.push(tx);
|
||||
self.txHistoryUnique[tx.txid] = true;
|
||||
if (!txHistoryUnique[tx.txid]) {
|
||||
ret.push(tx);
|
||||
txHistoryUnique[tx.txid] = true;
|
||||
} else {
|
||||
$log.debug('Ignoring duplicate TX in history: ' + tx.txid)
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
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) {
|
||||
return lodash.map(txs, function(tx) {
|
||||
if (tx.confirmations >= SOFT_CONFIRMATION_LIMIT)
|
||||
|
@ -749,17 +762,14 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
});
|
||||
}
|
||||
|
||||
self.getConfirmedTxs = function(cb) {
|
||||
var fc = profileService.focusedClient;
|
||||
var c = fc.credentials;
|
||||
self.getConfirmedTxs = function(walletId, cb) {
|
||||
|
||||
storageService.getTxHistory(c.walletId, function(err, txs) {
|
||||
storageService.getTxHistory(walletId, function(err, txs) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var localTxs = [];
|
||||
|
||||
if (!txs) {
|
||||
self.showWaitingSign = true;
|
||||
return cb(null, localTxs);
|
||||
}
|
||||
|
||||
|
@ -772,72 +782,107 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
});
|
||||
}
|
||||
|
||||
self.updateLocalTxHistory = function(cb) {
|
||||
self.getConfirmedTxs(function(err, txsFromLocal) {
|
||||
self.updateLocalTxHistory = function(client, cb) {
|
||||
var requestLimit = 6;
|
||||
var walletId = client.credentials.walletId;
|
||||
|
||||
self.getConfirmedTxs(walletId, function(err, txsFromLocal) {
|
||||
if (err) return cb(err);
|
||||
var endingTxid = txsFromLocal[0] ? txsFromLocal[0].txid : null;
|
||||
|
||||
var fc = profileService.focusedClient;
|
||||
var c = fc.credentials;
|
||||
fillTxsObject();
|
||||
function getNewTxs(newTxs, skip, i_cb) {
|
||||
|
||||
function fillTxsObject(txsResult, index) {
|
||||
txsResult = txsResult || [];
|
||||
index = index || 0;
|
||||
self.getTxsFromServer(client, skip, endingTxid, requestLimit, function(err, res, shouldContinue) {
|
||||
if (err) return i_cb(err);
|
||||
|
||||
self.makeTxHistoryRequest(txsResult, index, txsFromLocal[0], function(err, newIndex, exitLoop) {
|
||||
if (err) return cb(err);
|
||||
if (exitLoop) {
|
||||
self.txHistory = [];
|
||||
self.setTxHistory(lodash.compact(txsResult.concat(txsFromLocal)));
|
||||
return storageService.setTxHistory(JSON.stringify(self.txHistory), c.walletId, function() {
|
||||
return cb(null);
|
||||
});
|
||||
|
||||
newTxs = newTxs.concat(lodash.compact(res));
|
||||
skip = skip + requestLimit;
|
||||
|
||||
$log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue);
|
||||
|
||||
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) {
|
||||
var fc = profileService.focusedClient;
|
||||
var c = fc.credentials;
|
||||
var exitLoop = false;
|
||||
self.getTxsFromServer = function(client, skip, endingTxid, limit, cb) {
|
||||
var res = [];
|
||||
|
||||
fc.getTxHistory({
|
||||
skip: index,
|
||||
limit: self.limitHistory + 1
|
||||
}, function(err, txsFromBWC) {
|
||||
client.getTxHistory({
|
||||
skip: skip,
|
||||
limit: limit
|
||||
}, function(err, txsFromServer) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (!txsFromBWC[0])
|
||||
exitLoop = true;
|
||||
if (!txsFromServer.length)
|
||||
return cb();
|
||||
|
||||
lodash.each(txsFromBWC, function(t) {
|
||||
if (!endingTx) txsResult.push(t);
|
||||
else {
|
||||
if (!self.stopSync(t, endingTx) && !exitLoop) {
|
||||
txsResult.push(t);
|
||||
} else {
|
||||
exitLoop = true;
|
||||
}
|
||||
}
|
||||
var res = lodash.takeWhile(txsFromServer, function(tx) {
|
||||
return tx.txid != endingTxid;
|
||||
});
|
||||
index = index + self.limitHistory;
|
||||
return cb(null, index, exitLoop);
|
||||
|
||||
return cb(null, res, res.length == limit);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.updateHistory = function() {
|
||||
var fc = profileService.focusedClient;
|
||||
var walletId = fc.credentials.walletId;
|
||||
|
||||
if (!fc.isComplete() || self.updatingTxHistory[walletId]) return;
|
||||
|
||||
$log.debug('Updating Transaction History');
|
||||
self.txHistoryError = false;
|
||||
self.updatingTxHistory = true;
|
||||
self.updatingTxHistory[walletId] = true;
|
||||
|
||||
$timeout(function() {
|
||||
self.updateLocalTxHistory(function(err) {
|
||||
if (err) self.txHistoryError = true;
|
||||
self.updatingTxHistory = false;
|
||||
self.showWaitingSign = false;
|
||||
self.updateLocalTxHistory(fc, function(err) {
|
||||
self.updatingTxHistory[walletId] = false;
|
||||
if (err)
|
||||
self.txHistoryError = true;
|
||||
|
||||
$rootScope.$apply();
|
||||
});
|
||||
});
|
||||
|
@ -1288,4 +1333,12 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
|||
self.setFocusedWallet();
|
||||
});
|
||||
});
|
||||
|
||||
$rootScope.$on('Local/NewEncryptionSetting', function() {
|
||||
var fc = profileService.focusedClient;
|
||||
self.isPrivKeyEncrypted = fc.isPrivKeyEncrypted();
|
||||
$timeout(function() {
|
||||
$rootScope.$apply();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
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 defaults = configService.getDefaults();
|
||||
$scope.bwsurl = defaults.bws.url;
|
||||
$scope.derivationPath = derivationPathHelper.default;
|
||||
|
||||
this.onQrCodeScanned = function(data) {
|
||||
$scope.secret = data;
|
||||
|
@ -13,6 +14,42 @@ angular.module('copayApp.controllers').controller('joinController',
|
|||
$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) {
|
||||
if (form && form.$invalid) {
|
||||
self.error = gettext('Please enter the required fields');
|
||||
|
@ -23,10 +60,10 @@ angular.module('copayApp.controllers').controller('joinController',
|
|||
var opts = {
|
||||
secret: form.secret.$modelValue,
|
||||
myName: form.myName.$modelValue,
|
||||
bwsurl: $scope.bwsurl
|
||||
bwsurl: $scope.bwsurl,
|
||||
}
|
||||
|
||||
var setSeed = form.setSeed.$modelValue;
|
||||
var setSeed = self.seedSourceId =='set';
|
||||
if (setSeed) {
|
||||
var words = form.privateKey.$modelValue;
|
||||
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.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 {
|
||||
opts.passphrase = form.createPassphrase.$modelValue;
|
||||
}
|
||||
|
@ -44,12 +90,21 @@ angular.module('copayApp.controllers').controller('joinController',
|
|||
return;
|
||||
}
|
||||
|
||||
if (form.hwLedger.$modelValue || form.hwTrezor.$modelValue) {
|
||||
self.hwWallet = form.hwLedger.$modelValue ? 'Ledger' : 'TREZOR';
|
||||
var src = form.hwLedger.$modelValue ? ledger : trezor;
|
||||
if (self.seedSourceId == 'ledger' || self.seedSourceId == 'trezor') {
|
||||
var account = $scope.account;
|
||||
if (!account || account < 1) {
|
||||
this.error = gettext('Invalid account number');
|
||||
return;
|
||||
}
|
||||
|
||||
var account = 0;
|
||||
src.getInfoForNewWallet(account, function(err, lopts) {
|
||||
if ( self.seedSourceId == 'trezor')
|
||||
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;
|
||||
if (err) {
|
||||
self.error = err;
|
||||
|
@ -82,4 +137,7 @@ angular.module('copayApp.controllers').controller('joinController',
|
|||
});
|
||||
}, 100);
|
||||
};
|
||||
|
||||
updateSeedSourceSelect();
|
||||
self.setSeedSource('new');
|
||||
});
|
||||
|
|
|
@ -55,6 +55,7 @@ angular.module('copayApp.controllers').controller('preferencesController',
|
|||
return;
|
||||
}
|
||||
profileService.setPrivateKeyEncryptionFC(password, function() {
|
||||
$rootScope.$emit('Local/NewEncryptionSetting');
|
||||
$scope.encrypt = true;
|
||||
});
|
||||
});
|
||||
|
@ -66,6 +67,7 @@ angular.module('copayApp.controllers').controller('preferencesController',
|
|||
return;
|
||||
}
|
||||
profileService.disablePrivateKeyEncryptionFC(function(err) {
|
||||
$rootScope.$emit('Local/NewEncryptionSetting');
|
||||
if (err) {
|
||||
$scope.encrypt = true;
|
||||
$log.error(err);
|
||||
|
|
|
@ -13,7 +13,7 @@ angular.module('copayApp.controllers').controller('preferencesBwsUrlController',
|
|||
this.bwsurl = (config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url;
|
||||
|
||||
this.resetDefaultUrl = function() {
|
||||
this.bwsurl = 'https://bws.bitpay.com/bws/api';
|
||||
this.bwsurl = defaults.bws.url;
|
||||
};
|
||||
|
||||
this.save = function() {
|
||||
|
@ -50,4 +50,4 @@ angular.module('copayApp.controllers').controller('preferencesBwsUrlController',
|
|||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ angular.module('copayApp.controllers').controller('preferencesInformation',
|
|||
var c = fc.credentials;
|
||||
|
||||
this.init = function() {
|
||||
var basePath = profileService.getUtils().getBaseAddressDerivationPath(c.derivationStrategy, c.network, 0);
|
||||
var basePath = c.getBaseAddressDerivationPath();
|
||||
|
||||
$scope.walletName = c.walletName;
|
||||
$scope.walletId = c.walletId;
|
||||
|
|
|
@ -106,36 +106,6 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
|
|||
var cancel_msg = gettextCatalog.getString('Cancel');
|
||||
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) {
|
||||
$rootScope.modalOpened = true;
|
||||
var fc = profileService.focusedClient;
|
||||
|
|
|
@ -21,13 +21,14 @@ angular
|
|||
|
||||
$logProvider.debugEnabled(true);
|
||||
$provide.decorator('$log', ['$delegate',
|
||||
function($delegate) {
|
||||
function($delegate, isDevel) {
|
||||
var historicLog = historicLogProvider.$get();
|
||||
|
||||
['debug', 'info', 'warn', 'error', 'log'].forEach(function(level) {
|
||||
if (isDevel && level == 'error') return;
|
||||
|
||||
var orig = $delegate[level];
|
||||
$delegate[level] = function() {
|
||||
|
||||
if (level == 'error')
|
||||
console.log(arguments);
|
||||
|
||||
|
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('isDevel', function(nodeWebkit, isChromeApp, isMobile) {
|
||||
return !isMobile.any() && !isChromeApp && !nodeWebkit.isDefined();
|
||||
});
|
|
@ -1,43 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services')
|
||||
.factory('ledger', function($log, bwcService, gettext) {
|
||||
.factory('ledger', function($log, bwcService, gettext, hwWallet) {
|
||||
var root = {};
|
||||
var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf";
|
||||
|
||||
// Ledger magic number to get xPub without user confirmation
|
||||
root.ENTROPY_INDEX_PATH = "0xb11e/";
|
||||
|
||||
root.callbacks = {};
|
||||
|
||||
root.hasSession = function() {
|
||||
root._message({
|
||||
command: "has_session"
|
||||
});
|
||||
}
|
||||
|
||||
root.getEntropySource = function(account, callback) {
|
||||
var path = root.ENTROPY_INDEX_PATH + account + "'";
|
||||
var xpub = root.getXPubKey(path, function(data) {
|
||||
if (!data.success) {
|
||||
$log.warn(data.message);
|
||||
return callback(data);
|
||||
}
|
||||
|
||||
var b = bwcService.getBitcore();
|
||||
|
||||
var x = b.HDPublicKey(data.xpubkey);
|
||||
data.entropySource = x.publicKey.toString();
|
||||
return callback(data);
|
||||
root.getEntropySource = function(isMultisig, account, callback) {
|
||||
root.getXPubKey(hwWallet.getEntropyPath('ledger', isMultisig, account), function(data) {
|
||||
if (!data.success)
|
||||
return callback(hwWallet._err(data));
|
||||
|
||||
return callback(null, hwWallet.pubKeyToEntropySource(data.xpubkey));
|
||||
});
|
||||
};
|
||||
|
||||
root.getXPubKeyForAddresses = function(account, callback) {
|
||||
return root.getXPubKey(root._getPath(account), callback);
|
||||
};
|
||||
|
||||
root.getXPubKey = function(path, callback) {
|
||||
|
||||
$log.debug('Ledger deriving xPub path:', path);
|
||||
root.callbacks["get_xpubkey"] = callback;
|
||||
root._messageAfterSession({
|
||||
|
@ -47,35 +31,36 @@ angular.module('copayApp.services')
|
|||
};
|
||||
|
||||
|
||||
root.getInfoForNewWallet = function(account, callback) {
|
||||
root.getInfoForNewWallet = function(isMultisig, account, callback) {
|
||||
var opts = {};
|
||||
root.getEntropySource(account, function(data) {
|
||||
if (!data.success) {
|
||||
$log.warn(data.message);
|
||||
return callback(data.message);
|
||||
}
|
||||
opts.entropySource = data.entropySource;
|
||||
root.getXPubKeyForAddresses(account, function(data) {
|
||||
root.getEntropySource(isMultisig, account, function(err, entropySource) {
|
||||
if (err) return callback(err);
|
||||
|
||||
opts.entropySource = entropySource;
|
||||
root.getXPubKey(hwWallet.getAddressPath('ledger', isMultisig, account), function(data) {
|
||||
if (!data.success) {
|
||||
$log.warn(data.message);
|
||||
return callback(data);
|
||||
}
|
||||
opts.extendedPublicKey = data.xpubkey;
|
||||
opts.externalSource = 'ledger';
|
||||
opts.externalIndex = account;
|
||||
opts.account = account;
|
||||
|
||||
// Old ledger compat
|
||||
opts.derivationStrategy = account ? 'BIP48' : 'BIP44';
|
||||
return callback(null, opts);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
root._signP2SH = function(txp, account, callback) {
|
||||
root._signP2SH = function(txp, account, isMultisig, callback) {
|
||||
root.callbacks["sign_p2sh"] = callback;
|
||||
var redeemScripts = [];
|
||||
var paths = [];
|
||||
var tx = bwcService.getUtils().buildTx(txp);
|
||||
var tx = bwcService.buildTx(txp);
|
||||
for (var i = 0; i < tx.inputs.length; i++) {
|
||||
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 inputs = [];
|
||||
|
@ -98,12 +83,15 @@ angular.module('copayApp.services')
|
|||
};
|
||||
|
||||
root.signTx = function(txp, account, callback) {
|
||||
|
||||
// TODO Compat
|
||||
var isMultisig = true;
|
||||
if (txp.addressType == 'P2PKH') {
|
||||
var msg = 'P2PKH wallets are not supported with ledger';
|
||||
$log.error(msg);
|
||||
return callback(msg);
|
||||
} 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) {
|
||||
var result = {};
|
||||
var inputs = [];
|
||||
|
|
|
@ -46,8 +46,6 @@ angular.module('copayApp.services')
|
|||
client.setNotificationsInterval(BACKGROUND_UPDATE_PERIOD);
|
||||
});
|
||||
root.focusedClient.setNotificationsInterval(FOREGROUND_UPDATE_PERIOD);
|
||||
|
||||
console.log('[profileService.js.49] SETTING...'); //TODO
|
||||
}
|
||||
|
||||
return cb();
|
||||
|
@ -175,14 +173,17 @@ angular.module('copayApp.services')
|
|||
var walletClient = bwcService.getClient();
|
||||
var network = opts.networkName || 'livenet';
|
||||
|
||||
|
||||
if (opts.mnemonic) {
|
||||
try {
|
||||
opts.mnemonic = root._normalizeMnemonic(opts.mnemonic);
|
||||
walletClient.seedFromMnemonic(opts.mnemonic, {
|
||||
network: network,
|
||||
passphrase: opts.passphrase,
|
||||
account: 0,
|
||||
account: opts.account || 0,
|
||||
derivationStrategy: opts.derivationStrategy || 'BIP44',
|
||||
});
|
||||
|
||||
} catch (ex) {
|
||||
$log.info(ex);
|
||||
return cb(gettext('Could not create: Invalid wallet seed'));
|
||||
|
@ -197,7 +198,8 @@ angular.module('copayApp.services')
|
|||
} else if (opts.extendedPublicKey) {
|
||||
try {
|
||||
walletClient.seedFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
|
||||
account: 0
|
||||
account: opts.account || 0,
|
||||
derivationStrategy: opts.derivationStrategy || 'BIP44',
|
||||
});
|
||||
} catch (ex) {
|
||||
$log.warn("Creating wallet from Extended Public Key Arg:", ex, opts);
|
||||
|
@ -311,7 +313,7 @@ angular.module('copayApp.services')
|
|||
walletId: walletId
|
||||
});
|
||||
|
||||
delete root.walletClients[walletId];
|
||||
delete root.walletClients[walletId];
|
||||
root.focusedClient = null;
|
||||
|
||||
storageService.clearLastAddress(walletId, function(err) {
|
||||
|
@ -361,7 +363,7 @@ angular.module('copayApp.services')
|
|||
root.setWalletClients();
|
||||
|
||||
root.setAndStoreFocus(walletId, function() {
|
||||
storageService.storeProfile(root.profile, function(err){
|
||||
storageService.storeProfile(root.profile, function(err) {
|
||||
return cb(err, walletId);
|
||||
});
|
||||
});
|
||||
|
@ -420,7 +422,7 @@ angular.module('copayApp.services')
|
|||
walletClient.importFromMnemonic(words, {
|
||||
network: opts.networkName,
|
||||
passphrase: opts.passphrase,
|
||||
account: 0,
|
||||
account: opts.account || 0,
|
||||
}, function(err) {
|
||||
if (err)
|
||||
return bwsError.cb(err, gettext('Could not import'), cb);
|
||||
|
@ -437,7 +439,8 @@ angular.module('copayApp.services')
|
|||
$log.debug('Importing Wallet XPubKey');
|
||||
|
||||
walletClient.importFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
|
||||
account: 0
|
||||
account: opts.account || 0,
|
||||
derivationStrategy: opts.derivationStrategy || 'BIP44',
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
|
||||
|
@ -594,7 +597,7 @@ angular.module('copayApp.services')
|
|||
var fc = root.focusedClient;
|
||||
$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);
|
||||
if (!result.success)
|
||||
return cb(result.message || result.error);
|
||||
|
@ -612,7 +615,7 @@ angular.module('copayApp.services')
|
|||
$log.info('Requesting Trezor to sign the transaction');
|
||||
|
||||
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);
|
||||
|
||||
$log.debug('Trezor response', result);
|
||||
|
|
|
@ -1,39 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
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 SETTLE_TIME = 3000;
|
||||
|
||||
root.ENTROPY_INDEX_PATH = "0xb11e/";
|
||||
root.callbacks = {};
|
||||
|
||||
|
||||
root._err = function(data) {
|
||||
var msg = 'TREZOR Error: ' + (data.error || data.message || 'unknown');
|
||||
$log.warn(msg);
|
||||
return msg;
|
||||
};
|
||||
|
||||
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.getEntropySource = function(isMultisig, account, callback) {
|
||||
root.getXPubKey(hwWallet.getEntropyPath('trezor', isMultisig, account), function(data) {
|
||||
if (!data.success)
|
||||
return callback(hwWallet._err(data));
|
||||
|
||||
return callback(null, hwWallet.pubKeyToEntropySource(data.xpubkey));
|
||||
});
|
||||
};
|
||||
|
||||
root.getXPubKeyForAddresses = function(account, callback) {
|
||||
return root.getXPubKey(root._getPath(account), callback);
|
||||
};
|
||||
|
||||
root.getXPubKey = function(path, callback) {
|
||||
$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 = {};
|
||||
root.getEntropySource(account, function(err, data) {
|
||||
root.getEntropySource(isMultisig, account, function(err, data) {
|
||||
if (err) return callback(err);
|
||||
opts.entropySource = data.entropySource;
|
||||
opts.entropySource = data;
|
||||
$log.debug('Waiting TREZOR to settle...');
|
||||
$timeout(function() {
|
||||
root.getXPubKeyForAddresses(account, function(data) {
|
||||
if (!data.success)
|
||||
return callback(root._err(data));
|
||||
|
||||
root.getXPubKey(hwWallet.getAddressPath('trezor', isMultisig, account), function(data) {
|
||||
if (!data.success)
|
||||
return callback(hwWallet._err(data));
|
||||
|
||||
opts.extendedPublicKey = data.xpubkey;
|
||||
opts.externalSource = 'trezor';
|
||||
opts.externalIndex = account;
|
||||
opts.account = account;
|
||||
|
||||
if (isMultisig)
|
||||
opts.derivationStrategy = 'BIP48';
|
||||
|
||||
return callback(null, opts);
|
||||
});
|
||||
}, SETTLE_TIME);
|
||||
|
@ -107,10 +94,13 @@ angular.module('copayApp.services')
|
|||
|
||||
if (txp.addressType == 'P2PKH') {
|
||||
|
||||
$log.debug("Trezor signing uni-sig p2pkh. Account:", account);
|
||||
|
||||
var inAmount = 0;
|
||||
inputs = lodash.map(txp.inputs, function(i) {
|
||||
$log.debug("Trezor TX input path:", i.path);
|
||||
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;
|
||||
return {
|
||||
address_n: n,
|
||||
|
@ -121,8 +111,9 @@ angular.module('copayApp.services')
|
|||
|
||||
var change = inAmount - txp.fee - txp.amount;
|
||||
if (change > 0) {
|
||||
$log.debug("Trezor TX change path:", txp.changeAddress.path);
|
||||
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({
|
||||
address_n: n,
|
||||
|
@ -133,8 +124,9 @@ angular.module('copayApp.services')
|
|||
|
||||
} else {
|
||||
|
||||
// P2SH Wallet
|
||||
// P2SH Wallet, multisig wallet
|
||||
var inAmount = 0;
|
||||
$log.debug("Trezor signing multi-sig p2sh. Account:", account);
|
||||
|
||||
var sigs = xPubKeys.map(function(v) {
|
||||
return '';
|
||||
|
@ -142,8 +134,9 @@ angular.module('copayApp.services')
|
|||
|
||||
|
||||
inputs = lodash.map(txp.inputs, function(i) {
|
||||
$log.debug("Trezor TX input path:", i.path);
|
||||
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);
|
||||
|
||||
inAmount += i.satoshis;
|
||||
|
@ -171,8 +164,9 @@ angular.module('copayApp.services')
|
|||
|
||||
var change = inAmount - txp.fee - txp.amount;
|
||||
if (change > 0) {
|
||||
$log.debug("Trezor TX change path:", txp.changeAddress.path);
|
||||
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 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({
|
||||
address_n: n,
|
||||
amount: change,
|
||||
|
@ -226,17 +208,13 @@ angular.module('copayApp.services')
|
|||
outputs = JSON.parse(JSON.stringify(outputs));
|
||||
|
||||
$log.debug('Signing with TREZOR', inputs, outputs);
|
||||
TrezorConnect.signTx(inputs, outputs, function(result) {
|
||||
if (!data.success)
|
||||
return callback(root._err(data));
|
||||
TrezorConnect.signTx(inputs, outputs, function(res) {
|
||||
if (!res.success)
|
||||
return callback(hwWallet._err(res));
|
||||
|
||||
callback(null, result);
|
||||
callback(null, res);
|
||||
});
|
||||
};
|
||||
|
||||
root._getPath = function(account) {
|
||||
return "44'/0'/" + account + "'";
|
||||
}
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
window.TREZOR_CHROME_URL = './bower_components/trezor-connect/chrome/wrapper.html';
|
||||
|
372
src/js/trezor.js
372
src/js/trezor.js
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
}());
|
Loading…
Reference in New Issue