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

10
.gitignore vendored
View file

@ -55,3 +55,13 @@ coverage/
shell/bin/linux shell/bin/linux
shell/bin/darwin shell/bin/darwin
shell/bin/win32 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 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 ## Tests
Open test/index.html in your browser to test the models. Install and run karma 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", "sjcl": "1.0.0",
"file-saver": "*", "file-saver": "*",
"qrcode-decoder-js": "*", "qrcode-decoder-js": "*",
"bitcore": "0.1.24", "bitcore": "0.1.25",
"angular-moment": "~0.7.1", "angular-moment": "~0.7.1",
"socket.io-client": ">=1.0.0", "socket.io-client": ">=1.0.0",
"mousetrap": "1.4.6" "mousetrap": "1.4.6"

View file

@ -197,9 +197,9 @@ span.panel-res {
} }
.line-dashed { .line-dashed {
border-top: 2px dashed #ccc; border-top: 1px dashed #ccc;
margin: 1rem 0; margin: 1rem 0;
padding: 1rem 0; /* padding: 1rem 0; */
} }
@ -271,7 +271,7 @@ hr { margin: 2.25rem 0;}
.p70l {padding-left: 70px;} .p70l {padding-left: 70px;}
.p5h {padding: 0 5px;} .p5h {padding: 0 5px;}
.p20h {padding: 0 20px;} .p20h {padding: 0 20px;}
.m30v {margin: 30px 0;} .m30v {/* margin: 30px 0; */}
.m10h {margin:0 10px;} .m10h {margin:0 10px;}
.m30a {margin: 30px auto;} .m30a {margin: 30px auto;}
.br100 {border-radius: 100%;} .br100 {border-radius: 100%;}
@ -286,6 +286,11 @@ hr { margin: 2.25rem 0;}
display: inline; display: inline;
float: right; float: right;
} }
.setup .video-small {
float: none !important;
}
.online { .online {
background-color: black; background-color: black;
border: 3px solid #1ABC9C; 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>
<div ng-if='$root.wallet && !$root.wallet.isReady() && !loading'> <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="row" ng-if="!$root.wallet.publicKeyRing.isComplete()">
<div class="large-12 medium-12 small-12 columns "> <div class="large-12 medium-12 small-12 columns ">
<div class="panel radius m30v"> <div class="panel radius m30v">
<h3 class="m15b">Share this secret with your other copayers <h3 class="m15b">Share this secret with your other copayers
<small> for them to join your wallet</small>
</h3> </h3>
<div class="row"> <div class="row">
<div class="large-9 medium-12 small-12 columns line-dashed-v text-gray"> <div class="large-9 medium-12 small-12 columns line-dashed-v text-gray">
@ -122,25 +101,63 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row m10t">
<div class="large-12 medium-12 small-12 columns "> <div class="large-12 medium-12 small-12 columns ">
<div class="box-setup-copayers"> <div class="box-setup-copayers">
<div class="box-setup-copayers-fix"> <div class="box-setup-copayers-fix">
<img class="box-setup-copay" <h5>People on this wallet</h5>
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()}">
<button class="button primary radius right" <div class="setup" ng-repeat="c in $root.wallet.getRegisteredPeerIds()">
ng-click="backupAndOpen()" <video ng-if="$root.videoInfo[c.peerId]"
ng-disabled="!$root.wallet.publicKeyRing.isComplete()"> avatar peer="{{c}}"
Backup keys and continue autoplay
</button> 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>
</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>
</div> </div>
@ -156,10 +173,20 @@
<div id="footer" data-ng-controller="FooterController" ng-class="{'footer-home': !$root.wallet || !$root.wallet.isReady()}"> <div id="footer" data-ng-controller="FooterController" ng-class="{'footer-home': !$root.wallet || !$root.wallet.isReady()}">
<link rel="stylesheet" ng-href="{{theme}}"> <link rel="stylesheet" ng-href="{{theme}}">
<div ng-show="!$root.wallet"> <div ng-show="!$root.wallet">
<div class="large-12 columns text-left"> <div class="large-4 columns text-left">
Copay Copay
<small>v{{version}}</small> <small>v{{version}}</small>
<small ng-if="networkName==='testnet'">[TESTNET]</small>
</div> </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>
<div ng-show="$root.wallet && !$root.wallet.isReady()"> <div ng-show="$root.wallet && !$root.wallet.isReady()">
<div class="large-6 medium-6 small-6 columns"> <div class="large-6 medium-6 small-6 columns">
@ -235,6 +262,7 @@
</div> </div>
<div ng-show="!wallets.length"> <div ng-show="!wallets.length">
<h3>Create a new wallet</h3> <h3>Create a new wallet</h3>
<p class="text-gray"> <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. 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> </p>
@ -247,8 +275,8 @@
<h3>Join a Wallet in Creation</h3> <h3>Join a Wallet in Creation</h3>
<form name="joinForm" ng-submit="join(joinForm)" novalidate> <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="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="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> <button type="submit" class="button primary radius" ng-disabled="joinForm.$invalid || loading" loading="Joining">Join</button>
</form> </form>
</div> </div>
@ -275,29 +303,25 @@
{{ importStatus }} {{ importStatus }}
</div> </div>
<div ng-init="choosefile=0; pastetext=0" ng-show="!loading"> <div ng-init="choosefile=0; pastetext=0" ng-show="!loading">
<h3>{{title}}</h3>
<form name="importForm" ng-submit="import(importForm)" novalidate> <form name="importForm" ng-submit="import(importForm)" novalidate>
<div class="row"> <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> <fieldset>
<legend>Select which method want to use to restore</legend> <legend for="backupFile" class="m10b"> Choose backup file from your computer <i class="fi-laptop"></i></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>
<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>
</fieldset> </fieldset>
</div> </div>
<div class="large-6 large-centered medium-6 medium-centered columns"> <div class="large-6 large-centered medium-8 medium-centered columns">
<label for="password">Password <small>Required</small></label> <label for="password">Password <small>Required</small></label>
<input type="password" class="form-control" placeholder="Your wallet password" name="password" ng-model="password" required> <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> </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"> <button type="submit" class="button primary radius right" ng-disabled="importForm.$invalid" loading="Importing">
Import backup Import backup
</button> </button>
@ -314,70 +338,67 @@
<i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i> <i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i>
Creating wallet... Creating wallet...
</div> </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> <form name="setupForm" ng-submit="create(setupForm)" novalidate>
<h3>Create new wallet</h3> <h3>Create new wallet</h3>
<div class="row"> <div>
<div class="small-12 medium-8 medium-centered large-8 large-centered columns"> <label>Wallet name <small>Optional</small>
<label>Wallet name <small>Optional</small> <input type="text" placeholder="Family vacation funds" class="form-control" ng-model="walletName">
<input type="text" placeholder="Family vacation funds" class="form-control" ng-model="walletName"> </label>
</label> </div>
</div> <div>
<div class="small-12 medium-8 medium-centered large-8 large-centered columns"> <div class="row">
<div class="row"> <div class="small-12 medium-6 large-6 columns">
<div class="small-12 medium-6 large-6 columns"> <label>Your name <small>Optional</small>
<label>Your name <small>Optional</small> <input type="text" placeholder="Name" class="form-control" ng-model="myNickname">
<input type="text" placeholder="Name" class="form-control" ng-model="myNickname"> </label>
</label> </div>
</div> <div class="small-12 medium-6 large-6 columns">
<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>
<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"
<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>
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>
</label>
</div>
</div> </div>
</div> </div>
<div class="small-12 medium-8 medium-centered large-8 large-centered columns box-setup"> </div>
<fieldset> <div class="box-setup">
<div class="row"> <fieldset>
<div class="large-6 medium-6 columns line-dashed-v"> <div class="row">
<label>Select total number of copayers <div class="large-6 medium-6 columns line-dashed-v">
<select ng-model="totalCopayers" ng-options="totalCopayers as totalCopayers for totalCopayers in TCValues"> <label>Select total number of copayers
</select> <select ng-model="totalCopayers" ng-options="totalCopayers as totalCopayers for totalCopayers in TCValues">
</label> </select>
</div> </label>
<div class="large-6 medium-6 columns"> </div>
<label>Select required signatures <div class="large-6 medium-6 columns">
<select ng-model="requiredCopayers" ng-options="requiredCopayers as requiredCopayers for requiredCopayers in RCValues"> <label>Select required signatures
</select> <select ng-model="requiredCopayers" ng-options="requiredCopayers as requiredCopayers for requiredCopayers in RCValues">
</label> </select>
</div> </label>
</div> </div>
</fieldset>
</div> </div>
<div class="small-12 medium-6 medium-centered large-6 large-centered columns m10b"> </fieldset>
<div class="box-setup-copayers"> </div>
<div class="box-setup-copayers-fix"> <div class="box-setup-copayers">
<img class="box-setup-copay" ng-repeat="i in getNumber(totalCopayers) track by $index" <div class="box-setup-copayers-fix">
src="./img/satoshi.gif" <img class="box-setup-copay" ng-repeat="i in getNumber(totalCopayers) track by $index"
title="Copayer {{$index+1}}-{{totalCopayers}}" src="./img/satoshi.gif"
ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}"> title="Copayer {{$index+1}}-{{totalCopayers}}"
</div> ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}">
</div>
</div> </div>
</div> </div>
<div class="row line-dashed"> <div class="row">
<div class="large-6 medium-6 small-5 columns text-left"> <div class="line-dashed"></div>
<a class="button secondary radius" href="#signin">Go back</a> <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>
<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> </form>
</div> </div>
</div> </div>
@ -390,7 +411,7 @@
<div class="row"> <div class="row">
<div class="large-9 medium-12 columns" ng-if="addresses[0]"> <div class="large-9 medium-12 columns" ng-if="addresses[0]">
<div class="large-8 medium-8 columns" ng-init="showAll=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-click="selectAddress(addr)"
ng-class="{selected : addr.address == selectedAddr.address}"> ng-class="{selected : addr.address == selectedAddr.address}">
@ -412,7 +433,7 @@
</span> </span>
</a> </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 all</span>
<span ng-if="showAll">Show less</span> <span ng-if="showAll">Show less</span>
</a> </a>
@ -630,7 +651,7 @@
<script type="text/ng-template" id="send.html"> <script type="text/ng-template" id="send.html">
<div class="send" data-ng-controller="SendController"> <div class="send" data-ng-controller="SendController">
<div class="row" ng-show='$root.wallet.isReady()'> <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> <h3>{{title}}</h3>
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate> <form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
<div class="row"> <div class="row">
@ -809,7 +830,7 @@
</div> </div>
</div> </div>
<div class="row text-center"> <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>
</div> </div>
</script> </script>
@ -829,7 +850,6 @@
<legend>Wallet Unit</legend> <legend>Wallet Unit</legend>
<select class="form-control" ng-model="selectedUnit" ng-options="o.name for o in unitOpts" required> <select class="form-control" ng-model="selectedUnit" ng-options="o.name for o in unitOpts" required>
</select> </select>
<label for="settingsUnit">Prefered Unit for Wallet</label>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>Videoconferencing</legend> <legend>Videoconferencing</legend>
@ -845,6 +865,9 @@
<input id="insight-secure" type="checkbox" ng-model="insightSecure" class="form-control" ng-click="changeInsightSSL()"> <input id="insight-secure" type="checkbox" ng-model="insightSecure" class="form-control" ng-click="changeInsightSSL()">
<label for="insight-secure">Use SSL</label> <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>
<fieldset> <fieldset>
<legend>PeerJS server</legend> <legend>PeerJS server</legend>
@ -856,6 +879,10 @@
<input type="number" ng-model="networkPort" class="form-control" name="peerjs-port"> <input type="number" ng-model="networkPort" class="form-control" name="peerjs-port">
<input id="peerjs-secure" type="checkbox" ng-model="networkSecure" class="form-control"> <input id="peerjs-secure" type="checkbox" ng-model="networkSecure" class="form-control">
<label for="peerjs-secure">Use SSL</label> <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> </fieldset>
</div> </div>
<div class="row"> <div class="row">
@ -886,6 +913,21 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
</script> </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 --> <!-- NOT FOUND -->
<script type="text/ng-template" id="404.html"> <script type="text/ng-template" id="404.html">
<h2 class="text-center">404</h2> <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>
<script src="lib/mousetrap/mousetrap.min.js"></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/angular/angular.min.js"></script>
<script src="lib/moment/min/moment.min.js"></script> <script src="lib/moment/min/moment.min.js"></script>
<script src="lib/angular-moment/angular-moment.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-route/angular-route.min.js"></script>
<script src="lib/angular-foundation/mm-foundation.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/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/bitcore/browser/bundle.js"></script>
<script src="lib/crypto-js/rollups/sha256.js"></script> <script src="lib/crypto-js/rollups/sha256.js"></script>
<script src="lib/crypto-js/rollups/pbkdf2.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="lib/qrcode-decoder-js/lib/qrcode-decoder.min.js"></script>
<script src="config.js"></script> <script src="config.js"></script>
<script src="js/shell.js"></script>
<script src="js/copayBundle.js"></script> <script src="js/copayBundle.js"></script>
<script src="js/app.js"></script> <script src="js/app.js"></script>
<script src="js/routes.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/notifications.js"></script>
<script src="js/services/backupService.js"></script> <script src="js/services/backupService.js"></script>
<script src="js/services/isMobile.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/header.js"></script>
<script src="js/controllers/footer.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/setup.js"></script>
<script src="js/controllers/import.js"></script> <script src="js/controllers/import.js"></script>
<script src="js/controllers/settings.js"></script> <script src="js/controllers/settings.js"></script>
<script src="js/controllers/uriPayment.js"></script>
<script src="js/init.js"></script> <script src="js/init.js"></script>
</body> </body>

