This commit is contained in:
Gustavo Cortez 2014-07-07 18:13:26 -03:00
commit a1e5246727
44 changed files with 3763 additions and 366 deletions

10
.gitignore vendored
View File

@ -55,3 +55,13 @@ coverage/
shell/bin/linux
shell/bin/darwin
shell/bin/win32
shell/scripts/bin
shell/scripts/build
dist/darwin
dist/linux
dist/windows
dist/*.dmg
dist/*.tar.gz
dist/*.exe

View File

@ -88,6 +88,19 @@ Once this script has completed, you can launch the shell-based Copay by running:
npm run shell
```
## Building Native Shell Binaries/Installers (OSX)
```
npm run dist
```
This script will download atom shell binaries and combine them with Copay sources
to build a DMG for osx-x64, an installer EXE for win32, and a .tar.gz for linux-x64.
It was developed to be run on OSX. The outputs are copied to the dist directory.
DMG is created with hdiutil
EXE is created with makensis (brew install makensis)
## Tests
Open test/index.html in your browser to test the models. Install and run karma

View File

@ -18,7 +18,7 @@
"sjcl": "1.0.0",
"file-saver": "*",
"qrcode-decoder-js": "*",
"bitcore": "0.1.24",
"bitcore": "0.1.25",
"angular-moment": "~0.7.1",
"socket.io-client": ">=1.0.0",
"mousetrap": "1.4.6"

View File

@ -197,9 +197,9 @@ span.panel-res {
}
.line-dashed {
border-top: 2px dashed #ccc;
border-top: 1px dashed #ccc;
margin: 1rem 0;
padding: 1rem 0;
/* padding: 1rem 0; */
}
@ -271,7 +271,7 @@ hr { margin: 2.25rem 0;}
.p70l {padding-left: 70px;}
.p5h {padding: 0 5px;}
.p20h {padding: 0 20px;}
.m30v {margin: 30px 0;}
.m30v {/* margin: 30px 0; */}
.m10h {margin:0 10px;}
.m30a {margin: 30px auto;}
.br100 {border-radius: 100%;}
@ -286,6 +286,11 @@ hr { margin: 2.25rem 0;}
display: inline;
float: right;
}
.setup .video-small {
float: none !important;
}
.online {
background-color: black;
border: 3px solid #1ABC9C;

1
dist/README.md vendored Normal file
View File

@ -0,0 +1 @@
This is the destination directory for the built atom-shell binaries and the files used to create them

View File

@ -82,31 +82,10 @@
</div>
<div ng-if='$root.wallet && !$root.wallet.isReady() && !loading'>
<div class="row">
<div class="large-12 medium-12 small-12 columns">
<div class="alert-box secondary radius" ng-if="!$root.wallet.publicKeyRing.isComplete()" data-alert>
<i class="fi-info"></i>
Not all copayers have joined your wallet yet.
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()>1">
{{$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers() }} people have
</span>
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()==1">
One person has
</span>
yet to join.
</div>
<div class="alert-box success radius" ng-if="$root.wallet.publicKeyRing.isComplete()" data-alert>
<i class="fi-check"></i>
All copayers have joined the wallet, it's ready for use!
</div>
</div>
</div>
<div class="row" ng-if="!$root.wallet.publicKeyRing.isComplete()">
<div class="large-12 medium-12 small-12 columns ">
<div class="panel radius m30v">
<h3 class="m15b">Share this secret with your other copayers
<small> for them to join your wallet</small>
</h3>
<div class="row">
<div class="large-9 medium-12 small-12 columns line-dashed-v text-gray">
@ -122,25 +101,63 @@
</div>
</div>
</div>
<div class="row">
<div class="row m10t">
<div class="large-12 medium-12 small-12 columns ">
<div class="box-setup-copayers">
<div class="box-setup-copayers-fix">
<img class="box-setup-copay"
ng-repeat="i in getNumber($root.wallet.totalCopayers) track by $index"
src="./img/satoshi.gif"
title="Copayer {{$index+1}}-{{totalCopayers}}"
ng-class="{'box-setup-copay-required': ($index+1) <= $root.wallet.publicKeyRing.registeredCopayers()}">
<h5>People on this wallet</h5>
<button class="button primary radius right"
ng-click="backupAndOpen()"
ng-disabled="!$root.wallet.publicKeyRing.isComplete()">
Backup keys and continue
</button>
<div class="setup" ng-repeat="c in $root.wallet.getRegisteredPeerIds()">
<video ng-if="$root.videoInfo[c.peerId]"
avatar peer="{{c}}"
autoplay
ng-class="($root.wallet.getOnlinePeerIDs().indexOf(c.peerId) != -1) ? 'online' : 'offline'"
ng-src="{{getVideoURL(c.peerId)}}"
></video>
<img ng-if="!$root.videoInfo[c.peerId]"
avatar peer="{{c}}"
ng-class="($root.wallet.getOnlinePeerIDs().indexOf(c.peerId) != -1) ? 'online' : 'offline'"
src="./img/satoshi.gif"
/>
<span ng-show="c.index==0">
you
</span>
<span ng-show="c.index>0">
{{c.nick}}
[SIN: {{c.peerId}}]
</span>
</div>
<div class="m10" ng-if="!$root.wallet.publicKeyRing.isComplete()">
<i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i>
Waiting for other copayers to join
</div>
</div>
</div>
</div>
<div class="large-12 columns">
<div class="line-dashed"></div>
<button class="button primary radius right"
ng-click="backupAndOpen()"
ng-disabled="!$root.wallet.publicKeyRing.isComplete()">
<span ng-show="$root.wallet.publicKeyRing.isComplete()" >
Backup keys and continue
</span>
<span ng-show="!$root.wallet.publicKeyRing.isComplete()" >
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()>1">
{{$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers() }} people have
</span>
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()==1">
One person has
</span>
yet to join.
</span>
</button>
<!-- <a href="" class="db p10t left" ng-disabled="!$root.wallet.publicKeyRing.isComplete()">Skip Backup</a> -->
</div>
</div>
</div>
@ -156,10 +173,20 @@
<div id="footer" data-ng-controller="FooterController" ng-class="{'footer-home': !$root.wallet || !$root.wallet.isReady()}">
<link rel="stylesheet" ng-href="{{theme}}">
<div ng-show="!$root.wallet">
<div class="large-12 columns text-left">
<div class="large-4 columns text-left">
Copay
<small>v{{version}}</small>
<small ng-if="networkName==='testnet'">[TESTNET]</small>
</div>
<div class="large-4 columns">
</div>
<div class="large-4 columns text-right">
<small>
<a class="text-gray" href="https://copay.io" target="_blank"><b>Copay </b> Project Homapage</a>
</small>
</div>
</div>
<div ng-show="$root.wallet && !$root.wallet.isReady()">
<div class="large-6 medium-6 small-6 columns">
@ -235,6 +262,7 @@
</div>
<div ng-show="!wallets.length">
<h3>Create a new wallet</h3>
<p class="text-gray">
Copay is a free, open-source, multisignature bitcoin wallet. A single-owner bitcoin wallet's security depends on carefully securing the private keys. With copay you can have multiple people controlling the funds, using bitcoin's multisignature functionality, requiring no trust in any third party.
</p>
@ -247,8 +275,8 @@
<h3>Join a Wallet in Creation</h3>
<form name="joinForm" ng-submit="join(joinForm)" novalidate>
<input type="text" class="form-control" placeholder="Paste wallet secret here" name="connectionId" ng-model="connectionId" wallet-secret required>
<input type="password" class="form-control" placeholder="Choose your password" name="joinPassword" ng-model="$parent.joinPassword" check-strength="passwordStrength" tooltip-html-unsafe="Password strength: {{passwordStrength}}<br/><small>Tip: Use lower and uppercase, numbers and symbols</small>" tooltip-trigger="focus" required>
<input type="text" class="form-control" placeholder="Your name (optional)" name="nickname" ng-model="nickname">
<input type="password" class="form-control" placeholder="Choose your password" name="joinPassword" ng-model="$parent.joinPassword" check-strength="passwordStrength" tooltip-html-unsafe="Password strength: {{passwordStrength}}<br/><small>Tip: Use lower and uppercase, numbers and symbols</small>" tooltip-trigger="focus" required>
<button type="submit" class="button primary radius" ng-disabled="joinForm.$invalid || loading" loading="Joining">Join</button>
</form>
</div>
@ -275,29 +303,25 @@
{{ importStatus }}
</div>
<div ng-init="choosefile=0; pastetext=0" ng-show="!loading">
<h3>{{title}}</h3>
<form name="importForm" ng-submit="import(importForm)" novalidate>
<div class="row">
<div class="large-6 large-centered medium-6 medium-centered columns">
<div class="large-6 large-centered medium-8 medium-centered columns">
<h3 style="margin-bottom: 50px;">{{title}}</h3>
<fieldset>
<legend>Select which method want to use to restore</legend>
<label for="backupFile" ng-click="openFileDialog()" class="m10b">&middot; Choose backup file from your computer <i class="fi-laptop"></i></label>
<input type="file" class="form-control" placeholder="Select a backup file" name="backupFile" ng-model="backupFile" ng-file-select ng-show="choosefile">
<label for="backupText" ng-click="openPasteArea()" class="m10b">&middot; Paste backup plain text code <i class="fi-clipboard"></i></label>
<textarea class="form-control" name="backupText" ng-model="backupText" rows="5" ng-show="pastetext"></textarea>
<legend for="backupFile" class="m10b"> Choose backup file from your computer <i class="fi-laptop"></i></legend>
<input type="file" class="form-control" placeholder="Select a backup file" name="backupFile" ng-model="backupFile" ng-file-select>
</fieldset>
</div>
<div class="large-6 large-centered medium-6 medium-centered columns">
<label for="password">Password <small>Required</small></label>
<input type="password" class="form-control" placeholder="Your wallet password" name="password" ng-model="password" required>
</div>
</div>
<div class="row line-dashed">
<div class="large-6 medium-6 small-5 columns text-left">
<a class="button secondary radius" href="#signin">Go back</a>
<div class="large-6 large-centered medium-8 medium-centered columns">
<label for="password">Password <small>Required</small></label>
<input type="password" class="form-control" placeholder="Your wallet password" name="password" ng-model="password" required>
</div>
<div class="large-6 medium-6 small-5 columns text-right">
</div>
<div class="row m15b">
<div class="large-6 columns large-centered">
<div class="line-dashed"></div>
<a class="button secondary radius left" href="#signin">Go back</a>
<button type="submit" class="button primary radius right" ng-disabled="importForm.$invalid" loading="Importing">
Import backup
</button>
@ -314,70 +338,67 @@
<i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i>
Creating wallet...
</div>
<div ng-show="!loading">
<div ng-show="!loading" class="large-8 medium-12 small-12 columns large-centered">
<form name="setupForm" ng-submit="create(setupForm)" novalidate>
<h3>Create new wallet</h3>
<div class="row">
<div class="small-12 medium-8 medium-centered large-8 large-centered columns">
<label>Wallet name <small>Optional</small>
<input type="text" placeholder="Family vacation funds" class="form-control" ng-model="walletName">
</label>
</div>
<div class="small-12 medium-8 medium-centered large-8 large-centered columns">
<div class="row">
<div class="small-12 medium-6 large-6 columns">
<label>Your name <small>Optional</small>
<input type="text" placeholder="Name" class="form-control" ng-model="myNickname">
</label>
</div>
<div class="small-12 medium-6 large-6 columns">
<label>Your Wallet Password <small data-options="disable_for_touch:true" class="has-tip" tooltip="doesn't need to be shared">Required</small>
<input type="password" placeholder="Choose your password" class="form-control"
ng-model="$parent.walletPassword" check-strength="passwordStrength" tooltip-html-unsafe="Password strength: {{passwordStrength}}<br/><small>Tip: Use lower and uppercase, numbers and symbols</small>" tooltip-trigger="focus" required>
</label>
</div>
<div>
<label>Wallet name <small>Optional</small>
<input type="text" placeholder="Family vacation funds" class="form-control" ng-model="walletName">
</label>
</div>
<div>
<div class="row">
<div class="small-12 medium-6 large-6 columns">
<label>Your name <small>Optional</small>
<input type="text" placeholder="Name" class="form-control" ng-model="myNickname">
</label>
</div>
<div class="small-12 medium-6 large-6 columns">
<label>Your Wallet Password <small data-options="disable_for_touch:true" class="has-tip" tooltip="doesn't need to be shared">Required</small>
<input type="password" placeholder="Choose your password" class="form-control"
ng-model="$parent.walletPassword" check-strength="passwordStrength" tooltip-html-unsafe="Password strength: {{passwordStrength}}<br/><small>Tip: Use lower and uppercase, numbers and symbols</small>" tooltip-trigger="focus" required>
</label>
</div>
</div>
<div class="small-12 medium-8 medium-centered large-8 large-centered columns box-setup">
<fieldset>
<div class="row">
<div class="large-6 medium-6 columns line-dashed-v">
<label>Select total number of copayers
<select ng-model="totalCopayers" ng-options="totalCopayers as totalCopayers for totalCopayers in TCValues">
</select>
</label>
</div>
<div class="large-6 medium-6 columns">
<label>Select required signatures
<select ng-model="requiredCopayers" ng-options="requiredCopayers as requiredCopayers for requiredCopayers in RCValues">
</select>
</label>
</div>
</div>
<div class="box-setup">
<fieldset>
<div class="row">
<div class="large-6 medium-6 columns line-dashed-v">
<label>Select total number of copayers
<select ng-model="totalCopayers" ng-options="totalCopayers as totalCopayers for totalCopayers in TCValues">
</select>
</label>
</div>
<div class="large-6 medium-6 columns">
<label>Select required signatures
<select ng-model="requiredCopayers" ng-options="requiredCopayers as requiredCopayers for requiredCopayers in RCValues">
</select>
</label>
</div>
</fieldset>
</div>
<div class="small-12 medium-6 medium-centered large-6 large-centered columns m10b">
<div class="box-setup-copayers">
<div class="box-setup-copayers-fix">
<img class="box-setup-copay" ng-repeat="i in getNumber(totalCopayers) track by $index"
src="./img/satoshi.gif"
title="Copayer {{$index+1}}-{{totalCopayers}}"
ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}">
</div>
</div>
</fieldset>
</div>
<div class="box-setup-copayers">
<div class="box-setup-copayers-fix">
<img class="box-setup-copay" ng-repeat="i in getNumber(totalCopayers) track by $index"
src="./img/satoshi.gif"
title="Copayer {{$index+1}}-{{totalCopayers}}"
ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}">
</div>
</div>
<div class="row line-dashed">
<div class="large-6 medium-6 small-5 columns text-left">
<a class="button secondary radius" href="#signin">Go back</a>
<div class="row">
<div class="line-dashed"></div>
<div class="large-6 medium-6 small-5 columns text-left">
<a class="button secondary radius" href="#signin">Go back</a>
</div>
<div class="large-6 medium-6 small-7 columns text-right">
<button type="submit" class="button primary radius" ng-disabled="setupForm.$invalid || loading">
Create {{requiredCopayers}}-of-{{totalCopayers}} wallet
</button>
</div>
</div>
<div class="large-6 medium-6 small-7 columns text-right">
<button type="submit" class="button primary radius" ng-disabled="setupForm.$invalid || loading">
Create {{requiredCopayers}}-of-{{totalCopayers}} wallet
</button>
</div>
</div>
</form>
</div>
</div>
@ -390,7 +411,7 @@
<div class="row">
<div class="large-9 medium-12 columns" ng-if="addresses[0]">
<div class="large-8 medium-8 columns" ng-init="showAll=0">
<a class="panel radius db" ng-repeat="addr in addresses | limitAddress:showAll"
<a class="panel radius db" ng-repeat="addr in addresses|removeEmpty|limitAddress:showAll"
ng-click="selectAddress(addr)"
ng-class="{selected : addr.address == selectedAddr.address}">
@ -412,7 +433,7 @@
</span>
</a>
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="addresses.length != (addresses|limitAddress).length">
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|removeEmpty).length != (addresses|removeEmpty|limitAddress).length">
<span ng-if="!showAll">Show all</span>
<span ng-if="showAll">Show less</span>
</a>
@ -630,7 +651,7 @@
<script type="text/ng-template" id="send.html">
<div class="send" data-ng-controller="SendController">
<div class="row" ng-show='$root.wallet.isReady()'>
<div class="medium-6 medium-centered large-6 large-centered columns">
<div class="medium-8 medium-centered large-8 large-centered columns">
<h3>{{title}}</h3>
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
<div class="row">
@ -809,7 +830,7 @@
</div>
</div>
<div class="row text-center">
<div class="button radius warning small m30v" ng-really-message="Are you sure to delete this wallet from this computer?" ng-really-click="deleteWallet()">Delete this wallet from this computer</div>
<div class="button radius warning small m30v" ng-really-message="Are you sure to delete this wallet from this computer?" ng-really-click="deleteWallet()"><i class="fi-minus-circle large"></i> Delete this wallet from this computer</div>
</div>
</div>
</script>
@ -829,7 +850,6 @@
<legend>Wallet Unit</legend>
<select class="form-control" ng-model="selectedUnit" ng-options="o.name for o in unitOpts" required>
</select>
<label for="settingsUnit">Prefered Unit for Wallet</label>
</fieldset>
<fieldset>
<legend>Videoconferencing</legend>
@ -845,6 +865,9 @@
<input id="insight-secure" type="checkbox" ng-model="insightSecure" class="form-control" ng-click="changeInsightSSL()">
<label for="insight-secure">Use SSL</label>
<p class="small">
Insight API server is open-source software. You can run your own instance, check <a href="http://insight.is" target="_blank">Insight API Homepage</a>
</fieldset>
<fieldset>
<legend>PeerJS server</legend>
@ -856,6 +879,10 @@
<input type="number" ng-model="networkPort" class="form-control" name="peerjs-port">
<input id="peerjs-secure" type="checkbox" ng-model="networkSecure" class="form-control">
<label for="peerjs-secure">Use SSL</label>
<p class="small">
PeerJS Server is open-source software. You can run your own instance, or use PeerJS Server cloud. Check <a href="http://peerjs.com" target="_blank">PeerJS Server</a>
</fieldset>
</div>
<div class="row">
@ -886,6 +913,21 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
</script>
<!-- URI PAYMENT -->
<script type="text/ng-template" id="uri_payment.html">
<h3 class="text-center">
Preparing payment...
</h3>
<div data-ng-init="" data-ng-controller="UriPaymentController">
<p>Protocol: {{protocol}}</p>
<p>To: {{address}}</p>
<p>Amount: {{amount}}</p>
<p>Message:</p>
<div class="alert-box secondary radius">{{message}}</div>
</div>
</script>
<!-- NOT FOUND -->
<script type="text/ng-template" id="404.html">
<h2 class="text-center">404</h2>
@ -894,6 +936,9 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
</script>
<script src="lib/mousetrap/mousetrap.min.js"></script>
<!-- shell must be loaded before moment due to the way moment loads in a commonjs env -->
<script src="js/shell.js"></script>
<script src="lib/angular/angular.min.js"></script>
<script src="lib/moment/min/moment.min.js"></script>
<script src="lib/angular-moment/angular-moment.js"></script>
@ -902,7 +947,7 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
<script src="lib/angular-route/angular-route.min.js"></script>
<script src="lib/angular-foundation/mm-foundation.min.js"></script>
<script src="lib/angular-foundation/mm-foundation-tpls.min.js"></script>
<script src="lib/peerjs/peer.min.js"></script>
<script src="lib/peer.js"></script> <!-- TODO Change this on PeerJS version 0.3.9 -->
<script src="lib/bitcore/browser/bundle.js"></script>
<script src="lib/crypto-js/rollups/sha256.js"></script>
<script src="lib/crypto-js/rollups/pbkdf2.js"></script>
@ -914,7 +959,6 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
<script src="lib/qrcode-decoder-js/lib/qrcode-decoder.min.js"></script>
<script src="config.js"></script>
<script src="js/shell.js"></script>
<script src="js/copayBundle.js"></script>
<script src="js/app.js"></script>
<script src="js/routes.js"></script>
@ -928,6 +972,7 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
<script src="js/services/notifications.js"></script>
<script src="js/services/backupService.js"></script>
<script src="js/services/isMobile.js"></script>
<script src="js/services/uriHandler.js"></script>
<script src="js/controllers/header.js"></script>
<script src="js/controllers/footer.js"></script>
@ -939,6 +984,7 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
<script src="js/controllers/setup.js"></script>
<script src="js/controllers/import.js"></script>
<script src="js/controllers/settings.js"></script>
<script src="js/controllers/uriPayment.js"></script>
<script src="js/init.js"></script>
</body>