View file

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

View file

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

View file

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

View file

@ -15,8 +15,8 @@ angular.module('copayApp.controllers').controller('HeaderController',
'icon': 'fi-arrow-right', 'icon': 'fi-arrow-right',
'link': '#/send' 'link': '#/send'
}, { }, {
'title': 'Backup', 'title': 'Settings',
'icon': 'fi-archive', 'icon': 'fi-wrench',
'link': '#/backup' 'link': '#/backup'
}]; }];
@ -71,7 +71,9 @@ angular.module('copayApp.controllers').controller('HeaderController',
$rootScope.$watch('txAlertCount', function(txAlertCount) { $rootScope.$watch('txAlertCount', function(txAlertCount) {
if (txAlertCount && txAlertCount > 0) { 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 backupFile = $scope.file;
var backupText = form.backupText.$modelValue;
var password = form.password.$modelValue; var password = form.password.$modelValue;
if (!backupFile && !backupText) { if (!backupFile) {
$scope.loading = false; $scope.loading = false;
notification.error('Error', 'Please, select your backup file or paste the file contents'); notification.error('Error', 'Please, select your backup file or paste the file contents');
$scope.loading = false; $scope.loading = false;
@ -87,8 +86,6 @@ angular.module('copayApp.controllers').controller('ImportController',
if (backupFile) { if (backupFile) {
reader.readAsBinaryString(backupFile); reader.readAsBinaryString(backupFile);
} else { }
_importBackup(backupText);
}
}; };
}); });

View file

@ -23,6 +23,13 @@ angular.module('copayApp.controllers').controller('SendController',
return flag; 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 // Detect protocol
$scope.isHttp = ($window.location.protocol.indexOf('http') === 0); $scope.isHttp = ($window.location.protocol.indexOf('http') === 0);
@ -61,6 +68,7 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.loading = false; $scope.loading = false;
}); });
} }
$rootScope.pendingPayment = null;
}); });
// reset fields // reset fields
@ -250,7 +258,7 @@ angular.module('copayApp.controllers').controller('SendController',
}; };
$scope.topAmount = function(form) { $scope.topAmount = function(form) {
$scope.amount = $scope.getAvailableAmount(); $scope.amount = $scope.getAvailableAmount();
form.amount.$pristine = false; form.amount.$pristine = false;
}; };
}); });