View File

@ -33,7 +33,8 @@ angular.module('copayApp.controllers').controller('AddressesController',
$scope.addresses.push({
'address': addrinfo.addressStr,
'balance': $rootScope.balanceByAddr ? $rootScope.balanceByAddr[addrinfo.addressStr] : 0,
'isChange': addrinfo.isChange
'isChange': addrinfo.isChange,
'owned': addrinfo.owned
});
}
$scope.selectedAddr = $scope.addresses[0];

View File

@ -2,7 +2,7 @@
angular.module('copayApp.controllers').controller('BackupController',
function($scope, $rootScope, $location, $window, $timeout, $modal, backupService, walletFactory, controllerUtils) {
$scope.title = 'Backup';
$scope.title = 'Settings';
$scope.download = function() {
backupService.download($rootScope.wallet);

View File

@ -2,6 +2,8 @@
angular.module('copayApp.controllers').controller('FooterController', function($rootScope, $sce, $scope, $http) {
$scope.networkName = config.networkName;
if (config.themes && Array.isArray(config.themes) && config.themes[0]) {
$scope.themes = config.themes;
} else {

View File

@ -15,8 +15,8 @@ angular.module('copayApp.controllers').controller('HeaderController',
'icon': 'fi-arrow-right',
'link': '#/send'
}, {
'title': 'Backup',
'icon': 'fi-archive',
'title': 'Settings',
'icon': 'fi-wrench',
'link': '#/backup'
}];
@ -71,7 +71,9 @@ angular.module('copayApp.controllers').controller('HeaderController',
$rootScope.$watch('txAlertCount', function(txAlertCount) {
if (txAlertCount && txAlertCount > 0) {
notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount);
notification.info('New Transaction', ($rootScope.txAlertCount == 1) ?
'You have a pending transaction proposal' :
'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount);
}
});

View File

@ -75,10 +75,9 @@ angular.module('copayApp.controllers').controller('ImportController',
}
var backupFile = $scope.file;
var backupText = form.backupText.$modelValue;
var password = form.password.$modelValue;
if (!backupFile && !backupText) {
if (!backupFile) {
$scope.loading = false;
notification.error('Error', 'Please, select your backup file or paste the file contents');
$scope.loading = false;
@ -87,8 +86,6 @@ angular.module('copayApp.controllers').controller('ImportController',
if (backupFile) {
reader.readAsBinaryString(backupFile);
} else {
_importBackup(backupText);
}
}
};
});

View File

@ -23,6 +23,13 @@ angular.module('copayApp.controllers').controller('SendController',
return flag;
};
if ($rootScope.pendingPayment) {
var pp = $rootScope.pendingPayment;
$scope.address = pp.address;
var amount = pp.amount / config.unitToSatoshi * 100000000;
$scope.amount = amount;
}
// Detect protocol
$scope.isHttp = ($window.location.protocol.indexOf('http') === 0);
@ -61,6 +68,7 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.loading = false;
});
}
$rootScope.pendingPayment = null;
});
// reset fields
@ -250,7 +258,7 @@ angular.module('copayApp.controllers').controller('SendController',
};
$scope.topAmount = function(form) {
$scope.amount = $scope.getAvailableAmount();
$scope.amount = $scope.getAvailableAmount();
form.amount.$pristine = false;
};
});

View File

@ -11,7 +11,7 @@ angular.module('copayApp.controllers').controller('SettingsController',
$scope.networkHost = config.network.host;
$scope.networkPort = config.network.port;
$scope.networkSecure = config.network.secure || false;
$scope.disableVideo = typeof config.disableVideo === undefined ? true : config.disableVideo;
$scope.disableVideo = typeof config.disableVideo === undefined ? true : config.disableVideo;
$scope.unitOpts = [{
name: 'Satoshis (100,000,000 satoshis = 1BTC)',
@ -73,6 +73,8 @@ angular.module('copayApp.controllers').controller('SettingsController',
unitToSatoshi: $scope.selectedUnit.value,
}));
$window.location.href = $window.location.origin + $window.location.pathname;
var target = ($window.location.origin !== 'null' ? $window.location.origin : '') + $window.location.pathname;
$window.location.href = target;
};
});

View File

@ -13,6 +13,10 @@ angular.module('copayApp.controllers').controller('SigninController',
$scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null;
$scope.openPassword = '';
if ($rootScope.pendingPayment) {
notification.info('Login Required', 'Please open wallet to complete payment');
}
$scope.open = function(form) {
if (form && form.$invalid) {
notification.error('Error', 'Please enter the required fields');

View File

@ -0,0 +1,17 @@
'use strict';
angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams, $timeout, $location) {
var data = decodeURIComponent($routeParams.data);
$rootScope.pendingPayment = copay.Structure.parseBitcoinURI($routeParams.data);
$scope.protocol = $rootScope.pendingPayment.protocol;
$scope.address = $rootScope.pendingPayment.address;
$scope.amount = $rootScope.pendingPayment.amount;
$scope.message = $rootScope.pendingPayment.message;
$timeout(function() {
$location.path('signin');
}, 1000);
});

View File

@ -13,7 +13,7 @@ angular.module('copayApp.directives')
link: function(scope, elem, attrs, ctrl) {
var validator = function(value) {
var a = new Address(value);
ctrl.$setValidity('validAddress', a.isValid());
ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName);
return value;
};
@ -29,8 +29,7 @@ angular.module('copayApp.directives')
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
setTimeout(function() {
scope.$apply(function() {
});
scope.$apply(function() {});
}, 5000);
}
};

View File