View file

@ -11,7 +11,7 @@ angular.module('copayApp.controllers').controller('SettingsController',
$scope.networkHost = config.network.host; $scope.networkHost = config.network.host;
$scope.networkPort = config.network.port; $scope.networkPort = config.network.port;
$scope.networkSecure = config.network.secure || false; $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 = [{ $scope.unitOpts = [{
name: 'Satoshis (100,000,000 satoshis = 1BTC)', name: 'Satoshis (100,000,000 satoshis = 1BTC)',
@ -73,6 +73,8 @@ angular.module('copayApp.controllers').controller('SettingsController',
unitToSatoshi: $scope.selectedUnit.value, 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.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null;
$scope.openPassword = ''; $scope.openPassword = '';
if ($rootScope.pendingPayment) {
notification.info('Login Required', 'Please open wallet to complete payment');
}
$scope.open = function(form) { $scope.open = function(form) {
if (form && form.$invalid) { if (form && form.$invalid) {
notification.error('Error', 'Please enter the required fields'); 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) { link: function(scope, elem, attrs, ctrl) {
var validator = function(value) { var validator = function(value) {
var a = new Address(value); var a = new Address(value);
ctrl.$setValidity('validAddress', a.isValid()); ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName);
return value; return value;
}; };
@ -29,8 +29,7 @@ angular.module('copayApp.directives')
restrict: 'A', restrict: 'A',
link: function(scope, element, attrs, ctrl) { link: function(scope, element, attrs, ctrl) {
setTimeout(function() { setTimeout(function() {
scope.$apply(function() { scope.$apply(function() {});
});
}, 5000); }, 5000);
} }
}; };

View file

@ -17,6 +17,14 @@ angular.module('copayApp.filters', [])
return false; 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() { .filter('limitAddress', function() {
return function(elements, showAll) { return function(elements, showAll) {
if (elements.length <= 1 || showAll) { if (elements.length <= 1 || showAll) {

View file

@ -1,29 +1,56 @@
'use strict'; 'use strict';
var imports = require('soop').imports(); var imports = require('soop').imports();
var preconditions = require('preconditions').singleton();
var Structure = require('./Structure');
function AddressIndex(opts) { function AddressIndex(opts) {
opts = opts || {}; opts = opts || {};
this.walletId = opts.walletId; this.cosigner = opts.cosigner
this.changeIndex = opts.changeIndex || 0; this.changeIndex = opts.changeIndex || 0;
this.receiveIndex = opts.receiveIndex || 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) { AddressIndex.fromObj = function(data) {
if (data instanceof AddressIndex) { if (data instanceof AddressIndex) {
throw new Error('bad data format: Did you use .toObj()?'); throw new Error('bad data format: Did you use .toObj()?');
} }
var ret = new AddressIndex(data); return new AddressIndex(data);
return ret; };
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() { AddressIndex.prototype.toObj = function() {
return { return {
walletId: this.walletId, cosigner: this.cosigner,
changeIndex: this.changeIndex, changeIndex: this.changeIndex,
receiveIndex: this.receiveIndex, receiveIndex: this.receiveIndex
}; };
}; };
@ -34,10 +61,10 @@ AddressIndex.prototype.checkRange = function(index, isChange) {
} }
}; };
AddressIndex.prototype.getChangeIndex = function() { AddressIndex.prototype.getChangeIndex = function() {
return this.changeIndex; return this.changeIndex;
}; };
AddressIndex.prototype.getReceiveIndex = function() { AddressIndex.prototype.getReceiveIndex = function() {
return this.receiveIndex; return this.receiveIndex;
}; };
@ -51,6 +78,9 @@ AddressIndex.prototype.increment = function(isChange) {
}; };
AddressIndex.prototype.merge = function(inAddressIndex) { AddressIndex.prototype.merge = function(inAddressIndex) {
preconditions.shouldBeObject(inAddressIndex)
.checkArgument(this.cosigner == inAddressIndex.cosigner);
var hasChanged = false; var hasChanged = false;
// Indexes // Indexes

View file

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

View file

@ -24,7 +24,8 @@ function PublicKeyRing(opts) {
this.copayersHK = opts.copayersHK || []; 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.publicKeysCache = opts.publicKeysCache || {};
this.nicknameFor = opts.nicknameFor || {}; this.nicknameFor = opts.nicknameFor || {};
@ -36,6 +37,12 @@ PublicKeyRing.fromObj = function(data) {
if (data instanceof PublicKeyRing) { if (data instanceof PublicKeyRing) {
throw new Error('bad data format: Did you use .toObj()?'); 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); var ret = new PublicKeyRing(data);
for (var k in data.copayersExtPubKeys) { for (var k in data.copayersExtPubKeys) {
@ -51,7 +58,7 @@ PublicKeyRing.prototype.toObj = function() {
networkName: this.network.name, networkName: this.network.name,
requiredCopayers: this.requiredCopayers, requiredCopayers: this.requiredCopayers,
totalCopayers: this.totalCopayers, totalCopayers: this.totalCopayers,
indexes: this.indexes.toObj(), indexes: AddressIndex.serialize(this.indexes),
copayersExtPubKeys: this.copayersHK.map(function(b) { copayersExtPubKeys: this.copayersHK.map(function(b) {
return b.extendedPublicKeyString(); return b.extendedPublicKeyString();
@ -136,10 +143,10 @@ PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
return newEpk; return newEpk;
}; };
PublicKeyRing.prototype.getPubKeys = function(index, isChange) { PublicKeyRing.prototype.getPubKeys = function(index, isChange, cosigner) {
this._checkKeys(); this._checkKeys();
var path = Structure.Branch(index, isChange); var path = Structure.Branch(index, isChange, cosigner);
var pubKeys = this.publicKeysCache[path]; var pubKeys = this.publicKeysCache[path];
if (!pubKeys) { if (!pubKeys) {
pubKeys = []; pubKeys = [];
@ -162,20 +169,29 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange) {
}; };
// TODO this could be cached // TODO this could be cached
PublicKeyRing.prototype.getRedeemScript = function(index, isChange) { PublicKeyRing.prototype.getRedeemScript = function(index, isChange, cosigner) {
var pubKeys = this.getPubKeys(index, isChange); var pubKeys = this.getPubKeys(index, isChange, cosigner);
var script = Script.createMultisig(this.requiredCopayers, pubKeys); var script = Script.createMultisig(this.requiredCopayers, pubKeys);
return script; return script;
}; };
// TODO this could be cached // TODO this could be cached
PublicKeyRing.prototype.getAddress = function(index, isChange) { PublicKeyRing.prototype.getAddress = function(index, isChange, id) {
var script = this.getRedeemScript(index, isChange); var cosigner = this.getCosigner(id);
var script = this.getRedeemScript(index, isChange, cosigner);
var address = Address.fromScript(script, this.network.name); 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; 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) { PublicKeyRing.prototype.pathForAddress = function(address) {
var path = this.addressToPath[address]; var path = this.addressToPath[address];
if (!path) throw new Error('Couldn\'t find path for address ' + 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 // TODO this could be cached
PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange) { PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) {
var addr = this.getAddress(index, isChange); var cosigner = this.getCosigner(pubkey);
var addr = this.getAddress(index, isChange, cosigner);
return Script.createP2SH(addr.payload()).getBuffer().toString('hex'); return Script.createP2SH(addr.payload()).getBuffer().toString('hex');
}; };
//generate a new address, update index. //generate a new address, update index.
PublicKeyRing.prototype.generateAddress = function(isChange) { PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) {
isChange = !!isChange; isChange = !!isChange;
var index = isChange ? this.indexes.getChangeIndex() : this.indexes.getReceiveIndex(); var addrIndex = this.getIndex(pubkey);
var ret = this.getAddress(index, isChange); var index = isChange ? addrIndex.getChangeIndex() : addrIndex.getReceiveIndex();
this.indexes.increment(isChange); var ret = this.getAddress(index, isChange, addrIndex.cosigner);
addrIndex.increment(isChange);
return ret; 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 || {}; opts = opts || {};
var isOwned = index.cosigner == Structure.SHARED_INDEX
|| index.cosigner == cosigner;
var ret = []; var ret = [];
if (!opts.excludeChange) { if (!opts.excludeChange) {
for (var i = 0; i < this.indexes.getChangeIndex(); i++) { for (var i = 0; i < index.changeIndex; i++) {
var a = this.getAddress(i, true); var a = this.getAddress(i, true, index.cosigner);
ret.unshift({ ret.unshift({
address: this.getAddress(i, true), address: a,
addressStr: a.toString(), addressStr: a.toString(),
isChange: true isChange: true,
owned: isOwned
}); });
} }
} }
if (!opts.excludeMain) { if (!opts.excludeMain) {
for (var i = 0; i < this.indexes.getReceiveIndex(); i++) { for (var i = 0; i < index.receiveIndex; i++) {
var a = this.getAddress(i, false); var a = this.getAddress(i, false, index.cosigner);
ret.unshift({ ret.unshift({
address: a, address: a,
addressStr: a.toString(), addressStr: a.toString(),
isChange: false isChange: false,
owned: isOwned
}); });
} }
} }
@ -235,7 +283,7 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts) {
// TODO this could be cached // TODO this could be cached
PublicKeyRing.prototype._addScriptMap = function(map, path) { PublicKeyRing.prototype._addScriptMap = function(map, path) {
var p = Structure.indicesForPath(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'); 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) { PublicKeyRing.prototype.merge = function(inPKR, ignoreId) {
var hasChanged = false;
this._checkInPKR(inPKR, ignoreId); this._checkInPKR(inPKR, ignoreId);
if (this.indexes.merge(inPKR.indexes)) var hasChanged = false;
hasChanged = true; hasChanged |= this.mergeIndexes(inPKR.indexes);
hasChanged |= this._mergePubkeys(inPKR);
if (this._mergePubkeys(inPKR)) return !!hasChanged;
hasChanged = true;
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); module.exports = require('soop')(PublicKeyRing);

View file

@ -40,6 +40,7 @@ Structure.indicesForPath = function(path) {
return { return {
isChange: s[3] === '1', isChange: s[3] === '1',
index: parseInt(s[4]), 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.SHARED_INDEX = SHARED_INDEX;
Structure.ID_INDEX = ID_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); module.exports = require('soop')(Structure);

View file

@ -13,6 +13,7 @@ var buffertools = bitcore.buffertools;
var Builder = bitcore.TransactionBuilder; var Builder = bitcore.TransactionBuilder;
var SecureRandom = bitcore.SecureRandom; var SecureRandom = bitcore.SecureRandom;
var Base58Check = bitcore.Base58.base58Check; var Base58Check = bitcore.Base58.base58Check;
var Address = bitcore.Address;
var AddressIndex = require('./AddressIndex'); var AddressIndex = require('./AddressIndex');
var PublicKeyRing = require('./PublicKeyRing'); var PublicKeyRing = require('./PublicKeyRing');
@ -38,12 +39,6 @@ function Wallet(opts) {
this.id = opts.id || Wallet.getRandomId(); this.id = opts.id || Wallet.getRandomId();
this.name = opts.name; 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.verbose = opts.verbose;
this.publicKeyRing.walletId = this.id; this.publicKeyRing.walletId = this.id;
this.txProposals.walletId = this.id; this.txProposals.walletId = this.id;
@ -51,6 +46,7 @@ function Wallet(opts) {
this.registeredPeerIds = []; this.registeredPeerIds = [];
this.addressBook = opts.addressBook || {}; this.addressBook = opts.addressBook || {};
this.backupOffered = opts.backupOffered || false; this.backupOffered = opts.backupOffered || false;
this.publicKey = this.privateKey.publicHex;
} }
Wallet.parent = EventEmitter; Wallet.parent = EventEmitter;
@ -81,8 +77,8 @@ Wallet.prototype.connectToAll = function() {
Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { Wallet.prototype._handleIndexes = function(senderId, data, isInbound) {
this.log('RECV INDEXES:', data); this.log('RECV INDEXES:', data);
var inIndexes = AddressIndex.fromObj(data.indexes); var inIndexes = AddressIndex.fromList(data.indexes);
var hasChanged = this.publicKeyRing.indexes.merge(inIndexes); var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes);
if (hasChanged) { if (hasChanged) {
this.emit('publicKeyRingUpdated'); this.emit('publicKeyRingUpdated');
this.store(); this.store();
@ -218,11 +214,6 @@ Wallet.prototype._optsToObj = function() {
version: this.version, version: this.version,
}; };
if (this.token) {
obj.token = this.token;
obj.tokenTime = new Date().getTime();
}
return obj; return obj;
}; };
@ -255,6 +246,7 @@ Wallet.decodeSecret = function(secretB) {
} }
}; };
Wallet.prototype._lockIncomming = function() { Wallet.prototype._lockIncomming = function() {
this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds()); this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds());
}; };
@ -279,7 +271,6 @@ Wallet.prototype.netStart = function(callback) {
var startOpts = { var startOpts = {
copayerId: myId, copayerId: myId,
privkey: myIdPriv, privkey: myIdPriv,
token: self.token,
maxPeers: self.totalCopayers maxPeers: self.totalCopayers
}; };
@ -289,12 +280,10 @@ Wallet.prototype.netStart = function(callback) {
net.start(startOpts, function() { net.start(startOpts, function() {
self.emit('ready', net.getPeer()); self.emit('ready', net.getPeer());
self.token = net.peer.options.token;
setTimeout(function() { setTimeout(function() {
self.emit('publicKeyRingUpdated', true); self.emit('publicKeyRingUpdated', true);
self.scheduleConnect(); self.scheduleConnect();
self.emit('txProposalsUpdated'); self.emit('txProposalsUpdated');
self.store();
}, 10); }, 10);
}); });
}; };
@ -332,7 +321,8 @@ Wallet.prototype.getRegisteredPeerIds = function() {
var pid = this.network.peerFromCopayer(cid); var pid = this.network.peerFromCopayer(cid);
this.registeredPeerIds.push({ this.registeredPeerIds.push({
peerId: pid, 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() { Wallet.prototype.toEncryptedObj = function() {
var walletObj = this.toObj(); var walletObj = this.toObj();
delete walletObj.opts.token;
delete walletObj.opts.tokenTime;
return this.storage.export(walletObj); return this.storage.export(walletObj);
}; };
@ -434,11 +422,12 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) {
}); });
}; };
Wallet.prototype.sendIndexes = 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, { this.network.send(recipients, {
type: 'indexes', type: 'indexes',
indexes: this.publicKeyRing.indexes.toObj(), indexes: indexes,
walletId: this.id, walletId: this.id,
}); });
}; };
@ -457,7 +446,7 @@ Wallet.prototype.getName = function() {
}; };
Wallet.prototype._doGenerateAddress = function(isChange) { 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); if (cb) cb(false);
} }
var pkr = self.publicKeyRing;
var keys = self.privateKey.getForPaths(txp.inputChainPaths); var keys = self.privateKey.getForPaths(txp.inputChainPaths);
var b = txp.builder; var b = txp.builder;
@ -583,7 +571,7 @@ Wallet.prototype.getAddressesStr = function(opts) {
}; };
Wallet.prototype.getAddressesInfo = 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) { Wallet.prototype.addressIsOwn = function(addrStr, opts) {
@ -691,7 +679,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
opts = opts || {}; opts = opts || {};
var amountSat = bignum(amountSatStr); var amountSat = bignum(amountSatStr);
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName());
if (!pkr.isComplete()) { if (!pkr.isComplete()) {
throw new Error('publicKeyRing is not complete'); throw new Error('publicKeyRing is not complete');
} }
@ -749,31 +737,44 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
Wallet.prototype.updateIndexes = function(callback) { Wallet.prototype.updateIndexes = function(callback) {
var self = this; var self = this;
var start = self.publicKeyRing.indexes.changeIndex;
self.log('Updating indexes...'); 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 (err) return callback(err);
if (changeIndex != -1) if (changeIndex != -1)
self.publicKeyRing.indexes.changeIndex = changeIndex + 1; index.changeIndex = changeIndex + 1;
start = self.publicKeyRing.indexes.receiveIndex; self.indexDiscovery(index.receiveIndex, false, index.cosigner, SCANN_WINDOW, function(err, receiveIndex) {
self.indexDiscovery(start, false, 20, function(err, receiveIndex) {
if (err) return callback(err); if (err) return callback(err);
if (receiveIndex != -1) if (receiveIndex != -1)
self.publicKeyRing.indexes.receiveIndex = receiveIndex + 1; index.receiveIndex = receiveIndex + 1;
self.log('Indexes updated');
self.emit('publicKeyRingUpdated');
self.store();
callback(); callback();
}); });
}); });
} }
Wallet.prototype.deriveAddresses = function(index, amout, isChange) { Wallet.prototype.deriveAddresses = function(index, amout, isChange, cosigner) {
var ret = new Array(amout); var ret = new Array(amout);
for (var i = 0; i < amout; i++) { 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; 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, // 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. // 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. // 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 scanIndex = start;
var lastActive = -1; var lastActive = -1;
var hasActivity = false; var hasActivity = false;
@ -791,7 +792,7 @@ Wallet.prototype.indexDiscovery = function(start, change, gap, cb) {
function _do(next) { function _do(next) {
// Optimize window to minimize the derivations. // Optimize window to minimize the derivations.
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; 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) { self.blockchain.checkActivity(addresses, function(err, actives) {
if (err) throw err; if (err) throw err;

View file

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

View file

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

View file

@ -2,7 +2,7 @@
var bitcore = require('bitcore'); var bitcore = require('bitcore');
angular.module('copayApp.services') 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 = {}; var root = {};
root.getVideoMutedStatus = function(copayer) { root.getVideoMutedStatus = function(copayer) {
@ -43,20 +43,15 @@ angular.module('copayApp.services')
}; };
root.installStartupHandlers = function(wallet, $scope) { 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() { wallet.on('connectionError', function() {
var message = "Looks like you are already connected to this wallet, please logout and try importing it again."; var message = "Looks like you are already connected to this wallet, please logout and try importing it again.";
notification.error('PeerJS Error', message); notification.error('PeerJS Error', message);
root.onErrorDigest($scope); root.onErrorDigest($scope);
}); });
wallet.on('serverError', function() { wallet.on('serverError', function(m) {
var message = 'The PeerJS server is not responding, please try again'; var message = m || 'The PeerJS server is not responding, please try again';
notification.error('PeerJS Error', message); $location.path('addresses');
root.onErrorDigest($scope); root.onErrorDigest($scope, message);
}); });
wallet.on('ready', function() { wallet.on('ready', function() {
$scope.loading = false; $scope.loading = false;
@ -64,6 +59,7 @@ angular.module('copayApp.services')
}; };
root.setupRootVariables = function() { root.setupRootVariables = function() {
uriHandler.register();
$rootScope.unitName = config.unitName; $rootScope.unitName = config.unitName;
$rootScope.txAlertCount = 0; $rootScope.txAlertCount = 0;
$rootScope.insightError = 0; $rootScope.insightError = 0;
@ -102,7 +98,11 @@ angular.module('copayApp.services')
}); });
w.on('ready', function(myPeerID) { w.on('ready', function(myPeerID) {
$rootScope.wallet = w; $rootScope.wallet = w;
$location.path('addresses'); if ($rootScope.pendingPayment) {
$location.path('send');
} else {
$location.path('addresses');
}
if (!config.disableVideo) if (!config.disableVideo)
video.setOwnPeer(myPeerID, w, handlePeerVideo); 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. ** the renderer into thinking that we are _not_ in a CommonJS environment.
*/ */
if (typeof module !== 'undefined') module = { if (typeof module !== 'undefined') module = {
exports: null exports: false
}; };
// are we running in copay shell? // are we running in copay shell?
@ -39,22 +39,12 @@
var ipc = require('ipc'); var ipc = require('ipc');
var clipb = require('clipboard'); 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() { Mousetrap.stopCallback = function() {
return false 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) { Mousetrap.bind('command+c', function(e) {
clipb.writeText(window.getSelection().toString()); clipb.writeText(window.getSelection().toString());
}); });
@ -62,6 +52,7 @@
Mousetrap.bind('command+v', function(e) { Mousetrap.bind('command+v', function(e) {
if (document.activeElement) { if (document.activeElement) {
document.activeElement.value = clipb.readText(); 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", "name": "copay",
"version": "0.2.1", "version": "0.3.1",
"description": "A multisignature wallet", "description": "A multisignature wallet",
"repository": { "repository": {
"type": "git", "type": "git",
@ -25,12 +25,13 @@
"shell": "node shell/scripts/launch.js", "shell": "node shell/scripts/launch.js",
"setup-shell": "node shell/scripts/download-atom-shell.js", "setup-shell": "node shell/scripts/download-atom-shell.js",
"chrome": "source browser-extensions/chrome/build.sh", "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", "homepage": "https://github.com/bitpay/copay",
"devDependencies": { "devDependencies": {
"async": "0.9.0", "async": "0.9.0",
"bitcore": "0.1.24", "bitcore": "0.1.25",
"blanket": "1.1.6", "blanket": "1.1.6",
"browser-pack": "2.0.1", "browser-pack": "2.0.1",
"browserify": "3.32.1", "browserify": "3.32.1",

View file

@ -8,4 +8,5 @@ using [Atom Shell](https://github.com/atom/atom-shell).
## Building ## 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 // quit when all windows are closed
app.on('window-all-closed', function() { app.on('window-all-closed', function() {
if (process.platform !== 'darwin') app.quit(); app.quit();
}); });
// initilization when ready // initilization when ready
@ -52,7 +52,7 @@ module.exports = function(copay) {
mainWindow = null; 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 color = require('cli-color');
var path = require('path'); var path = require('path');
var appPath = path.normalize(__dirname + '/../../'); var appPath = path.normalize(__dirname + '/../../');
var execPath = path.normalize(__dirname + '/../bin/' + process.platform); 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 // update execPath with platform specific binary locations
switch (process.platform) { switch (process.platform) {
@ -26,14 +26,14 @@ switch (process.platform) {
var copay = spawn(execPath, [appPath]); var copay = spawn(execPath, [appPath]);
copay.stdout.on('data', function (data) { copay.stdout.on('data', function(data) {
console.log(data); console.log("STDOUT:" + data);
}); });
copay.stderr.on('data', function (data) { copay.stderr.on('data', function(data) {
console.log(color.red(data)); console.log("STDERR:" + data);
}); });
copay.on('close', function (code) { copay.on('close', function(code) {
console.log('child process exited with code ' + 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 PublicKeyRing = copay.PublicKeyRing;
var AddressIndex = copay.AddressIndex; var AddressIndex = copay.AddressIndex;
var Structure = copay.Structure;
var config = { var config = {
@ -22,7 +23,7 @@ var createAI = function() {
var i = new AddressIndex(); var i = new AddressIndex();
should.exist(i); should.exist(i);
i.walletId = '1234567'; i.cosigner = 1;
return i; return i;
}; };
@ -34,7 +35,31 @@ describe('AddressIndex model', function() {
should.exist(i); 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 i = createAI();
var changeN = 2; var changeN = 2;
var addressN = 2; var addressN = 2;
@ -49,7 +74,7 @@ describe('AddressIndex model', function() {
should.exist(data); should.exist(data);
var i2 = AddressIndex.fromObj(data); var i2 = AddressIndex.fromObj(data);
i2.walletId.should.equal(i.walletId); i2.cosigner.should.equal(i.cosigner);
i2.getChangeIndex().should.equal(changeN); i2.getChangeIndex().should.equal(changeN);
i2.getReceiveIndex().should.equal(addressN); i2.getReceiveIndex().should.equal(addressN);
@ -74,7 +99,7 @@ describe('AddressIndex model', function() {
for (var i = 0; i < 7; i++) for (var i = 0; i < 7; i++)
j.increment(false); j.increment(false);
var j2 = new AddressIndex({ var j2 = new AddressIndex({
walletId: j.walletId, cosigner: j.cosigner,
}); });
j2.merge(j).should.equal(true); j2.merge(j).should.equal(true);
j2.changeIndex.should.equal(15); j2.changeIndex.should.equal(15);
@ -83,4 +108,12 @@ describe('AddressIndex model', function() {
j2.merge(j).should.equal(false); 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 bitcore = bitcore || require('bitcore');
var Address = bitcore.Address; var Address = bitcore.Address;
var buffertools = bitcore.buffertools; var buffertools = bitcore.buffertools;
var Structure = require('../js/models/core/Structure');
try { try {
var copay = require('copay'); //browser var copay = require('copay'); //browser
} catch (e) { } catch (e) {
@ -33,7 +36,8 @@ var createW = function(networkName) {
return { return {
w: w, 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 k = createW();
var w = k.w; var w = k.w;
var copayers = k.copayers; var copayers = k.copayers;
@ -93,10 +97,10 @@ describe('PublicKeyRing model', function() {
var addressN = 2; var addressN = 2;
var start = new Date().getTime(); var start = new Date().getTime();
for (var i = 0; i < changeN; i++) { for (var i = 0; i < changeN; i++) {
w.generateAddress(true); w.generateAddress(true, k.pub);
} }
for (var i = 0; i < addressN; i++) { for (var i = 0; i < addressN; i++) {
w.generateAddress(false); w.generateAddress(false, k.pub);
} }
var data = w.toObj(); var data = w.toObj();
@ -112,8 +116,8 @@ describe('PublicKeyRing model', function() {
}).should.throw(); }).should.throw();
} }
w2.indexes.getChangeIndex().should.equal(changeN); w2.getIndex(k.pub).getChangeIndex().should.equal(changeN);
w2.indexes.getReceiveIndex().should.equal(addressN); w2.getIndex(k.pub).getReceiveIndex().should.equal(addressN);
}); });
@ -123,7 +127,7 @@ describe('PublicKeyRing model', function() {
[true, false].forEach(function(isChange){ [true, false].forEach(function(isChange){
for (var i = 0; i < 2; i++) { 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.isValid().should.equal(true);
a.isScript().should.equal(true); a.isScript().should.equal(true);
a.network().name.should.equal('livenet'); a.network().name.should.equal('livenet');
@ -145,7 +149,7 @@ describe('PublicKeyRing model', function() {
[true, false].forEach(function(isChange){ [true, false].forEach(function(isChange){
for (var i = 0; i < 2; i++) { 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; var w = k.w;
for (var i = 0; i < 3; i++) for (var i = 0; i < 3; i++)
w.generateAddress(true); w.generateAddress(true, k.pub);
for (var i = 0; i < 2; i++) for (var i = 0; i < 2; i++)
w.generateAddress(false); w.generateAddress(false, k.pub);
w.indexes.getChangeIndex().should.equal(3); w.getIndex(k.pub).getChangeIndex().should.equal(3);
w.indexes.getReceiveIndex().should.equal(2); w.getIndex(k.pub).getReceiveIndex().should.equal(2);
}); });
it('#merge index tests', function() { it('#merge index tests', function() {
@ -177,9 +181,9 @@ describe('PublicKeyRing model', function() {
var w = k.w; var w = k.w;
for (var i = 0; i < 2; i++) for (var i = 0; i < 2; i++)
w.generateAddress(true); w.generateAddress(true, k.pub);
for (var i = 0; i < 3; i++) for (var i = 0; i < 3; i++)
w.generateAddress(false); w.generateAddress(false, k.pub);
var w2 = new PublicKeyRing({ var w2 = new PublicKeyRing({
networkName: 'livenet', networkName: 'livenet',
@ -188,8 +192,8 @@ describe('PublicKeyRing model', function() {
w2.merge(w).should.equal(true); w2.merge(w).should.equal(true);
w2.requiredCopayers.should.equal(3); w2.requiredCopayers.should.equal(3);
w2.totalCopayers.should.equal(5); w2.totalCopayers.should.equal(5);
w2.indexes.getChangeIndex().should.equal(2); w2.getIndex(k.pub).getChangeIndex().should.equal(2);
w2.indexes.getReceiveIndex().should.equal(3); w2.getIndex(k.pub).getReceiveIndex().should.equal(3);
// //
w2.merge(w).should.equal(false); 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() { it('#getRedeemScriptMap check tests', function() {
var k = createW(); var k = createW();
var w = k.w; var w = k.w;
var amount = 2; var amount = 2;
for (var i = 0; i < amount; i++) for (var i = 0; i < amount; i++)
w.generateAddress(true); w.generateAddress(true, k.pub);
for (var i = 0; i < amount; i++) for (var i = 0; i < amount; i++)
w.generateAddress(false); w.generateAddress(false, k.pub);
var m = w.getRedeemScriptMap([ var m = w.getRedeemScriptMap([
'm/45\'/2147483647/1/0', '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/0', {
['m/45\'/0/0/1', {index: 1, isChange: false}], index: 0,
['m/45\'/0/0/2', {index: 2, isChange: false}], isChange: false
['m/45\'/0/1/0', {index: 0, isChange: true}], }],
['m/45\'/0/1/1', {index: 1, isChange: true}], ['m/45\'/0/0/1', {
['m/45\'/0/1/2', {index: 2, isChange: true}], index: 1,
['m/45\'/0/0/900', {index: 900, isChange: false}], 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) { ].forEach(function(datum) {
var path = datum[0]; var path = datum[0];
var result = datum[1]; var result = datum[1];
@ -55,5 +76,13 @@ describe('Structure model', function() {
i.isChange.should.equal(result.isChange); 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.addCopayer();
} }
} }
w.generateAddress(false);
w.generateAddress(false); var pubkey = bip32s[0].publicHex;
w.generateAddress(false);
w.generateAddress(true); w.generateAddress(false, pubkey);
w.generateAddress(true); w.generateAddress(false, pubkey);
w.generateAddress(true); w.generateAddress(false, pubkey);
w.generateAddress(true, pubkey);
w.generateAddress(true, pubkey);
w.generateAddress(true, pubkey);
return w; return w;
}; };
@ -77,19 +80,22 @@ describe('TxProposals model', function() {
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var priv2 = new PrivateKey(config); var priv2 = new PrivateKey(config);
var priv3 = new PrivateKey(config); var priv3 = new PrivateKey(config);
var pub = priv.publicHex;
var ts = Date.now(); var ts = Date.now();
var pkr = createPKR([priv, priv2, priv3]); var pkr = createPKR([priv, priv2, priv3]);
var opts = { var opts = {
remainderOut: { remainderOut: {
address: pkr.generateAddress(true).toString() address: pkr.generateAddress(true, pub).toString()
} }
}; };
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, 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( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
@ -102,8 +108,10 @@ describe('TxProposals model', function() {
var b = w.txps[ntxid].builder; var b = w.txps[ntxid].builder;
var tx = b.build(); var tx = b.build();
tx.isComplete().should.equal(false); 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 = b.build();
tx.isComplete().should.equal(true); tx.isComplete().should.equal(true);
@ -132,6 +140,7 @@ describe('TxProposals model', function() {
opts = opts || {}; opts = opts || {};
var amountSat = bitcore.Bignum(amountSatStr); var amountSat = bitcore.Bignum(amountSatStr);
var pub = priv.publicHex;
if (!pkr.isComplete()) { if (!pkr.isComplete()) {
throw new Error('publicKeyRing is not complete'); throw new Error('publicKeyRing is not complete');
@ -139,7 +148,7 @@ describe('TxProposals model', function() {
if (!opts.remainderOut) { if (!opts.remainderOut) {
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() { it('#getUsedUnspend', function() {
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var pub = priv.publicHex;
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
}); });
var start = new Date().getTime(); var start = new Date().getTime();
var pkr = createPKR([priv]); var pkr = createPKR([priv]);
var ts = Date.now(); var ts = Date.now();
unspentTest[0].address = pkr.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
@ -204,6 +215,8 @@ describe('TxProposals model', function() {
it('#merge with self', function() { it('#merge with self', function() {
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var pub = priv.publicHex;
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
}); });
@ -211,8 +224,8 @@ describe('TxProposals model', function() {
var pkr = createPKR([priv]); var pkr = createPKR([priv]);
var ts = Date.now(); var ts = Date.now();
unspentTest[0].address = pkr.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
@ -246,11 +259,13 @@ describe('TxProposals model', function() {
it('#merge, merge signatures case 1', function() { it('#merge, merge signatures case 1', function() {
var priv2 = new PrivateKey(config); var priv2 = new PrivateKey(config);
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var pub = priv.publicHex;
var ts = Date.now(); var ts = Date.now();
var pkr = createPKR([priv]); var pkr = createPKR([priv]);
var opts = { var opts = {
remainderOut: { remainderOut: {
address: pkr.generateAddress(true).toString() address: pkr.generateAddress(true, pub).toString()
} }
}; };
@ -258,8 +273,8 @@ describe('TxProposals model', function() {
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
}); });
unspentTest[0].address = pkr.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
@ -282,8 +297,8 @@ describe('TxProposals model', function() {
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: w.publicKeyRing, publicKeyRing: w.publicKeyRing,
}); });
unspentTest[0].address = pkr.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w2.add(createTx( w2.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
@ -346,6 +361,7 @@ describe('TxProposals model', function() {
var priv = PrivateKey.fromObj(o1); var priv = PrivateKey.fromObj(o1);
var priv2 = PrivateKey.fromObj(o2); var priv2 = PrivateKey.fromObj(o2);
var priv3 = PrivateKey.fromObj(o3); var priv3 = PrivateKey.fromObj(o3);
var pub = priv.publicHex;
var ts = Date.now(); var ts = Date.now();
var pkr = createPKR([priv, priv2]); var pkr = createPKR([priv, priv2]);
@ -354,9 +370,9 @@ describe('TxProposals model', function() {
address: '2MxK2m7cPtEwjZBB8Ksq7ppjkgJyFPJGemr' address: '2MxK2m7cPtEwjZBB8Ksq7ppjkgJyFPJGemr'
} }
}; };
var addressToSign = pkr.generateAddress(false); var addressToSign = pkr.generateAddress(false, pub);
unspentTest[0].address = addressToSign.toString(); unspentTest[0].address = addressToSign.toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
var tx, txb; var tx, txb;
var w = new TxProposals({ var w = new TxProposals({
@ -459,19 +475,22 @@ describe('TxProposals model', function() {
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var priv2 = new PrivateKey(config); var priv2 = new PrivateKey(config);
var priv3 = new PrivateKey(config); var priv3 = new PrivateKey(config);
var pub = priv.publicHex;
var ts = Date.now(); var ts = Date.now();
var pkr = createPKR([priv, priv2, priv3]); var pkr = createPKR([priv, priv2, priv3]);
var opts = { var opts = {
remainderOut: { remainderOut: {
address: pkr.generateAddress(true).toString() address: pkr.generateAddress(true, pub).toString()
} }
}; };
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
}); });
unspentTest[0].address = pkr.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
@ -491,8 +510,8 @@ describe('TxProposals model', function() {
var w2 = new TxProposals({ var w2 = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
}); });
unspentTest[0].address = pkr.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w2.add(createTx( w2.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
@ -511,8 +530,8 @@ describe('TxProposals model', function() {
var w3 = new TxProposals({ var w3 = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
}); });
unspentTest[0].address = pkr.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w3.add(createTx( w3.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
@ -558,6 +577,8 @@ describe('TxProposals model', function() {
it('#toObj #fromObj roundtrip', function() { it('#toObj #fromObj roundtrip', function() {
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var pub = priv.publicHex;
var pkr = createPKR([priv]); var pkr = createPKR([priv]);
var w = new TxProposals({ var w = new TxProposals({
walletId: 'qwerty', walletId: 'qwerty',
@ -565,8 +586,8 @@ describe('TxProposals model', function() {
}); });
var ts = Date.now(); var ts = Date.now();
unspentTest[0].address = pkr.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString();
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub);
w.add(createTx( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',

View file

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

View file

@ -42,7 +42,7 @@ describe("Unit: Controllers", function() {
})); }));
it('Should have a Backup controller', function() { it('Should have a Backup controller', function() {
expect(scope.title).equal('Backup'); expect(scope.title).equal('Settings');
}); });
it('Backup controller #download', function() { it('Backup controller #download', function() {
@ -174,12 +174,17 @@ describe("Unit: Controllers", function() {
expect(scope.showAddressBook()).equal(true); expect(scope.showAddressBook()).equal(true);
}); });
it('should validate address', function() { it('should validate address with network', function() {
form.newaddress.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); form.newaddress.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
expect(form.newaddress.$invalid).to.equal(false); 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'); form.newaddress.$setViewValue('thisisaninvalidaddress');
expect(form.newaddress.$invalid).to.equal(true); expect(form.newaddress.$invalid).to.equal(true);
}); });
@ -194,7 +199,7 @@ describe("Unit: Controllers", function() {
}); });
it('should create a transaction proposal', function() { it('should create a transaction proposal', function() {
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
sendForm.amount.$setViewValue(1000); sendForm.amount.$setViewValue(1000);
var spy = sinon.spy(scope.wallet, 'createTx'); var spy = sinon.spy(scope.wallet, 'createTx');
@ -205,7 +210,7 @@ describe("Unit: Controllers", function() {
}); });
it('should create and send a transaction proposal', function() { it('should create and send a transaction proposal', function() {
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
sendForm.amount.$setViewValue(1000); sendForm.amount.$setViewValue(1000);
scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1; scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1;
@ -300,7 +305,7 @@ describe("Unit: Controllers", function() {
'<form name="form">' + '<form name="form">' +
'<input type="number" id="amount" name="amount" placeholder="Amount" ng-model="amount" min="0.0001" max="10000000" enough-amount required>' + '<input type="number" id="amount" name="amount" placeholder="Amount" ng-model="amount" min="0.0001" max="10000000" enough-amount required>' +
'</form>' '</form>'
); );
scope.model = { scope.model = {
amount: null 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; form = $scope.form;
})); }));
it('should validate', function() { it('should validate with network', function() {
config.networkName = 'testnet';
form.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); form.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
expect(form.address.$invalid).to.equal(false); 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'); form.address.$setViewValue('thisisaninvalidaddress');
expect(form.address.$invalid).to.equal(true); 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() { describe('noFractionNumber bits', function() {
beforeEach(function() { beforeEach(function() {
config.unitToSatoshi = 100; config.unitToSatoshi = 100;

View file

@ -181,3 +181,20 @@ describe("Unit: isMobile Service", function() {
isMobile.any().should.equal(true); 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();
}));
});