@ -17,6 +17,14 @@ angular.module('copayApp.filters', [])
return false;
};
})
.filter('removeEmpty', function() {
return function(elements) {
// Hide empty addresses from other copayers
return elements.filter(function(e) {
return e.owned || e.balance > 0;
});
}
})
.filter('limitAddress', function() {
return function(elements, showAll) {
if (elements.length <= 1 || showAll) {

View File

@ -1,29 +1,56 @@
'use strict';
var imports = require('soop').imports();
var preconditions = require('preconditions').singleton();
var Structure = require('./Structure');
function AddressIndex(opts) {
opts = opts || {};
this.walletId = opts.walletId;
this.cosigner = opts.cosigner
this.changeIndex = opts.changeIndex || 0;
this.receiveIndex = opts.receiveIndex || 0;
if (typeof this.cosigner === 'undefined') {
this.cosigner = Structure.SHARED_INDEX;
}
}
AddressIndex.init = function(totalCopayers) {
preconditions.shouldBeNumber(totalCopayers);
var indexes = [new AddressIndex()];
for (var i = 0 ; i < totalCopayers ; i++) {
indexes.push(new AddressIndex({cosigner: i}));
}
return indexes;
}
AddressIndex.fromList = function(indexes) {
return indexes.map(function(i) { return AddressIndex.fromObj(i); });
}
AddressIndex.fromObj = function(data) {
if (data instanceof AddressIndex) {
throw new Error('bad data format: Did you use .toObj()?');
}
var ret = new AddressIndex(data);
return ret;
return new AddressIndex(data);
};
AddressIndex.serialize = function(indexes) {
return indexes.map(function(i) { return i.toObj(); });
}
AddressIndex.update = function(shared, totalCopayers) {
var indexes = this.init(totalCopayers);
indexes[0].changeIndex = shared.changeIndex;
indexes[0].receiveIndex = shared.receiveIndex;
return this.serialize(indexes);
};
AddressIndex.prototype.toObj = function() {
return {
walletId: this.walletId,
cosigner: this.cosigner,
changeIndex: this.changeIndex,
receiveIndex: this.receiveIndex,
receiveIndex: this.receiveIndex
};
};
@ -34,10 +61,10 @@ AddressIndex.prototype.checkRange = function(index, isChange) {
}
};
AddressIndex.prototype.getChangeIndex = function() {
return this.changeIndex;
};
AddressIndex.prototype.getReceiveIndex = function() {
return this.receiveIndex;
};
@ -51,6 +78,9 @@ AddressIndex.prototype.increment = function(isChange) {
};
AddressIndex.prototype.merge = function(inAddressIndex) {
preconditions.shouldBeObject(inAddressIndex)
.checkArgument(this.cosigner == inAddressIndex.cosigner);
var hasChanged = false;
// Indexes

View File

@ -16,6 +16,7 @@ function PrivateKey(opts) {
var init = opts.extendedPrivateKeyString || this.network.name;
this.bip = opts.HK || new HK(init);
this.privateKeyCache = opts.privateKeyCache || {};
this.publicHex = this.deriveBIP45Branch().eckey.public.toString('hex');
};
PrivateKey.prototype.getId = function() {
@ -101,21 +102,21 @@ PrivateKey.prototype.getForPath = function(path) {
return wk;
};
PrivateKey.prototype.get = function(index, isChange) {
var path = Structure.FullBranch(index, isChange);
PrivateKey.prototype.get = function(index, isChange, cosigner) {
var path = Structure.FullBranch(index, isChange, cosigner);
return this.getForPath(path);
};
PrivateKey.prototype.getAll = function(receiveIndex, changeIndex) {
PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) {
if (typeof receiveIndex === 'undefined' || typeof changeIndex === 'undefined')
throw new Error('Invalid parameters');
var ret = [];
for (var i = 0; i < receiveIndex; i++) {
ret.push(this.get(i, false));
ret.push(this.get(i, false, cosigner));
}
for (var i = 0; i < changeIndex; i++) {
ret.push(this.get(i, true));
ret.push(this.get(i, true, cosigner));
}
return ret;
};

View File

@ -24,7 +24,8 @@ function PublicKeyRing(opts) {
this.copayersHK = opts.copayersHK || [];
this.indexes = AddressIndex.fromObj(opts.indexes) || new AddressIndex(opts);
this.indexes = opts.indexes ? AddressIndex.fromList(opts.indexes)
: AddressIndex.init(this.totalCopayers);
this.publicKeysCache = opts.publicKeysCache || {};
this.nicknameFor = opts.nicknameFor || {};
@ -36,6 +37,12 @@ PublicKeyRing.fromObj = function(data) {
if (data instanceof PublicKeyRing) {
throw new Error('bad data format: Did you use .toObj()?');
}
// Support old indexes schema
if (!Array.isArray(data.indexes)) {
data.indexes = AddressIndex.update(data.indexes, data.totalCopayers);
}
var ret = new PublicKeyRing(data);
for (var k in data.copayersExtPubKeys) {
@ -51,7 +58,7 @@ PublicKeyRing.prototype.toObj = function() {
networkName: this.network.name,
requiredCopayers: this.requiredCopayers,
totalCopayers: this.totalCopayers,
indexes: this.indexes.toObj(),
indexes: AddressIndex.serialize(this.indexes),
copayersExtPubKeys: this.copayersHK.map(function(b) {
return b.extendedPublicKeyString();
@ -136,10 +143,10 @@ PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
return newEpk;
};
PublicKeyRing.prototype.getPubKeys = function(index, isChange) {
PublicKeyRing.prototype.getPubKeys = function(index, isChange, cosigner) {
this._checkKeys();
var path = Structure.Branch(index, isChange);
var path = Structure.Branch(index, isChange, cosigner);
var pubKeys = this.publicKeysCache[path];
if (!pubKeys) {
pubKeys = [];
@ -162,20 +169,29 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange) {
};
// TODO this could be cached
PublicKeyRing.prototype.getRedeemScript = function(index, isChange) {
var pubKeys = this.getPubKeys(index, isChange);
PublicKeyRing.prototype.getRedeemScript = function(index, isChange, cosigner) {
var pubKeys = this.getPubKeys(index, isChange, cosigner);
var script = Script.createMultisig(this.requiredCopayers, pubKeys);
return script;
};
// TODO this could be cached
PublicKeyRing.prototype.getAddress = function(index, isChange) {
var script = this.getRedeemScript(index, isChange);
PublicKeyRing.prototype.getAddress = function(index, isChange, id) {
var cosigner = this.getCosigner(id);
var script = this.getRedeemScript(index, isChange, cosigner);
var address = Address.fromScript(script, this.network.name);
this.addressToPath[address.toString()] = Structure.FullBranch(index, isChange);
this.addressToPath[address.toString()] = Structure.FullBranch(index, isChange, cosigner);
return address;
};
// Overloaded to receive a PubkeyString or a consigner index
PublicKeyRing.prototype.getIndex = function(id) {
var cosigner = this.getCosigner(id);
var index = this.indexes.filter(function(i) { return i.cosigner == cosigner });
if (index.length != 1) throw new Error('no index for cosigner');
return index[0];
};
PublicKeyRing.prototype.pathForAddress = function(address) {
var path = this.addressToPath[address];
if (!path) throw new Error('Couldn\'t find path for address ' + address);
@ -183,17 +199,19 @@ PublicKeyRing.prototype.pathForAddress = function(address) {
};
// TODO this could be cached
PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange) {
var addr = this.getAddress(index, isChange);
PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) {
var cosigner = this.getCosigner(pubkey);
var addr = this.getAddress(index, isChange, cosigner);
return Script.createP2SH(addr.payload()).getBuffer().toString('hex');
};
//generate a new address, update index.
PublicKeyRing.prototype.generateAddress = function(isChange) {
PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) {
isChange = !!isChange;
var index = isChange ? this.indexes.getChangeIndex() : this.indexes.getReceiveIndex();
var ret = this.getAddress(index, isChange);
this.indexes.increment(isChange);
var addrIndex = this.getIndex(pubkey);
var index = isChange ? addrIndex.getChangeIndex() : addrIndex.getReceiveIndex();
var ret = this.getAddress(index, isChange, addrIndex.cosigner);
addrIndex.increment(isChange);
return ret;
};
@ -203,28 +221,58 @@ PublicKeyRing.prototype.getAddresses = function(opts) {
});
};
PublicKeyRing.prototype.getAddressesInfo = function(opts) {
PublicKeyRing.prototype.getCosigner = function(pubKey) {
if (typeof pubKey == 'undefined') return Structure.SHARED_INDEX;
if (typeof pubKey == 'number') return pubKey;
var sorted = this.copayersHK.map(function(h, i){
return h.eckey.public.toString('hex');
}).sort(function(h1, h2){ return h1.localeCompare(h2); });
var index = sorted.indexOf(pubKey);
if (index == -1) throw new Error('no public key in ring');
return index;
}
PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) {
var ret = [];
var self = this;
var cosigner = pubkey && this.getCosigner(pubkey);
this.indexes.forEach(function(index) {
ret = ret.concat(self.getAddressesInfoForIndex(index, opts, cosigner));
});
return ret;
}
PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, cosigner) {
opts = opts || {};
var isOwned = index.cosigner == Structure.SHARED_INDEX
|| index.cosigner == cosigner;
var ret = [];
if (!opts.excludeChange) {
for (var i = 0; i < this.indexes.getChangeIndex(); i++) {
var a = this.getAddress(i, true);
for (var i = 0; i < index.changeIndex; i++) {
var a = this.getAddress(i, true, index.cosigner);
ret.unshift({
address: this.getAddress(i, true),
address: a,
addressStr: a.toString(),
isChange: true
isChange: true,
owned: isOwned
});
}
}
if (!opts.excludeMain) {
for (var i = 0; i < this.indexes.getReceiveIndex(); i++) {
var a = this.getAddress(i, false);
for (var i = 0; i < index.receiveIndex; i++) {
var a = this.getAddress(i, false, index.cosigner);
ret.unshift({
address: a,
addressStr: a.toString(),
isChange: false
isChange: false,
owned: isOwned
});
}
}
@ -235,7 +283,7 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts) {
// TODO this could be cached
PublicKeyRing.prototype._addScriptMap = function(map, path) {
var p = Structure.indicesForPath(path);
var script = this.getRedeemScript(p.index, p.isChange);
var script = this.getRedeemScript(p.index, p.isChange, p.cosigner);
map[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex');
};
@ -303,17 +351,25 @@ PublicKeyRing.prototype._mergePubkeys = function(inPKR) {
};
PublicKeyRing.prototype.merge = function(inPKR, ignoreId) {
var hasChanged = false;
this._checkInPKR(inPKR, ignoreId);
if (this.indexes.merge(inPKR.indexes))
hasChanged = true;
var hasChanged = false;
hasChanged |= this.mergeIndexes(inPKR.indexes);
hasChanged |= this._mergePubkeys(inPKR);
if (this._mergePubkeys(inPKR))
hasChanged = true;
return hasChanged;
return !!hasChanged;
};
PublicKeyRing.prototype.mergeIndexes = function(indexes) {
var self = this;
var hasChanged = false;
indexes.forEach(function(theirs) {
var mine = self.getIndex(theirs.cosigner);
hasChanged |= mine.merge(theirs);
});
return !!hasChanged
}
module.exports = require('soop')(PublicKeyRing);

View File

@ -40,6 +40,7 @@ Structure.indicesForPath = function(path) {
return {
isChange: s[3] === '1',
index: parseInt(s[4]),
cosigner: parseInt(s[2])
};
};
@ -50,4 +51,23 @@ Structure.MAX_NON_HARDENED = MAX_NON_HARDENED;
Structure.SHARED_INDEX = SHARED_INDEX;
Structure.ID_INDEX = ID_INDEX;
Structure.parseBitcoinURI = function(uri) {
var ret = {};
var data = decodeURIComponent(uri);
var splitDots = data.split(':');
ret.protocol = splitDots[0];
data = splitDots[1];
var splitQuestion = data.split('?');
ret.address = splitQuestion[0];
var search = splitQuestion[1];
data = JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}',
function(key, value) {
return key === "" ? value : decodeURIComponent(value);
});
ret.amount = parseFloat(data.amount);
ret.message = data.message;
return ret;
};
module.exports = require('soop')(Structure);

View File

@ -13,6 +13,7 @@ var buffertools = bitcore.buffertools;
var Builder = bitcore.TransactionBuilder;
var SecureRandom = bitcore.SecureRandom;
var Base58Check = bitcore.Base58.base58Check;
var Address = bitcore.Address;
var AddressIndex = require('./AddressIndex');
var PublicKeyRing = require('./PublicKeyRing');
@ -38,12 +39,6 @@ function Wallet(opts) {
this.id = opts.id || Wallet.getRandomId();
this.name = opts.name;
// Renew token every 24hs
if (opts.tokenTime && new Date().getTime() - opts.tokenTime < 86400000) {
this.token = opts.token;
this.tokenTime = opts.tokenTime;
}
this.verbose = opts.verbose;
this.publicKeyRing.walletId = this.id;
this.txProposals.walletId = this.id;
@ -51,6 +46,7 @@ function Wallet(opts) {
this.registeredPeerIds = [];
this.addressBook = opts.addressBook || {};
this.backupOffered = opts.backupOffered || false;
this.publicKey = this.privateKey.publicHex;
}
Wallet.parent = EventEmitter;
@ -81,8 +77,8 @@ Wallet.prototype.connectToAll = function() {
Wallet.prototype._handleIndexes = function(senderId, data, isInbound) {
this.log('RECV INDEXES:', data);
var inIndexes = AddressIndex.fromObj(data.indexes);
var hasChanged = this.publicKeyRing.indexes.merge(inIndexes);
var inIndexes = AddressIndex.fromList(data.indexes);
var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes);
if (hasChanged) {
this.emit('publicKeyRingUpdated');
this.store();
@ -218,11 +214,6 @@ Wallet.prototype._optsToObj = function() {
version: this.version,
};
if (this.token) {
obj.token = this.token;
obj.tokenTime = new Date().getTime();
}
return obj;
};
@ -255,6 +246,7 @@ Wallet.decodeSecret = function(secretB) {
}
};
Wallet.prototype._lockIncomming = function() {
this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds());
};
@ -279,7 +271,6 @@ Wallet.prototype.netStart = function(callback) {
var startOpts = {
copayerId: myId,
privkey: myIdPriv,
token: self.token,
maxPeers: self.totalCopayers
};
@ -289,12 +280,10 @@ Wallet.prototype.netStart = function(callback) {
net.start(startOpts, function() {
self.emit('ready', net.getPeer());
self.token = net.peer.options.token;
setTimeout(function() {
self.emit('publicKeyRingUpdated', true);
self.scheduleConnect();
self.emit('txProposalsUpdated');
self.store();
}, 10);
});
};
@ -332,7 +321,8 @@ Wallet.prototype.getRegisteredPeerIds = function() {
var pid = this.network.peerFromCopayer(cid);
this.registeredPeerIds.push({
peerId: pid,
nick: this.publicKeyRing.nicknameForCopayer(cid)
nick: this.publicKeyRing.nicknameForCopayer(cid),
index: i,
});
}
}
@ -377,8 +367,6 @@ Wallet.fromObj = function(o, storage, network, blockchain) {
Wallet.prototype.toEncryptedObj = function() {
var walletObj = this.toObj();
delete walletObj.opts.token;
delete walletObj.opts.tokenTime;
return this.storage.export(walletObj);
};
@ -434,11 +422,12 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) {
});
};
Wallet.prototype.sendIndexes = function(recipients) {
this.log('### INDEXES TO:', recipients || 'All', this.publicKeyRing.indexes.toObj());
var indexes = AddressIndex.serialize(this.publicKeyRing.indexes);
this.log('### INDEXES TO:', recipients || 'All', indexes);
this.network.send(recipients, {
type: 'indexes',
indexes: this.publicKeyRing.indexes.toObj(),
indexes: indexes,
walletId: this.id,
});
};
@ -457,7 +446,7 @@ Wallet.prototype.getName = function() {
};
Wallet.prototype._doGenerateAddress = function(isChange) {
return this.publicKeyRing.generateAddress(isChange);
return this.publicKeyRing.generateAddress(isChange, this.publicKey);
};
@ -511,7 +500,6 @@ Wallet.prototype.sign = function(ntxid, cb) {
if (cb) cb(false);
}
var pkr = self.publicKeyRing;
var keys = self.privateKey.getForPaths(txp.inputChainPaths);
var b = txp.builder;
@ -583,7 +571,7 @@ Wallet.prototype.getAddressesStr = function(opts) {
};
Wallet.prototype.getAddressesInfo = function(opts) {
return this.publicKeyRing.getAddressesInfo(opts);
return this.publicKeyRing.getAddressesInfo(opts, this.publicKey);
};
Wallet.prototype.addressIsOwn = function(addrStr, opts) {
@ -691,7 +679,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
opts = opts || {};
var amountSat = bignum(amountSatStr);
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName());
if (!pkr.isComplete()) {
throw new Error('publicKeyRing is not complete');
}
@ -749,31 +737,44 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
Wallet.prototype.updateIndexes = function(callback) {
var self = this;
var start = self.publicKeyRing.indexes.changeIndex;
self.log('Updating indexes...');
self.indexDiscovery(start, true, 20, function(err, changeIndex) {
var tasks = this.publicKeyRing.indexes.map(function(index) {
return function(callback) {
self.updateIndex(index, callback);
};
});
async.parallel(tasks, function(err) {
if (err) callback(err);
self.log('Indexes updated');
self.emit('publicKeyRingUpdated');
self.store();
callback();
});
}
Wallet.prototype.updateIndex = function(index, callback) {
var self = this;
var SCANN_WINDOW = 20;
self.indexDiscovery(index.changeIndex, true, index.cosigner, SCANN_WINDOW, function(err, changeIndex) {
if (err) return callback(err);
if (changeIndex != -1)
self.publicKeyRing.indexes.changeIndex = changeIndex + 1;
index.changeIndex = changeIndex + 1;
start = self.publicKeyRing.indexes.receiveIndex;
self.indexDiscovery(start, false, 20, function(err, receiveIndex) {
self.indexDiscovery(index.receiveIndex, false, index.cosigner, SCANN_WINDOW, function(err, receiveIndex) {
if (err) return callback(err);
if (receiveIndex != -1)
self.publicKeyRing.indexes.receiveIndex = receiveIndex + 1;
self.log('Indexes updated');
self.emit('publicKeyRingUpdated');
self.store();
index.receiveIndex = receiveIndex + 1;
callback();
});
});
}
Wallet.prototype.deriveAddresses = function(index, amout, isChange) {
Wallet.prototype.deriveAddresses = function(index, amout, isChange, cosigner) {
var ret = new Array(amout);
for (var i = 0; i < amout; i++) {
ret[i] = this.publicKeyRing.getAddress(index + i, isChange).toString();
ret[i] = this.publicKeyRing.getAddress(index + i, isChange, cosigner).toString();
}
return ret;
}
@ -781,7 +782,7 @@ Wallet.prototype.deriveAddresses = function(index, amout, isChange) {
// This function scans the publicKeyRing branch starting at index @start and reports the index with last activity,
// using a scan window of @gap. The argument @change defines the branch to scan: internal or external.
// Returns -1 if no activity is found in range.
Wallet.prototype.indexDiscovery = function(start, change, gap, cb) {
Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) {
var scanIndex = start;
var lastActive = -1;
var hasActivity = false;
@ -791,7 +792,7 @@ Wallet.prototype.indexDiscovery = function(start, change, gap, cb) {
function _do(next) {
// Optimize window to minimize the derivations.
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
var addresses = self.deriveAddresses(scanIndex, scanWindow, change);
var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner);
self.blockchain.checkActivity(addresses, function(err, actives) {
if (err) throw err;

View File

@ -48,7 +48,6 @@ Network.prototype.cleanUp = function() {
this.privkey = null; //TODO: hide privkey in a closure
this.key = null;
this.copayerId = null;
this.signingKey = null;
this.allowedCopayerIds = null;
this.isInboundPeerAuth = [];
this.copayerForPeer = {};
@ -346,6 +345,7 @@ Network.prototype.start = function(opts, openCallback) {
if (!self.criticalError && self.tries < self.reconnectAttempts) {
self.tries++;
self.opts.token = util.sha256(self.peerId).toString('hex');
self.peer = new Peer(self.peerId, self.opts);
self.started = true;
self._setupPeerHandlers(openCallback);

View File

@ -26,10 +26,6 @@ angular
templateUrl: 'addresses.html',
validate: true
})
.when('/join/:id', {
templateUrl: 'join.html',
validate: true
})
.when('/transactions', {
templateUrl: 'transactions.html',
validate: true
@ -49,6 +45,9 @@ angular
.when('/unsupported', {
templateUrl: 'unsupported.html'
})
.when('/uri_payment/:data', {
templateUrl: 'uri_payment.html'
})
.otherwise({
templateUrl: '404.html'
});
@ -64,7 +63,6 @@ angular
})
.run(function($rootScope, $location) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (!util.supports.data) {
$location.path('unsupported');
} else {

View File

@ -2,7 +2,7 @@
var bitcore = require('bitcore');
angular.module('copayApp.services')
.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video) {
.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) {
var root = {};
root.getVideoMutedStatus = function(copayer) {
@ -43,20 +43,15 @@ angular.module('copayApp.services')
};
root.installStartupHandlers = function(wallet, $scope) {
wallet.on('serverError', function(msg) {
notification.error('PeerJS Error', 'There was an error connecting to the PeerJS server.' + (msg || 'Check you settings and Internet connection.'));
root.onErrorDigest($scope);
$location.path('addresses');
});
wallet.on('connectionError', function() {
var message = "Looks like you are already connected to this wallet, please logout and try importing it again.";
notification.error('PeerJS Error', message);
root.onErrorDigest($scope);
});
wallet.on('serverError', function() {
var message = 'The PeerJS server is not responding, please try again';
notification.error('PeerJS Error', message);
root.onErrorDigest($scope);
wallet.on('serverError', function(m) {
var message = m || 'The PeerJS server is not responding, please try again';
$location.path('addresses');
root.onErrorDigest($scope, message);
});
wallet.on('ready', function() {
$scope.loading = false;
@ -64,6 +59,7 @@ angular.module('copayApp.services')
};
root.setupRootVariables = function() {
uriHandler.register();
$rootScope.unitName = config.unitName;
$rootScope.txAlertCount = 0;
$rootScope.insightError = 0;
@ -102,7 +98,11 @@ angular.module('copayApp.services')
});
w.on('ready', function(myPeerID) {
$rootScope.wallet = w;
$location.path('addresses');
if ($rootScope.pendingPayment) {
$location.path('send');
} else {
$location.path('addresses');
}
if (!config.disableVideo)
video.setOwnPeer(myPeerID, w, handlePeerVideo);
});

11
js/services/uriHandler.js Normal file
View File

@ -0,0 +1,11 @@
'use strict';
var UriHandler = function() {};
UriHandler.prototype.register = function() {
var base = window.location.origin + '/';
var url = base + '#/uri_payment/%s';
// navigator.registerProtocolHandler('bitcoin', url, 'Copay');
};
angular.module('copayApp.services').value('uriHandler', new UriHandler());

View File

@ -12,7 +12,7 @@
** the renderer into thinking that we are _not_ in a CommonJS environment.
*/
if (typeof module !== 'undefined') module = {
exports: null
exports: false
};
// are we running in copay shell?
@ -39,22 +39,12 @@
var ipc = require('ipc');
var clipb = require('clipboard');
// atom shell forces to implement the clipboard on our own - thanks obama.
// atom shell forces to implement the clipboard (on osx) on our own - thanks obama.
Mousetrap.stopCallback = function() {
return false
};
Mousetrap.bind('ctrl+c', function(e) {
clipb.writeText(window.getSelection().toString());
});
Mousetrap.bind('ctrl+v', function(e) {
if (document.activeElement) {
document.activeElement.value = clipb.readText();
}
});
Mousetrap.bind('command+c', function(e) {
clipb.writeText(window.getSelection().toString());
});
@ -62,6 +52,7 @@
Mousetrap.bind('command+v', function(e) {
if (document.activeElement) {
document.activeElement.value = clipb.readText();
document.activeElement.dispatchEvent(new Event('change'));
}
});

2657
lib/peer.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "copay",
"version": "0.2.1",
"version": "0.3.1",
"description": "A multisignature wallet",
"repository": {
"type": "git",
@ -25,12 +25,13 @@
"shell": "node shell/scripts/launch.js",
"setup-shell": "node shell/scripts/download-atom-shell.js",
"chrome": "source browser-extensions/chrome/build.sh",
"firefox": "source browser-extensions/firefox/build.sh"
"firefox": "source browser-extensions/firefox/build.sh",
"dist": "node shell/scripts/dist.js"
},
"homepage": "https://github.com/bitpay/copay",
"devDependencies": {
"async": "0.9.0",
"bitcore": "0.1.24",
"bitcore": "0.1.25",
"blanket": "1.1.6",
"browser-pack": "2.0.1",
"browserify": "3.32.1",

View File

@ -8,4 +8,5 @@ using [Atom Shell](https://github.com/atom/atom-shell).
## Building
Automated build scripts are currently being developed.
Run from the top level (copay) directory:
npm run dist

View File

@ -12,7 +12,7 @@ module.exports = function(copay) {
// quit when all windows are closed
app.on('window-all-closed', function() {
if (process.platform !== 'darwin') app.quit();
app.quit();
});
// initilization when ready
@ -52,7 +52,7 @@ module.exports = function(copay) {
mainWindow = null;
});
// mainWindow.toggleDevTools();
//mainWindow.toggleDevTools();
});

181
shell/scripts/dist.js Normal file
View File

@ -0,0 +1,181 @@
require('shelljs/global');
var color = require('cli-color');
var download = require('./lib/download')();
var async = require('async');
var atom_version = 'v0.13.0';
var app_root = './';
var build_dir = 'shell/scripts/build';
var dist_dir = 'dist';
var darwin_app_dir = '/Copay.app/Contents/Resources/app';
var linux_app_dir = '/resources/app';
var windows_app_dir = '/resources/app';
console.log(color.blue('{copay}'), '');
console.log(color.blue('{copay}'), 'Preparing to build Copay binaries');
console.log(color.blue('{copay}'), '');
/* Clean up before the build */
rm('-rf', build_dir);
rm(dist_dir + '/Copay*');
rm('-rf', dist_dir + '/darwin', dist_dir + '/linux', dist_dir + '/windows');
// Download the atom shell binaries. If you exceed your download quota,
// just download the zips manually and unpack them into shell/scripts/bin/<platform>
async.series([
function(callback) {
download.atom(atom_version, 'darwin', 'x64', callback);
},
function(callback){
download.atom(atom_version, 'linux', 'x64', callback);
},
function(callback) {
download.atom(atom_version, 'win32', 'ia32', callback);
},
function() {
runBuild();
}]);
function runBuild() {
mkdir(build_dir);
/* DARWIN BUILD */
console.log(color.blue('{copay}'), '');
console.log(color.blue('{copay}'), 'Starting DARWIN build');
console.log(color.blue('{copay}'), '');
// Copy the core atom shell
cp('-r', app_root + '/shell/scripts/bin/darwin/*', build_dir);
mv(build_dir + '/Atom.app', build_dir + '/Copay.app');
mv(build_dir + '/Copay.app/Contents/MacOS/Atom', build_dir + '/Copay.app/Contents/MacOS/Copay');
// Replace Atom darwin assets with Copay assets
cp(app_root + '/shell/assets/darwin/copay.icns', build_dir + '/Copay.app/Contents/Resources/copay.icns');
cp('-f', app_root + '/shell/assets/darwin/Info.plist', build_dir + '/Copay.app/Contents/Info.plist');
rm(build_dir + '/Copay.app/Contents/Resources/atom.icns');
// Copy Copay sources
cp('-r', app_root + '/css', build_dir + darwin_app_dir);
cp('-r', app_root + '/js', build_dir + darwin_app_dir);
cp('-r', app_root + '/font', build_dir + darwin_app_dir);
cp('-r', app_root + '/img', build_dir + darwin_app_dir);
cp('-r', app_root + '/lib', build_dir + darwin_app_dir);
cp('-r', app_root + '/sound', build_dir + darwin_app_dir);
cp('-r', app_root + '/shell/*.js*', build_dir + darwin_app_dir + '/shell');
cp('-r', app_root + '/shell/lib/*', build_dir + darwin_app_dir + '/shell/lib');
cp(app_root + '*.js', build_dir + darwin_app_dir);
cp(app_root + '*.json', build_dir + darwin_app_dir);
cp(app_root + '*.html', build_dir + darwin_app_dir);
// Copay needs express, put other node deps here if you need any
cp('-r', app_root + '/node_modules/express', build_dir + darwin_app_dir + "/node_modules");
// Clean up extra Atom sources
rm('-r', build_dir + darwin_app_dir + '/../*.lproj');
rm('-r', build_dir + darwin_app_dir + '/../default_app');
mkdir('-p', app_root + '/dist/darwin');
cp('-r', app_root + build_dir + '/*', app_root + '/dist/darwin/');
rm('-rf', app_root + build_dir + '/*');
console.log(color.blue('{copay}'), 'Copied files to ' + 'dist/darwin');
if (which('hdiutil') != null) {
cd(app_root + '/dist/darwin');
exec('ln -s /Applications Applications');
exec('hdiutil create ../Copay-darwin-x64.dmg -volname "Copay Installer - Drag Copay to Applications Folder" -fs HFS+ -srcfolder "."');
exec("rm Applications");
cd('../..');
}
/* Linux Build */
console.log(color.blue('{copay}'), '');
console.log(color.blue('{copay}'), 'Starting LINUX build');
console.log(color.blue('{copay}'), '');
// Copy the core atom shell
cp('-r', app_root + '/shell/scripts/bin/linux/*', build_dir);
mv(build_dir + '/atom', build_dir + '/Copay');
// Copy Copay sources
cp('-r', app_root + '/css', build_dir + linux_app_dir);
cp('-r', app_root + '/js', build_dir + linux_app_dir);
cp('-r', app_root + '/font', build_dir + linux_app_dir);
cp('-r', app_root + '/img', build_dir + linux_app_dir);
cp('-r', app_root + '/lib', build_dir + linux_app_dir);
cp('-r', app_root + '/sound', build_dir + linux_app_dir);
cp('-r', app_root + '/shell/*.js*', build_dir + linux_app_dir + '/shell');
cp('-r', app_root + '/shell/lib/*', build_dir + linux_app_dir + '/shell/lib');
cp(app_root + '*.js', build_dir + linux_app_dir);
cp(app_root + '*.json', build_dir + linux_app_dir);
cp(app_root + '*.html', build_dir + linux_app_dir);
cp('-r', app_root + '/node_modules/express', build_dir + linux_app_dir + "/node_modules");
// Clean up extra Atom sources
rm('-r', build_dir + linux_app_dir + '/../default_app');
cp('-r', app_root + build_dir + '/*', app_root + '/dist/linux');
rm('-rf', app_root + build_dir + '/*');
exec('tar czf ./dist/Copay-linux-x64.tar.gz -C ./dist/linux .');
console.log(color.blue('{copay}'), 'Copied files to ' + 'dist/linux');
/* Windows Build */
console.log(color.blue('{copay}'), '');
console.log(color.blue('{copay}'), 'Starting WIN32 build');
console.log(color.blue('{copay}'), '');
// Copy the core atom shell
cp('-r', app_root + '/shell/scripts/bin/win32/*', build_dir);
mv(build_dir + '/atom.exe', build_dir + '/Copay.exe');
// Copy Copay sources
cp('-r', app_root + '/css', build_dir + windows_app_dir);
cp('-r', app_root + '/js', build_dir + windows_app_dir);
cp('-r', app_root + '/font', build_dir + windows_app_dir);
cp('-r', app_root + '/img', build_dir + windows_app_dir);
cp('-r', app_root + '/lib', build_dir + windows_app_dir);
cp('-r', app_root + '/sound', build_dir + windows_app_dir);
cp('-r', app_root + '/shell/*.js*', build_dir + windows_app_dir + '/shell');
cp('-r', app_root + '/shell/lib/*', build_dir + windows_app_dir + '/shell/lib');
cp(app_root + '*.js', build_dir + windows_app_dir);
cp(app_root + '*.json', build_dir + windows_app_dir);
cp(app_root + '*.html', build_dir + windows_app_dir);
cp('-r', app_root + '/node_modules/express', build_dir + windows_app_dir + "/node_modules");
cp(app_root + "/shell/assets/win32/*", build_dir);
rm('-r', build_dir + windows_app_dir + '/../default_app');
mkdir('-p', app_root + '/dist/windows');
cp('-r', app_root + build_dir + '/*', app_root + '/dist/windows/');
rm('-rf', app_root + build_dir + '/*');
console.log(color.blue('{copay}'), 'Copied files to ' + 'dist/windows');
// generating windows installer requires makensis
// install on OSX with "brew install makensis"
if (which('makensis') != null) {
console.log(color.blue('{copay}'), 'Running NSIS to generate win32 installer');
cd('dist/windows');
exec('makensis -V2 build-installer.nsi');
cd("../../");
cp('dist/windows/copay-setup.exe', app_root + '/dist/Copay-setup-win32.exe')
}
console.log(color.blue('{copay}'));
console.log(color.blue('{copay}'), 'BUILD COMPLETE');
console.log(color.blue('{copay}'), 'Files can be found in the dist directory');
}

View File

@ -1,12 +1,12 @@
/*
** copay-shell - launch
*/
** copay-shell - launch
*/
var color = require('cli-color');
var path = require('path');
var appPath = path.normalize(__dirname + '/../../');
var color = require('cli-color');
var path = require('path');
var appPath = path.normalize(__dirname + '/../../');
var execPath = path.normalize(__dirname + '/../bin/' + process.platform);
var spawn = require('child_process').spawn;
var spawn = require('child_process').spawn;
// update execPath with platform specific binary locations
switch (process.platform) {
@ -26,14 +26,14 @@ switch (process.platform) {
var copay = spawn(execPath, [appPath]);
copay.stdout.on('data', function (data) {
console.log(data);
copay.stdout.on('data', function(data) {
console.log("STDOUT:" + data);
});
copay.stderr.on('data', function (data) {
console.log(color.red(data));
copay.stderr.on('data', function(data) {
console.log("STDERR:" + data);
});
copay.on('close', function (code) {
copay.on('close', function(code) {
console.log('child process exited with code ' + code);
});

View File

@ -0,0 +1,104 @@
var path = require('path');
var fs = require('fs');
var GitHub = require('github-releases');
var async = require('async');
var readl = require('readline');
var color = require('cli-color');
var github = new GitHub({ repo: 'atom/atom-shell' });
var exec = require('child_process').exec;
var os = require('os');
var Static = function() {
return {
atom: function(version, platform, arch, callback) {
var targetRoot = path.normalize(__dirname + '/../bin/'),
target = path.normalize(__dirname + '/../bin/' + platform);
console.log(color.blue('{copay}'), 'Downloading atom-shell ' + version + ' ' + platform + '-' + arch);
if(!fs.existsSync(targetRoot)) {
fs.mkdirSync(targetRoot);
}
if (!fs.existsSync(target)) {
fs.mkdirSync(target);
} else {
console.log(color.blue('{copay}'), platform + '-' + arch + ' ' + ' already downloaded');
callback.call(this, null);
return;
}
console.log(color.blue('{copay}'), 'getting atom-shell release ' + version);
github.getReleases({ tag_name: version }, function(err, releases) {
var filename = 'atom-shell-' + version + '-' + platform + '-' + arch + '.zip';
if (err || !releases.length) {
console.log(err);
return console.log(color.blue('{copay}'), 'Release not found');
}
console.log(color.blue('{copay}'), 'looking for prebuilt binary ' + filename);
for (var a = 0; a < releases[0].assets.length; a++) {
var asset = releases[0].assets[a];
if (asset.name === filename) {
console.log(color.blue('{copay}'), 'downloading ' + asset.name);
var rl = readl.createInterface({
input: process.stdin,
output: process.stdout
});
rl.write(' bytes received: 0');
return github.downloadAsset(asset, function(err, inStream) {
if (err) {
console.log(err);
process.exit();
}
var bytes = 0;
inStream.on('data', function(chunk) {
rl.write(null, { ctrl: true, name: 'u' });
rl.write(' bytes received: ' + (bytes + chunk.length));
bytes += chunk.length;
});
inStream.on('end', function() {
rl.close();
console.log('');
console.log(color.blue('{copay}'), 'downloaded!');
});
var out = target;
var tmp = os.tmpDir() + '/atom-shell.zip';
var outStream = fs.createWriteStream(tmp);
outStream.on('finish', function() {
console.log(color.blue('{copay}'), 'unzipping archive');
exec('unzip -o ' + tmp + ' -d ' + out, function(err, stdout, stderr) {
console.log(err || stderr || (color.blue('{copay}') + ' done!'))
callback.call(this, null);
});
});
inStream.pipe(outStream);
});
}
}
});
}
}
}
module.exports = Static;

View File

@ -12,6 +12,7 @@ try {
}
var PublicKeyRing = copay.PublicKeyRing;
var AddressIndex = copay.AddressIndex;
var Structure = copay.Structure;
var config = {
@ -22,7 +23,7 @@ var createAI = function() {
var i = new AddressIndex();
should.exist(i);
i.walletId = '1234567';
i.cosigner = 1;
return i;
};
@ -34,7 +35,31 @@ describe('AddressIndex model', function() {
should.exist(i);
});
it('show be able to tostore and read', function() {
it('should init indexes', function() {
var is = AddressIndex.init(2);
should.exist(is);
is.length.should.equal(3);
var cosigners = is.map(function(i) { return i.cosigner; });
cosigners.indexOf(Structure.SHARED_INDEX).should.not.equal(-1);
cosigners.indexOf(0).should.not.equal(-1);
cosigners.indexOf(1).should.not.equal(-1);
cosigners.indexOf(2).should.equal(-1);
});
it('should serialize to object list and back', function() {
var is = AddressIndex.init(3);
should.exist(is);
is.length.should.equal(4);
var list = AddressIndex.serialize(is);
list.length.should.equal(4);
var is2 = AddressIndex.fromList(list);
is2.length.should.equal(4);
});
it('show be able to store and read', function() {
var i = createAI();
var changeN = 2;
var addressN = 2;
@ -49,7 +74,7 @@ describe('AddressIndex model', function() {
should.exist(data);
var i2 = AddressIndex.fromObj(data);
i2.walletId.should.equal(i.walletId);
i2.cosigner.should.equal(i.cosigner);
i2.getChangeIndex().should.equal(changeN);
i2.getReceiveIndex().should.equal(addressN);
@ -74,7 +99,7 @@ describe('AddressIndex model', function() {
for (var i = 0; i < 7; i++)
j.increment(false);
var j2 = new AddressIndex({
walletId: j.walletId,
cosigner: j.cosigner,
});
j2.merge(j).should.equal(true);
j2.changeIndex.should.equal(15);
@ -83,4 +108,12 @@ describe('AddressIndex model', function() {
j2.merge(j).should.equal(false);
});
it('#merge should fail with different cosigner index', function() {
var j1 = new AddressIndex({ walletId: '1234', cosigner: 2 });
var j2 = new AddressIndex({ walletId: '1234', cosigner: 3 });
var merge = function() { j2.merge(j1); };
merge.should.throw(Error);
})
});

View File

@ -5,6 +5,9 @@ var should = chai.should();
var bitcore = bitcore || require('bitcore');
var Address = bitcore.Address;
var buffertools = bitcore.buffertools;
var Structure = require('../js/models/core/Structure');
try {
var copay = require('copay'); //browser
} catch (e) {
@ -33,7 +36,8 @@ var createW = function(networkName) {
return {
w: w,
copayers: copayers
copayers: copayers,
pub: w.copayersHK[0].eckey.public.toString('hex')
};
};
@ -85,7 +89,7 @@ describe('PublicKeyRing model', function() {
}
});
it('show be able to tostore and read', function() {
it('should be able to to store and read', function() {
var k = createW();
var w = k.w;
var copayers = k.copayers;
@ -93,10 +97,10 @@ describe('PublicKeyRing model', function() {
var addressN = 2;
var start = new Date().getTime();
for (var i = 0; i < changeN; i++) {
w.generateAddress(true);
w.generateAddress(true, k.pub);
}
for (var i = 0; i < addressN; i++) {
w.generateAddress(false);
w.generateAddress(false, k.pub);
}
var data = w.toObj();
@ -112,8 +116,8 @@ describe('PublicKeyRing model', function() {
}).should.throw();
}
w2.indexes.getChangeIndex().should.equal(changeN);
w2.indexes.getReceiveIndex().should.equal(addressN);
w2.getIndex(k.pub).getChangeIndex().should.equal(changeN);
w2.getIndex(k.pub).getReceiveIndex().should.equal(addressN);
});
@ -123,7 +127,7 @@ describe('PublicKeyRing model', function() {
[true, false].forEach(function(isChange){
for (var i = 0; i < 2; i++) {
var a = w.generateAddress(isChange);
var a = w.generateAddress(isChange, k.pub);
a.isValid().should.equal(true);
a.isScript().should.equal(true);
a.network().name.should.equal('livenet');
@ -145,7 +149,7 @@ describe('PublicKeyRing model', function() {
[true, false].forEach(function(isChange){
for (var i = 0; i < 2; i++) {
w.generateAddress(isChange);
w.generateAddress(isChange, k.pub);
}
});
@ -164,12 +168,12 @@ describe('PublicKeyRing model', function() {
var w = k.w;
for (var i = 0; i < 3; i++)
w.generateAddress(true);
w.generateAddress(true, k.pub);
for (var i = 0; i < 2; i++)
w.generateAddress(false);
w.generateAddress(false, k.pub);
w.indexes.getChangeIndex().should.equal(3);
w.indexes.getReceiveIndex().should.equal(2);
w.getIndex(k.pub).getChangeIndex().should.equal(3);
w.getIndex(k.pub).getReceiveIndex().should.equal(2);
});
it('#merge index tests', function() {
@ -177,9 +181,9 @@ describe('PublicKeyRing model', function() {
var w = k.w;
for (var i = 0; i < 2; i++)
w.generateAddress(true);
w.generateAddress(true, k.pub);
for (var i = 0; i < 3; i++)
w.generateAddress(false);
w.generateAddress(false, k.pub);
var w2 = new PublicKeyRing({
networkName: 'livenet',
@ -188,8 +192,8 @@ describe('PublicKeyRing model', function() {
w2.merge(w).should.equal(true);
w2.requiredCopayers.should.equal(3);
w2.totalCopayers.should.equal(5);
w2.indexes.getChangeIndex().should.equal(2);
w2.indexes.getReceiveIndex().should.equal(3);
w2.getIndex(k.pub).getChangeIndex().should.equal(2);
w2.getIndex(k.pub).getReceiveIndex().should.equal(3);
//
w2.merge(w).should.equal(false);
@ -380,15 +384,36 @@ describe('PublicKeyRing model', function() {
});
it('#getIndex should return the right one', function() {
var config = {
networkName: 'livenet',
};
var p = new PublicKeyRing(config);
var i = p.getIndex(Structure.SHARED_INDEX);
should.exist(i);
i.cosigner.should.equal(Structure.SHARED_INDEX);
});
it('#getIndex should throw error', function() {
var config = {
networkName: 'livenet',
};
var p = new PublicKeyRing(config);
(function badCosigner() {
return p.getIndex(54);
}).should.throw();
});
it('#getRedeemScriptMap check tests', function() {
var k = createW();
var w = k.w;
var amount = 2;
for (var i = 0; i < amount; i++)
w.generateAddress(true);
w.generateAddress(true, k.pub);
for (var i = 0; i < amount; i++)
w.generateAddress(false);
w.generateAddress(false, k.pub);
var m = w.getRedeemScriptMap([
'm/45\'/2147483647/1/0',

View File

@ -39,13 +39,34 @@ describe('Structure model', function() {
});
[
['m/45\'/0/0/0', {index: 0, isChange: false}],
['m/45\'/0/0/1', {index: 1, isChange: false}],
['m/45\'/0/0/2', {index: 2, isChange: false}],
['m/45\'/0/1/0', {index: 0, isChange: true}],
['m/45\'/0/1/1', {index: 1, isChange: true}],
['m/45\'/0/1/2', {index: 2, isChange: true}],
['m/45\'/0/0/900', {index: 900, isChange: false}],
['m/45\'/0/0/0', {
index: 0,
isChange: false
}],
['m/45\'/0/0/1', {
index: 1,
isChange: false
}],
['m/45\'/0/0/2', {
index: 2,
isChange: false
}],
['m/45\'/0/1/0', {
index: 0,
isChange: true
}],
['m/45\'/0/1/1', {
index: 1,
isChange: true
}],
['m/45\'/0/1/2', {
index: 2,
isChange: true
}],
['m/45\'/0/0/900', {
index: 900,
isChange: false
}],
].forEach(function(datum) {
var path = datum[0];
var result = datum[1];
@ -55,5 +76,13 @@ describe('Structure model', function() {
i.isChange.should.equal(result.isChange);
});
});
it('should get the correct result for bitcoin uri', function() {
var uri = 'bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8%3Famount=0.1&message=a%20bitcoin%20donation';
var result = Structure.parseBitcoinURI(uri);
result.address.should.equal('19mP9FKrXqL46Si58pHdhGKow88SUPy1V8');
result.amount.should.equal(0.1);
result.message.should.equal('a bitcoin donation');
result.protocol.should.equal('bitcoin');
});
});

View File

@ -51,12 +51,15 @@ var createPKR = function(bip32s) {
w.addCopayer();
}
}
w.generateAddress(false);
w.generateAddress(false);
w.generateAddress(false);
w.generateAddress(true);
w.generateAddress(true);
w.generateAddress(true);
var pubkey = bip32s[0].publicHex;
w.generateAddress(false, pubkey);
w.generateAddress(false, pubkey);
w.generateAddress(false, pubkey);
w.generateAddress(true, pubkey);
w.generateAddress(true, pubkey);
w.generateAddress(true, pubkey);
return w;
};
@ -77,19 +80,22 @@ describe('TxProposals model', function() {
var priv = new PrivateKey(config);
var priv2 = new PrivateKey(config);
var priv3 = new PrivateKey(config);
var pub = priv.publicHex;
var ts = Date.now();
var pkr = createPKR([priv, priv2, priv3]);
var opts = {
remainderOut: {
address: pkr.generateAddress(true).toString()
address: pkr.generateAddress(true, pub).toString()
}
};
var w = new TxProposals({
networkName: config.networkName,
});
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
@ -102,8 +108,10 @@ describe('TxProposals model', function() {
var b = w.txps[ntxid].builder;
var tx = b.build();
tx.isComplete().should.equal(false);
b.sign(priv2.getAll(pkr.indexes.getReceiveIndex(), pkr.indexes.getChangeIndex()));
b.sign(priv3.getAll(pkr.indexes.getReceiveIndex(), pkr.indexes.getChangeIndex()));
var ringIndex = pkr.getIndex(pub);
b.sign(priv2.getAll(ringIndex.getReceiveIndex(), ringIndex.getChangeIndex(), ringIndex.cosigner));
b.sign(priv3.getAll(ringIndex.getReceiveIndex(), ringIndex.getChangeIndex(), ringIndex.cosigner));
tx = b.build();
tx.isComplete().should.equal(true);
@ -132,6 +140,7 @@ describe('TxProposals model', function() {
opts = opts || {};
var amountSat = bitcore.Bignum(amountSatStr);
var pub = priv.publicHex;
if (!pkr.isComplete()) {
throw new Error('publicKeyRing is not complete');
@ -139,7 +148,7 @@ describe('TxProposals model', function() {
if (!opts.remainderOut) {
opts.remainderOut = {
address: pkr.generateAddress(true).toString()
address: pkr.generateAddress(true, pub).toString()
};
};
@ -181,14 +190,16 @@ describe('TxProposals model', function() {
it('#getUsedUnspend', function() {
var priv = new PrivateKey(config);
var pub = priv.publicHex;
var w = new TxProposals({
networkName: config.networkName,
});
var start = new Date().getTime();
var pkr = createPKR([priv]);
var ts = Date.now();
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
@ -204,6 +215,8 @@ describe('TxProposals model', function() {
it('#merge with self', function() {
var priv = new PrivateKey(config);
var pub = priv.publicHex;
var w = new TxProposals({
networkName: config.networkName,
});
@ -211,8 +224,8 @@ describe('TxProposals model', function() {
var pkr = createPKR([priv]);
var ts = Date.now();
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
@ -246,11 +259,13 @@ describe('TxProposals model', function() {
it('#merge, merge signatures case 1', function() {
var priv2 = new PrivateKey(config);
var priv = new PrivateKey(config);
var pub = priv.publicHex;
var ts = Date.now();
var pkr = createPKR([priv]);
var opts = {
remainderOut: {
address: pkr.generateAddress(true).toString()
address: pkr.generateAddress(true, pub).toString()
}
};
@ -258,8 +273,8 @@ describe('TxProposals model', function() {
var w = new TxProposals({
networkName: config.networkName,
});
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
@ -282,8 +297,8 @@ describe('TxProposals model', function() {
networkName: config.networkName,
publicKeyRing: w.publicKeyRing,
});
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w2.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
@ -346,6 +361,7 @@ describe('TxProposals model', function() {
var priv = PrivateKey.fromObj(o1);
var priv2 = PrivateKey.fromObj(o2);
var priv3 = PrivateKey.fromObj(o3);
var pub = priv.publicHex;
var ts = Date.now();
var pkr = createPKR([priv, priv2]);
@ -354,9 +370,9 @@ describe('TxProposals model', function() {
address: '2MxK2m7cPtEwjZBB8Ksq7ppjkgJyFPJGemr'
}
};
var addressToSign = pkr.generateAddress(false);
var addressToSign = pkr.generateAddress(false, pub);
unspentTest[0].address = addressToSign.toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
var tx, txb;
var w = new TxProposals({
@ -459,19 +475,22 @@ describe('TxProposals model', function() {
var priv = new PrivateKey(config);
var priv2 = new PrivateKey(config);
var priv3 = new PrivateKey(config);
var pub = priv.publicHex;
var ts = Date.now();
var pkr = createPKR([priv, priv2, priv3]);
var opts = {
remainderOut: {
address: pkr.generateAddress(true).toString()
address: pkr.generateAddress(true, pub).toString()
}
};
var w = new TxProposals({
networkName: config.networkName,
});
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
@ -491,8 +510,8 @@ describe('TxProposals model', function() {
var w2 = new TxProposals({
networkName: config.networkName,
});
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w2.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
@ -511,8 +530,8 @@ describe('TxProposals model', function() {
var w3 = new TxProposals({
networkName: config.networkName,
});
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w3.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
@ -558,6 +577,8 @@ describe('TxProposals model', function() {
it('#toObj #fromObj roundtrip', function() {
var priv = new PrivateKey(config);
var pub = priv.publicHex;
var pkr = createPKR([priv]);
var w = new TxProposals({
walletId: 'qwerty',
@ -565,8 +586,8 @@ describe('TxProposals model', function() {
});
var ts = Date.now();
unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',

View File

@ -9,6 +9,7 @@ try {
var copay = require('../copay'); //node
}
var Wallet = require('../js/models/core/Wallet');
var Structure = copay.Structure;
var Storage = require('./mocks/FakeStorage');
var Network = require('./mocks/FakeNetwork');
var Blockchain = require('./mocks/FakeBlockchain');
@ -124,7 +125,7 @@ describe('Wallet model', function() {
var opts = {};
var w = cachedCreateW();
addCopayers(w);
w.publicKeyRing.generateAddress(false);
w.publicKeyRing.generateAddress(false, w.publicKey);
w.publicKeyRing.isComplete().should.equal(true);
w.generateAddress(true).isValid().should.equal(true);
w.generateAddress(true, function(addr) {
@ -174,15 +175,33 @@ describe('Wallet model', function() {
return w;
};
it('#create, 1 sign', function() {
it('#create, fail for network', function() {
var w = cachedCreateW2();
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
var f = function() {
var ntxid = w.createTxSync(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
null,
unspentTest
);
};
f.should.throw(Error);
});
it('#create, 1 sign', function() {
var w = cachedCreateW2();
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
var ntxid = w.createTxSync(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79',
'123456789',
null,
unspentTest
@ -203,11 +222,11 @@ describe('Wallet model', function() {
var w = cachedCreateW2();
var comment = 'This is a comment';
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
var ntxid = w.createTxSync(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79',
'123456789',
comment,
unspentTest
@ -225,12 +244,12 @@ describe('Wallet model', function() {
var w = cachedCreateW2();
var comment = 'Lorem ipsum dolor sit amet, suas euismod vis te, velit deleniti vix an. Pri ex suscipit similique, inermis per';
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
var badCreate = function() {
w.createTxSync(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79',
'123456789',
comment,
unspentTest
@ -261,10 +280,10 @@ describe('Wallet model', function() {
var ts = Date.now();
for (var isChange = false; !isChange; isChange = true) {
for (var index = 0; index < 3; index++) {
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange);
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange, w.publicKey);
w.createTxSync(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79',
'123456789',
null,
unspentTest
@ -343,13 +362,15 @@ describe('Wallet model', function() {
it('handle network indexes correctly', function() {
var w = createW();
var aiObj = {
walletId: w.id,
changeIndex: 3,
receiveIndex: 2
indexes: [{
cosigner: 0,
changeIndex: 3,
receiveIndex: 2
}]
};
w._handleIndexes('senderID', aiObj, true);
w.publicKeyRing.indexes.getReceiveIndex(2);
w.publicKeyRing.indexes.getChangeIndex(3);
w.publicKeyRing.getIndex(0).getReceiveIndex(2);
w.publicKeyRing.getIndex(0).getChangeIndex(3);
});
it('handle network pubKeyRings correctly', function() {
@ -365,19 +386,19 @@ describe('Wallet model', function() {
networkName: w.networkName,
requiredCopayers: w.requiredCopayers,
totalCopayers: w.totalCopayers,
indexes: {
walletId: undefined,
indexes: [{
cosigner: 0,
changeIndex: 2,
receiveIndex: 3
},
}],
copayersExtPubKeys: cepk,
nicknameFor: {},
};
w._handlePublicKeyRing('senderID', {
publicKeyRing: pkrObj
}, true);
w.publicKeyRing.indexes.getReceiveIndex(2);
w.publicKeyRing.indexes.getChangeIndex(3);
w.publicKeyRing.getIndex(0).getReceiveIndex(2);
w.publicKeyRing.getIndex(0).getChangeIndex(3);
for (var i = 0; i < w.requiredCopayers; i++) {
w.publicKeyRing.toObj().copayersExtPubKeys[i].should.equal(cepk[i]);
}
@ -579,7 +600,7 @@ describe('Wallet model', function() {
}];
var addr = w.generateAddress().toString();
utxo[0].address = addr;
utxo[0].scriptPubKey = TransactionBuilder.scriptForAddress(addr).serialize().toString('hex');
utxo[0].scriptPubKey = (new bitcore.Address(addr)).getScriptPubKey().serialize().toString('hex');
return utxo;
};
var toAddress = 'mjfAe7YrzFujFf8ub5aUrCaN5GfSABdqjh';
@ -669,8 +690,8 @@ describe('Wallet model', function() {
before(function() {
w = cachedCreateW2();
ADDRESSES_CHANGE = w.deriveAddresses(0, 20, true);
ADDRESSES_RECEIVE = w.deriveAddresses(0, 20, false);
ADDRESSES_CHANGE = w.deriveAddresses(0, 20, true, 0);
ADDRESSES_RECEIVE = w.deriveAddresses(0, 20, false, 0);
});
var mockFakeActivity = function(f) {
@ -689,7 +710,7 @@ describe('Wallet model', function() {
mockFakeActivity(function(index) {
return false;
});
w.indexDiscovery(0, false, 5, function(e, lastActive) {
w.indexDiscovery(0, false, 0, 5, function(e, lastActive) {
lastActive.should.equal(-1);
done();
});
@ -699,7 +720,7 @@ describe('Wallet model', function() {
mockFakeActivity(function(index) {
return index <= 7;
});
w.indexDiscovery(0, false, 5, function(e, lastActive) {
w.indexDiscovery(0, false, 0, 5, function(e, lastActive) {
lastActive.should.equal(7);
done();
});
@ -709,7 +730,7 @@ describe('Wallet model', function() {
mockFakeActivity(function(index) {
return index <= 10 || index == 17;
});
w.indexDiscovery(0, false, 5, function(e, lastActive) {
w.indexDiscovery(0, false, 0, 5, function(e, lastActive) {
lastActive.should.equal(10);
done();
});
@ -719,7 +740,7 @@ describe('Wallet model', function() {
mockFakeActivity(function(index) {
return index <= 14 && index % 2 == 0;
});
w.indexDiscovery(0, false, 5, function(e, lastActive) {
w.indexDiscovery(0, false, 0, 5, function(e, lastActive) {
lastActive.should.equal(14);
done();
});
@ -731,8 +752,11 @@ describe('Wallet model', function() {
});
w.updateIndexes(function(err) {
w.publicKeyRing.indexes.receiveIndex.should.equal(15);
w.publicKeyRing.indexes.changeIndex.should.equal(15);
w.publicKeyRing.getIndex(0).receiveIndex.should.equal(15);
w.publicKeyRing.getIndex(0).changeIndex.should.equal(15);
w.publicKeyRing.getIndex(1).receiveIndex.should.equal(0);
w.publicKeyRing.getIndex(1).changeIndex.should.equal(0);
done();
});
});
@ -752,8 +776,8 @@ describe('Wallet model', function() {
it('#deriveAddresses', function(done) {
var w = cachedCreateW2();
var addresses1 = w.deriveAddresses(0, 5, false);
var addresses2 = w.deriveAddresses(4, 5, false);
var addresses1 = w.deriveAddresses(0, 5, false, 0);
var addresses2 = w.deriveAddresses(4, 5, false, 0);
addresses1.length.should.equal(5);
addresses2.length.should.equal(5);

View File

@ -10,7 +10,7 @@ var FakeBlockchain = require('./mocks/FakeBlockchain');
var FakeStorage = require('./mocks/FakeStorage');
var WalletFactory = require('../js/models/core/WalletFactory');
var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}';
var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}';
describe('WalletFactory model', function() {
var config = {
@ -82,7 +82,6 @@ describe('WalletFactory model', function() {
});
it('#fromObj #toObj round trip', function() {
var wf = new WalletFactory(config, '0.0.5');
var w = wf.fromObj(JSON.parse(o));
@ -95,6 +94,22 @@ describe('WalletFactory model', function() {
JSON.stringify(w.toObj()).should.equal(o);
});
it('support old index schema: #fromObj #toObj round trip', function() {
var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}';
var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2147483647,"changeIndex":0,"receiveIndex":0},{"cosigner":0,"changeIndex":0,"receiveIndex":0},{"cosigner":1,"changeIndex":0,"receiveIndex":0},{"cosigner":2,"changeIndex":0,"receiveIndex":0},{"cosigner":3,"changeIndex":0,"receiveIndex":0},{"cosigner":4,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}';
var wf = new WalletFactory(config, '0.0.5');
var w = wf.fromObj(JSON.parse(o));
should.exist(w);
w.id.should.equal("dbfe10c3fae71cea");
should.exist(w.publicKeyRing.getCopayerId);
should.exist(w.txProposals.toObj);
should.exist(w.privateKey.toObj);
JSON.stringify(w.toObj()).should.equal(o2);
});
it('should create wallet from encrypted object', function() {
var wf = new WalletFactory(config, '0.0.1');
var walletObj = JSON.parse(o);

View File

@ -42,7 +42,7 @@ describe("Unit: Controllers", function() {
}));
it('Should have a Backup controller', function() {
expect(scope.title).equal('Backup');
expect(scope.title).equal('Settings');
});
it('Backup controller #download', function() {
@ -174,12 +174,17 @@ describe("Unit: Controllers", function() {
expect(scope.showAddressBook()).equal(true);
});
it('should validate address', function() {
form.newaddress.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
it('should validate address with network', function() {
form.newaddress.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
expect(form.newaddress.$invalid).to.equal(false);
});
it('should not validate address', function() {
it('should not validate address with other network', function() {
form.newaddress.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
expect(form.newaddress.$invalid).to.equal(true);
});
it('should not validate random address', function() {
form.newaddress.$setViewValue('thisisaninvalidaddress');
expect(form.newaddress.$invalid).to.equal(true);
});
@ -194,7 +199,7 @@ describe("Unit: Controllers", function() {
});
it('should create a transaction proposal', function() {
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
sendForm.amount.$setViewValue(1000);
var spy = sinon.spy(scope.wallet, 'createTx');
@ -205,7 +210,7 @@ describe("Unit: Controllers", function() {
});
it('should create and send a transaction proposal', function() {
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
sendForm.amount.$setViewValue(1000);
scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1;
@ -300,7 +305,7 @@ describe("Unit: Controllers", function() {
'<form name="form">' +
'<input type="number" id="amount" name="amount" placeholder="Amount" ng-model="amount" min="0.0001" max="10000000" enough-amount required>' +
'</form>'
);
);
scope.model = {
amount: null
};
@ -368,4 +373,30 @@ describe("Unit: Controllers", function() {
});
});
describe('UriPayment Controller', function() {
var what;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
var routeParams = {
data: 'bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8%3Famount=0.1&message=a%20bitcoin%20donation'
};
what = $controller('UriPaymentController', {
$scope: scope,
$routeParams: routeParams
});
}));
it('should exist', function() {
should.exist(what);
});
it('should parse url correctly', function() {
should.exist(what);
scope.protocol.should.equal('bitcoin');
scope.address.should.equal('19mP9FKrXqL46Si58pHdhGKow88SUPy1V8');
scope.amount.should.equal(0.1);
scope.message.should.equal('a bitcoin donation');
});
});
});

View File

@ -36,11 +36,17 @@ describe("Unit: Testing Directives", function() {
form = $scope.form;
}));
it('should validate', function() {
it('should validate with network', function() {
config.networkName = 'testnet';
form.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
expect(form.address.$invalid).to.equal(false);
});
it('should not validate', function() {
it('should not validate with other network', function() {
config.networkName = 'livenet';
form.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
expect(form.address.$invalid).to.equal(true);
});
it('should not validate random', function() {
form.address.$setViewValue('thisisaninvalidaddress');
expect(form.address.$invalid).to.equal(true);
});

View File

@ -69,6 +69,35 @@ describe('Unit: Testing Filters', function() {
}));
});
describe('removeEmpty addresses', function() {
it('should work with empty lists', inject(function($filter) {
var removeEmpty = $filter('removeEmpty');
expect(removeEmpty([]).length).to.equal(0);
}));
it('should filter empty addresses from other copayers', inject(function($filter) {
var removeEmpty = $filter('removeEmpty');
var addresses = [{
owned: true,
balance: 0
}, {
owned: false,
balance: 0
}, {
owned: true,
balance: 0
}, {
owned: false,
balance: 0
}];
expect(removeEmpty(addresses).length).to.equal(2);
addresses[1].owned = true;
expect(removeEmpty(addresses).length).to.equal(3);
addresses[3].balance = 10;
expect(removeEmpty(addresses).length).to.equal(4);
}));
});
describe('noFractionNumber bits', function() {
beforeEach(function() {
config.unitToSatoshi = 100;

View File

@ -181,3 +181,20 @@ describe("Unit: isMobile Service", function() {
isMobile.any().should.equal(true);
}));
});
describe("Unit: video service", function() {
beforeEach(angular.mock.module('copayApp.services'));
it('should contain a video service', inject(function(video) {
should.exist(video);
}));
});
describe("Unit: uriHandler service", function() {
beforeEach(angular.mock.module('copayApp.services'));
it('should contain a uriHandler service', inject(function(uriHandler) {
should.exist(uriHandler);
}));
it('should register', inject(function(uriHandler) {
(function() {
uriHandler.register();
}).should.not.throw();
}));
});