diff --git a/.jshint b/.jshint
new file mode 100644
index 000000000..219ea40f2
--- /dev/null
+++ b/.jshint
@@ -0,0 +1,15 @@
+{
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "freeze": true,
+ "indent": 2,
+ "newcap": true,
+ "quotmark": "single",
+ "maxdepth": 3,
+ "maxstatements": 15,
+ "maxlen": 80,
+ "eqnull": true,
+ "funcscope": true,
+ "node": true
+}
diff --git a/Gruntfile.js b/Gruntfile.js
index d6e444263..55eeb99f7 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -55,7 +55,11 @@ module.exports = function(grunt) {
scripts: {
files: [
'js/models/*.js',
- 'plugins/*.js',
+ 'js/util/*.js',
+ 'js/plugins/*.js',
+ 'js/*.js',
+ '!js/copayBundle.js',
+ '!js/copayMain.js'
],
tasks: ['shell:dev']
},
@@ -81,7 +85,7 @@ module.exports = function(grunt) {
tasks: ['shell:dev', 'concat:main']
},
test: {
- files: ['test/models/*.js'],
+ files: ['test/**/*.js'],
tasks: ['mochaTest']
}
},
@@ -114,7 +118,7 @@ module.exports = function(grunt) {
'js/shell.js', // shell must be loaded before moment due to the way moment loads in a commonjs env
'lib/moment/min/moment.min.js',
'lib/qrcode-generator/js/qrcode.js',
- 'lib/underscore/underscore.js',
+ 'lib/lodash/dist/lodash.js',
'lib/bitcore.js',
'lib/file-saver/FileSaver.js',
'lib/socket.io-client/socket.io.js',
@@ -198,7 +202,7 @@ module.exports = function(grunt) {
},
jsdoc: {
dist: {
- src: ['js/models/*.js', 'plugins/*.js'],
+ src: ['js/models/*.js', 'js/plugins/*.js'],
options: {
destination: 'doc',
configure: 'jsdoc.conf.json',
diff --git a/bower.json b/bower.json
index dc63b2c21..ab97d836e 100644
--- a/bower.json
+++ b/bower.json
@@ -22,9 +22,9 @@
"mousetrap": "1.4.6",
"zeroclipboard": "~1.3.5",
"ng-idle": "*",
- "underscore": "~1.7.0",
"inherits": "~0.0.1",
- "angular-load": "0.2.0"
+ "angular-load": "0.2.0",
+ "lodash": "~2.4.1"
},
"resolutions": {
"angular": "=1.2.19"
diff --git a/config.js b/config.js
index 35e3d9b14..cbf991b12 100644
--- a/config.js
+++ b/config.js
@@ -3,7 +3,7 @@ var defaultConfig = {
defaultLanguage: 'en',
// DEFAULT network (livenet or testnet)
networkName: 'livenet',
- logLevel: 'info',
+ logLevel: 'debug',
// wallet limits
@@ -15,10 +15,12 @@ var defaultConfig = {
// network layer config
network: {
testnet: {
- url: 'https://test-insight.bitpay.com:443'
+ url: 'https://test-insight.bitpay.com:443',
+ transports: ['polling'],
},
livenet: {
- url: 'https://insight.bitpay.com:443'
+ url: 'https://insight.bitpay.com:443',
+ transports: ['polling'],
},
},
@@ -39,7 +41,7 @@ var defaultConfig = {
},
// local encryption/security config
- passphrase: {
+ passphraseConfig: {
iterations: 100,
storageSalt: 'mjuBtGybi/4=',
},
@@ -52,14 +54,22 @@ var defaultConfig = {
verbose: 1,
plugins: {
- LocalStorage: true,
+ //LocalStorage: true,
+ EncryptedLocalStorage: true,
//GoogleDrive: true,
+ //InsightStorage: true
+ //EncryptedInsightStorage: true
+ },
+
+ EncryptedInsightStorage: {
+ url: 'https://test-insight.bitpay.com:443/api/email'
+ // url: 'http://localhost:3001/api/email'
},
GoogleDrive: {
home: 'copay',
- /*
+ /*
* This clientId was generated at:
* https://console.developers.google.com/project
* To run Copay with Google Drive at your domain you need
diff --git a/copay.js b/copay.js
index dc1ff87de..511a3dafd 100644
--- a/copay.js
+++ b/copay.js
@@ -3,19 +3,18 @@ module.exports.PublicKeyRing = require('./js/models/PublicKeyRing');
module.exports.TxProposal = require('./js/models/TxProposal');
module.exports.TxProposals = require('./js/models/TxProposals');
module.exports.PrivateKey = require('./js/models/PrivateKey');
-module.exports.Passphrase = require('./js/models/Passphrase');
module.exports.HDPath = require('./js/models/HDPath');
module.exports.HDParams = require('./js/models/HDParams');
+module.exports.crypto = require('./js/util/crypto');
// components
var Async = module.exports.Async = require('./js/models/Async');
var Insight = module.exports.Insight = require('./js/models/Insight');
-var Storage = module.exports.Storage = require('./js/models/Storage');
-module.exports.WalletFactory = require('./js/models/WalletFactory');
+module.exports.Identity = require('./js/models/Identity');
module.exports.Wallet = require('./js/models/Wallet');
-module.exports.WalletLock = require('./js/models/WalletLock');
+module.exports.Compatibility = require('./js/models/Compatibility');
module.exports.PluginManager = require('./js/models/PluginManager');
module.exports.version = require('./version').version;
module.exports.commitHash = require('./version').commitHash;
diff --git a/css/src/main.css b/css/src/main.css
index df285847f..9df1e9080 100644
--- a/css/src/main.css
+++ b/css/src/main.css
@@ -4,6 +4,8 @@
*
*/
+
+
@font-face {
font-family: 'Ubuntu';
font-style: normal;
@@ -53,19 +55,19 @@
}
::-webkit-input-placeholder {
- color: #4D657C;
+ color: #B7C2CD;
}
:-moz-placeholder { /* Firefox 18- */
- color: #4D657C;
+ color: #B7C2CD;
}
::-moz-placeholder { /* Firefox 19+ */
- color: #4D657C;
+ color: #B7C2CD;
}
:-ms-input-placeholder {
- color: #4D657C;
+ color: #B7C2CD;
}
#qr-canvas { display: none; }
@@ -109,7 +111,120 @@ body, html{
}
header {
- padding: 15px 20px 5px;
+ background-color: #1ABC9C;
+ overflow: hidden;
+}
+
+header .creation {
+ color: white;
+ background: red;
+ font-weight: bold;
+}
+
+header .alt-currency {
+ background: #16A085;
+}
+
+.alt-currency {
+ background: #2C3E50;
+ padding: 0.05rem 0.2rem;
+ border-radius: 2px;
+ font-size: 10px;
+}
+
+.head {
+ padding-left: 20px;
+ -moz-box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.10);
+ box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.10);
+ background-color: #FFF;
+ height: 62px;
+ position: fixed;
+ z-index: 10;
+ left: 250px;
+ right: 0;
+}
+
+.head .title h1 {
+ float: left;
+ padding: 12px 10px;
+ margin: 0;
+}
+
+.head .menu {
+ float: right;
+ position: relative;
+}
+
+.head .menu a.dropdown {
+ display: block;
+ height: 62px;
+ width: 140px;
+ padding: 22px 5px;
+ text-align: center;
+}
+
+.head .menu a.dropdown:hover,
+.head .menu a.dropdown.hover {
+ border-bottom: 1px solid #fff;
+}
+
+.head .menu ul {
+ position: absolute;
+ right: 0;
+ width: 160px;
+ list-style-type: none;
+ top: 61px;
+}
+
+.head .menu ul.hover {
+ background: #FFFFFF;
+ -moz-box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.25);
+ box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.25);
+}
+
+.head .menu ul li a {
+ display: block;
+ padding: 5px 10px;
+}
+
+.head .menu ul li a:hover {
+ background-color: #fff;
+}
+
+.col1 {
+ width: 56px;
+ float: left;
+}
+
+.col2 {
+ width: 164px;
+ float: left;
+}
+
+.col3 a {
+ font-size: 20px;
+ display: block;
+ height: 62px;
+ width: 30px;
+ float: right;
+ background-color: #23C9A9;
+ padding: 22px 6px;
+ color: #B6E9DF;
+}
+
+.col3 a.selected {
+ background-color: #213140;
+ color: #3C4E60;
+}
+
+.col3 a.selected:hover {
+ background-color: #213140;
+ color: #fff;
+}
+
+.col3 a:hover {
+ background-color: #16A085;
+ color: #fff;
}
.off-canvas-wrap, .inner-wrap{
@@ -128,6 +243,27 @@ input:-webkit-autofill, textarea:-webkit-autofill, select:-webkit-autofill, inpu
-webkit-box-shadow: 0 0 0px 1000px white inset;
}
+.side-nav.wallets .avatar-wallet{
+ background-color: #7A8C9E;
+ color: #213140;
+ padding: 0.35rem 0.7rem;
+ margin-top: 6px;
+ width: 35px;
+}
+
+.avatar-wallet {
+ font-size: 20px;
+ font-weight: 700;
+ margin-top: 10px;
+ margin-left: 10px;
+ margin-right: 8px;
+ padding: 0.5rem 0.8rem;
+ background-color: #fff;
+ color: #1ABC9C;
+ border-radius: 3px;
+ line-height: 24px;
+}
+
.status {
font-weight: 700;
-moz-box-shadow: inset 0px -1px 1px 0px rgba(159,47,34,0.30);
@@ -135,23 +271,13 @@ input:-webkit-autofill, textarea:-webkit-autofill, select:-webkit-autofill, inpu
color: #CA5649;
background-color: #E2CFD0;
position: absolute;
- margin-left: auto;
- margin-right: auto;
left: 250px;
right: 0;
- top: 0;
- width: 100%;
- padding: 20px 50px;
- height: 60px;
- z-index: 9999;
-}
-
-.join label,
-.open label,
-.setup label {
- font-size: 0.875rem;
- color: #fff;
- font-weight: 100;
+ top: 62px;
+ padding: 5px 0;
+ z-index: 9;
+ font-size: 12px;
+ text-align: center;
}
.setup .comment {
@@ -163,29 +289,6 @@ a:hover {
color: #2980b9;
}
-.open input,
-.join input,
-.setup input,
-.import input,
-.import textarea,
-.settings input {
- background: #2C3E50 !important;
- -moz-box-shadow: inset 0px 0px 3px 0px rgba(0,0,0,0.10) !important;
- box-shadow: inset 0px 0px 3px 0px rgba(0,0,0,0.10) !important;
- border: 0 !important;
- color: #fff !important;
-}
-
-.open select,
-.join select,
-.setup select,
-.import select,
-.settings select {
- background: #2C3E50 !important;
- border: 0 !important;
- color: #fff !important;
-}
-
.page, .main {
height:100%;
overflow-y: auto;
@@ -195,7 +298,6 @@ a:hover {
.sidebar {
height:100%;
- overflow-y: auto;
overflow-x: none;
}
@@ -207,10 +309,6 @@ a:hover {
line-height: 24px;
}
-.sidebar a {
- color: #fff;
-}
-
.button.small.side-bar {
padding: 0rem 0.4rem;
}
@@ -231,50 +329,29 @@ a:hover {
.main {
margin-left: 250px;
- padding: 1.5rem;
+ padding: 80px 1.5rem;
background-color: #F8F8FB;
}
-.home, .open, .join, .waiting-copayers, .setup, .import, .settings {
- margin-top: 15%;
- color: #fff;
-}
-
-.import fieldset, .settings fieldset {
- border: 1px solid #3C5269;
-}
-
-.import fieldset legend, .settings fieldset legend {
- background: transparent;
- color: #fff;
- font-weight: normal;
-}
-
-.import input[type="file"],
-.import label,
-.import label small,
-.settings label,
-.settings label small {
- color: #fff;
-}
-
.logo-setup {
text-align: center;
- margin-top: 9%;
-}
-
-.setup .logo-setup, .join .logo-setup {
- margin-top: 16%;
+ padding: 2rem 0;
+ color: #fff;
}
.box-setup {
- padding: 20px 30px;
- background: #34495E;
+ padding: 1.3rem;
+ border-radius: 2px;
+ background: #FFFFFF;
+ -moz-box-shadow: 1px 1px 0px 0px #213140;
+ box-shadow: 1px 1px 0px 0px #213140;
}
-.box-setup label small.has-error {
- font-size: 11px;
- color: #FFA59B;
+.box-setup-footer {
+ overflow: hidden;
+ padding: 1rem 0 0;
+ border-top: 1px solid #E5E7EA;
+ font-size: 12px;
}
.last-transactions {
@@ -381,29 +458,38 @@ table.last-transactions-content {
margin-bottom: 10px;
}
-.button-setup a {
+a.button-setup.add-wallet {
+ opacity: .5;
+ margin: 1rem auto;
+ width: 125px;
+ font-size: 14px;
+ padding: .3rem 0.7rem;
+ color: #fff;
+}
+
+a.button-setup.add-wallet:hover {
+ opacity: 1;
+}
+
+a.button-setup {
+ border-radius: 3px;
+ border: 1px solid #B7C2CE;
display: block;
- padding: 20px 30px;
- background: #34495E;
- margin-bottom: 20px;
- font-weight: 100;
- font-size: 24px;
+ padding: 0.5rem;
+ background: transparent;
}
.button-setup a:hover {
background: #3C4E60;
}
-.footer-setup {
- overflow: hidden;
-}
-
.dn {display: none;}
.pr {position: relative;}
.pa {position: absolute;}
.m0 {margin: 0;}
.p0 {padding: 0 !important;}
.db {display: block;}
+.size-10 { font-size: 10px; }
.size-12 { font-size: 12px; }
.size-14 { font-size: 14px; }
.size-16 { font-size: 16px; }
@@ -448,7 +534,7 @@ table.last-transactions-content {
.br100 {border-radius: 100%;}
.lh {line-height: 0;}
.oh {overflow:hidden;}
-.lh {line-height: 0;}
+.vm {vertical-align: middle;}
.small {
font-size: 60%;
@@ -495,8 +581,18 @@ table.last-transactions-content {
}
.name-wallet {
- font-size: 16px;
- line-height: 16px;
+ font-size: 14px;
+ width: 72%;
+ float: left;
+ color: #fff;
+}
+
+.name-wallet i {
+ color: #B6E9DF;
+}
+
+.name-wallet i:hover {
+ color: #fff;
}
.box-livenet {
@@ -507,7 +603,7 @@ table.last-transactions-content {
.founds {
font-weight: 100;
- color: #7A8C9E;
+ color: #B6E9DF;
}
.hidden {
@@ -685,6 +781,10 @@ input[type=number]::-webkit-outer-spin-button {
color: #1ABC9C;
}
+.bg-success {
+ background-color: #1ABC9C;
+}
+
.label.success {
background-color: #1ABC9C;
}
@@ -695,6 +795,10 @@ input[type=number]::-webkit-outer-spin-button {
font-weight: 700;
}
+.bg-alert {
+ background-color: #C0392A;
+}
+
.dr-notification-text {
font-size: 12px;
line-height: 120%;
@@ -716,15 +820,10 @@ ul.pagination li.current a:hover, ul.pagination li.current a:focus {
}
.tooltip {
- background: #1ABC9C;
+ background-color: #1ABC9C;
color: #fff;
- font-weight: normal;
- font-size: 14px;
- padding: 3px 5px;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
- border: 1px solid #16A085;
+ font-size: 12px;
+ border-color: #16A085;
}
.tooltip>.nub {
@@ -751,9 +850,25 @@ ul.pagination li.current a:hover, ul.pagination li.current a:focus {
background-size: 130px 51px;
}
+input {
+ border-radius: 2px;
+ background: #EDEDED;
+ -moz-box-shadow: 0px 0px 0px 0px rgba(255,255,255,0.09), inset 1px 1px 0px 0px rgba(0,0,0,0.05);
+ box-shadow: 0px 0px 0px 0px rgba(255,255,255,0.09), inset 1px 1px 0px 0px rgba(0,0,0,0.05);
+ color: #2C3E42;
+ padding: 1.2rem 0.7rem;
+ margin-bottom: 1.5rem;
+ border: 0;
+}
+
button.radius, .button.radius {
- -webkit-border-radius: 5px;
- border-radius: 5px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+
+label small.has-error {
+ font-size: 11px;
+ color: #FFA59B;
}
/* SECONDARY */
@@ -767,8 +882,10 @@ input[type='submit']
button.secondary,
.button.secondary {
- background-color: #4A90E2;
color: #fff;
+ background: #008CC1;
+ -moz-box-shadow: 1px 1px 0px 0px #10769D;
+ box-shadow: 1px 1px 0px 0px #10769D;
}
button.secondary:hover,
button.secondary:focus,
@@ -798,7 +915,10 @@ button.primary,
.button.primary {
background-color: #1ABC9C;
color: #fff;
- border-radius: 0;
+ border-radius: 2px;
+ border-radius: 2px;
+ -moz-box-shadow: 1px 1px 0px 0px #16A085;
+ box-shadow: 1px 1px 0px 0px #16A085;
}
button.primary:hover,
button.primary:focus,
@@ -819,6 +939,8 @@ button[disabled].primary:focus,
.button.disabled.primary:focus,
.button[disabled].primary:hover,
.button[disabled].primary:focus {
+ -moz-box-shadow: 1px 1px 0px 0px #687D80;
+ box-shadow: 1px 1px 0px 0px #687D80;
background-color: #95a5a6;
color: #E6E6E6;
}
@@ -886,6 +1008,8 @@ button[disabled].white:focus,
/* BLACK */
button.black,
.button.black {
+ -moz-box-shadow: 1px 1px 0px 0px #1B2937;
+ box-shadow: 1px 1px 0px 0px #1B2937;
background-color: #2C3E50;
color: #fff;
}
@@ -927,12 +1051,57 @@ button.gray:focus,
color: #2C3E50;
}
+.button, button {
+ text-transform: uppercase;
+}
+
+.side-nav.wallets {
+ background-color: #213140 ;
+ padding: 1.2rem 0.7rem;
+ border-bottom: 1px solid #3A4E61;
+ overflow-y: auto;
+ height: 160px;
+}
+
+.side-nav.wallets.medium {
+ height: 280px;
+}
+
+.side-nav.wallets.large {
+ height: 380px;
+}
+
+.side-nav.wallets a.wallet-item,
+.side-nav.wallets a.wallet-item:hover {
+ color: #7A8C9E;
+ line-height: 20px;
+}
+
+.side-nav.wallets .type-wallet {
+ color: #AAB1B9;
+}
+
+.side-nav .wallet-item {
+ padding: 4px 0;
+}
+.side-nav li.nav-item {
+ overflow: hidden;
+}
+
+.side-nav li.nav-item:hover {
+ background-color: #3C4E60;
+ overflow: hidden;
+ border-radius: 3px;
+}
+
+
.side-nav {padding: 0;}
.side-nav li {
font-size: 16px;
line-height: 40px;
font-weight: 100;
+ margin-bottom: 10px;
}
.side-nav li.active>a:first-child:not(.button) {
@@ -960,6 +1129,15 @@ button.gray:focus,
background-color: #3C4E60;
}
+.sidebar-footer {
+ width: 100%;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ padding-bottom: 10px;
+ overflow-y: hidden;
+}
+
.addresses ul {
margin-left: 0;
}
@@ -1046,12 +1224,12 @@ input.ng-invalid-match, input.ng-invalid-match:focus {
.copayers {
width: 100%;
background-color: #213140;
- position: absolute;
- bottom: 0;
- left: 0;
+ /* position: absolute; */
+ /* bottom: 0; */
+ /* left: 0; */
padding: 20px;
border-top: 1px solid #475065;
- overflow-y: hidden;
+ /* overflow-y: hidden; */
}
.copayers i {
@@ -1070,7 +1248,6 @@ input.ng-invalid-match, input.ng-invalid-match:focus {
.text-white {color: #fff;}
.text-warning {color: #CA5649;}
-.footer-setup a.text-gray:hover {color: #fff;}
a.text-gray:hover {color: #2C3E50;}
a.text-black:hover {color: #213140;}
a.text-primary:hover {color: #50E3C2;}
@@ -1094,8 +1271,7 @@ a.text-warning:hover {color: #FD7262;}
text-align: center;
background-color: #1ABC9C;
width: 100%;
- position: absolute;
- top: 40%;
+ margin-top: 15%;
}
.box-setup .panel {
@@ -1108,6 +1284,12 @@ a.text-warning:hover {color: #FD7262;}
color: #fff;
}
+.box-setup h1 {
+ font-size: 16px;
+ text-transform: uppercase;
+ text-align: center;
+}
+
.joyride-tip-guide {
width: 150px;
background: #213140;
@@ -1147,16 +1329,21 @@ a.text-warning:hover {color: #FD7262;}
}
.panel .secret {
- line-height: 1.3rem;
- padding-top: 4rem;
float: left;
margin-left: 2rem;
overflow-wrap: break-word;
word-wrap: break-word;
- width: 55%;
text-align: left;
}
+.panel {
+ border-radius: 3px;
+ background: #FFFFFF;
+ -moz-box-shadow: 1px 1px 0px 0px rgba(32,48,64,0.10);
+ box-shadow: 1px 1px 0px 0px rgba(32,48,64,0.10);
+ border: none;
+}
+
/**** Copy to clipboard ****/
.btn-copy {
@@ -1184,10 +1371,6 @@ a.text-warning:hover {color: #FD7262;}
display: none;
}
-fieldset legend {
- background: transparent;
-}
-
@media only screen and (min-width: 40.063em) {
dialog.tiny, .reveal-modal.tiny {
width: 50%;
@@ -1195,43 +1378,16 @@ fieldset legend {
}
}
-@media (max-height: 610px) {
- .copayer-list {
+@media (max-height: 590px) {
+ .side-nav.wallets {
+ height: 180px !important;
+ }
+}
+
+@media (max-height: 380px) {
+ .sidebar-footer {
display: none;
}
-
- .copayer-list-small-height {
- display: block;
- }
-
- .sidebar .copayer-list-small-height {
- list-style-type: none;
- padding:0; margin:0;
- }
-
- .sidebar .copayer-list-small-height li {
- margin-top: 15px;
- font-weight: 100;
- font-size: 12px;
- color: #C9C9C9;
- }
-
- .sidebar .copayer-list-small-height img {
- width: 30px;
- height: 30px;
- }
-
- .copayers {
- padding: 10px 20px;
- }
-
- .side-bar h3 {
- margin-top: 0;
- }
-
- .side-nav li {
- line-height: 30px;
- }
}
.wide-page {
@@ -1246,4 +1402,6 @@ fieldset legend {
}
+
+
/*-----------------------------------------------------------------*/
diff --git a/css/src/mobile.css b/css/src/mobile.css
index b36b42fbe..a51ae68c0 100644
--- a/css/src/mobile.css
+++ b/css/src/mobile.css
@@ -14,10 +14,7 @@
.status {
left: 0;
- top: 45px;
- font-size: 13px;
- font-weight: 700;
- height: 55px;
+ top: 40px;
}
.logo-setup {
@@ -42,6 +39,7 @@
margin-left: 0;
margin-bottom: -40px;
padding-bottom: 60px;
+ padding-top: 20px;
}
.tab-bar {
@@ -187,5 +185,14 @@
margin-bottom: 50px;
}
+ .founds {
+ color: #8597A7;
+ }
+
+ .side-nav.wallets {
+ padding: 0;
+ height: auto;
+ }
+
}
diff --git a/css/style.css b/css/style.css
new file mode 100755
index 000000000..3ed5344a0
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,70 @@
+@font-face {
+ font-family: 'icomoon';
+ src:url("../font/icomoon.eot?-5b2xva");
+ src:url("../font/icomoon.eot?#iefix-5b2xva") format('embedded-opentype'),
+ url("../font/icomoon.woff?-5b2xva") format('woff'),
+ url("../font/icomoon.ttf?-5b2xva") format('truetype'),
+ url("../font/icomoon.svg?-5b2xva#icomoon") format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+[class^="icon-"], [class*=" icon-"] {
+ font-family: 'icomoon' !important;
+ speak: none;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+
+ /* Better Font Rendering =========== */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-arrow-left:before {
+ content: "\e600";
+}
+.icon-arrow-down:before {
+ content: "\e601";
+}
+.icon-arrow-up:before {
+ content: "\e602";
+}
+.icon-arrow-right:before {
+ content: "\e603";
+}
+.icon-arrow-left2:before {
+ content: "\e604";
+}
+.icon-arrow-down2:before {
+ content: "\e605";
+}
+.icon-arrow-up2:before {
+ content: "\e606";
+}
+.icon-arrow-right2:before {
+ content: "\e607";
+}
+.icon-arrow-left3:before {
+ content: "\e608";
+}
+.icon-arrow-down3:before {
+ content: "\e609";
+}
+.icon-arrow-up3:before {
+ content: "\e60a";
+}
+.icon-arrow-right3:before {
+ content: "\e60b";
+}
+.icon-arrow-left4:before {
+ content: "\e60c";
+}
+.icon-arrow-down4:before {
+ content: "\e60d";
+}
+.icon-arrow-up4:before {
+ content: "\e60e";
+}
diff --git a/font/icomoon.eot b/font/icomoon.eot
new file mode 100755
index 000000000..b8ff229bc
Binary files /dev/null and b/font/icomoon.eot differ
diff --git a/font/icomoon.svg b/font/icomoon.svg
new file mode 100755
index 000000000..5504c7b77
--- /dev/null
+++ b/font/icomoon.svg
@@ -0,0 +1,25 @@
+
+
+
\ No newline at end of file
diff --git a/font/icomoon.ttf b/font/icomoon.ttf
new file mode 100755
index 000000000..3ac922071
Binary files /dev/null and b/font/icomoon.ttf differ
diff --git a/font/icomoon.woff b/font/icomoon.woff
new file mode 100755
index 000000000..86fcfb928
Binary files /dev/null and b/font/icomoon.woff differ
diff --git a/index.html b/index.html
index b7b0c4eb2..730ed48d9 100644
--- a/index.html
+++ b/index.html
@@ -28,7 +28,7 @@
-
+
Network Error. Attempting to reconnect...
+ class="sidebar"
+ ng-if="$root.iden">
-
+
+
+
-
+
+
diff --git a/js/app.js b/js/app.js
index 5209dea82..7c5c6dc87 100644
--- a/js/app.js
+++ b/js/app.js
@@ -1,7 +1,7 @@
'use strict';
var copay = require('copay');
-var _ = require('underscore');
+var _ = require('lodash');
var config = defaultConfig;
var localConfig = JSON.parse(localStorage.getItem('config'));
var defaults = JSON.parse(JSON.stringify(defaultConfig));
diff --git a/js/controllers/addresses.js b/js/controllers/addresses.js
index 3432ef8c5..3c735212f 100644
--- a/js/controllers/addresses.js
+++ b/js/controllers/addresses.js
@@ -2,11 +2,15 @@
angular.module('copayApp.controllers').controller('AddressesController',
function($scope, $rootScope, $timeout, $modal, controllerUtils) {
+ controllerUtils.redirIfNotComplete();
+
+ $rootScope.title = 'Addresses';
+
$scope.loading = false;
$scope.showAll = false;
- var w = $rootScope.wallet;
$scope.newAddr = function() {
+ var w = $rootScope.wallet;
$scope.loading = true;
w.generateAddress(null, function() {
$timeout(function() {
@@ -80,6 +84,7 @@ angular.module('copayApp.controllers').controller('AddressesController',
for (var i = 0; i < addrInfos.length; i++) {
var addrinfo = addrInfos[i];
$scope.addresses.push({
+ 'index': i,
'address': addrinfo.addressStr,
'balance': $rootScope.balanceByAddr ? $rootScope.balanceByAddr[addrinfo.addressStr] : 0,
'isChange': addrinfo.isChange,
diff --git a/js/controllers/copayers.js b/js/controllers/copayers.js
index 43721afb7..fecc482c9 100644
--- a/js/controllers/copayers.js
+++ b/js/controllers/copayers.js
@@ -1,53 +1,18 @@
'use strict';
angular.module('copayApp.controllers').controller('CopayersController',
- function($scope, $rootScope, $location, backupService, walletFactory, controllerUtils) {
- $scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
- $scope.hideAdv = true;
-
- $scope.skipBackup = function() {
- var w = $rootScope.wallet;
- w.setBackupReady(true);
- };
-
- $scope.backup = function() {
- var w = $rootScope.wallet;
- if ($scope.isSafari) {
- $scope.viewBackup(w);
- } else {
- w.setBackupReady();
- $scope.downloadBackup(w);
- }
- };
-
- $scope.downloadBackup = function(w) {
- backupService.download(w);
- };
-
- $scope.viewBackup = function(w) {
- $scope.backupPlainText = backupService.getBackup(w);
- $scope.hideViewBackup = true;
- };
+ function($scope, $rootScope, $location, controllerUtils) {
+ $rootScope.title = 'Copayers';
$scope.goToWallet = function() {
controllerUtils.updateAddressList();
$location.path('/receive');
};
- $scope.deleteWallet = function() {
- var w = $rootScope.wallet;
- walletFactory.delete(w.id, function() {
- controllerUtils.logout();
- });
- };
-
$scope.copayersList = function() {
- $scope.copayers = $rootScope.wallet.getRegisteredPeerIds();
+ if ($rootScope.wallet) {
+ $scope.copayers = $rootScope.wallet.getRegisteredPeerIds();
+ }
return $scope.copayers;
}
-
- $scope.isBackupReady = function(copayer) {
- return $rootScope.wallet.publicKeyRing.isBackupReady(copayer.copayerId);
- }
-
});
diff --git a/js/controllers/create.js b/js/controllers/create.js
index 77059bb92..0f7cd7593 100644
--- a/js/controllers/create.js
+++ b/js/controllers/create.js
@@ -1,8 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('CreateController',
- function($scope, $rootScope, $location, $timeout, walletFactory, controllerUtils, Passphrase, backupService, notification, defaults) {
- controllerUtils.redirIfLogged();
+ function($scope, $rootScope, $location, $timeout, controllerUtils, backupService, notification, defaults) {
$rootScope.fromSetup = true;
$scope.loading = false;
@@ -10,7 +9,7 @@ angular.module('copayApp.controllers').controller('CreateController',
$scope.isMobile = !!window.cordova;
$scope.hideAdv = true;
$scope.networkName = config.networkName;
- $scope.networkUrl = config.network[$scope.networkName].url;
+ $rootScope.title = 'Create a wallet';
// ng-repeat defined number of times instead of repeating over array?
$scope.getNumber = function(num) {
@@ -36,7 +35,7 @@ angular.module('copayApp.controllers').controller('CreateController',
$scope.networkUrl = config.network[$scope.networkName].url;
});
- $scope.showNetwork = function(){
+ $scope.showNetwork = function() {
return $scope.networkUrl != defaults.network.livenet.url && $scope.networkUrl != defaults.network.testnet.url;
};
@@ -46,26 +45,17 @@ angular.module('copayApp.controllers').controller('CreateController',
return;
}
$scope.loading = true;
- Passphrase.getBase64Async($scope.walletPassword, function(passphrase) {
- var opts = {
- requiredCopayers: $scope.requiredCopayers,
- totalCopayers: $scope.totalCopayers,
- name: $scope.walletName,
- nickname: $scope.myNickname,
- passphrase: passphrase,
- privateKeyHex: $scope.private,
- networkName: $scope.networkName,
- };
- walletFactory.create(opts, function(err, w) {
- controllerUtils.startNetwork(w, $scope);
- });
+ var opts = {
+ requiredCopayers: $scope.requiredCopayers,
+ totalCopayers: $scope.totalCopayers,
+ name: $scope.walletName,
+ privateKeyHex: $scope.private,
+ networkName: $scope.networkName,
+ };
+ $rootScope.iden.createWallet(opts, function(err, w) {
+ $scope.loading = false;
+ controllerUtils.installWalletHandlers($scope, w);
+ controllerUtils.setFocusedWallet(w);
});
};
-
- $scope.isSetupWalletPage = 0;
-
- $scope.setupWallet = function() {
- $scope.isSetupWalletPage = !$scope.isSetupWalletPage;
- };
-
});
diff --git a/js/controllers/createProfile.js b/js/controllers/createProfile.js
new file mode 100644
index 000000000..34997f5a7
--- /dev/null
+++ b/js/controllers/createProfile.js
@@ -0,0 +1,16 @@
+'use strict';
+
+angular.module('copayApp.controllers').controller('CreateProfileController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService) {
+ controllerUtils.redirIfLogged();
+ $scope.retreiving = false;
+
+ $scope.createProfile = function(form) {
+ if (form && form.$invalid) {
+ notification.error('Error', 'Please enter the required fields');
+ return;
+ }
+ $scope.loading = true;
+ identityService.create($scope, form);
+ }
+
+});
diff --git a/js/controllers/head.js b/js/controllers/head.js
new file mode 100644
index 000000000..6593ee32d
--- /dev/null
+++ b/js/controllers/head.js
@@ -0,0 +1,51 @@
+'use strict';
+
+angular.module('copayApp.controllers').controller('HeadController', function($scope, $rootScope, $filter, notification, controllerUtils) {
+ $scope.username = $rootScope.iden.getName();
+ $scope.hoverMenu = false;
+
+ $scope.hoverIn = function() {
+ this.hoverMenu = true;
+ };
+
+ $scope.hoverOut = function() {
+ this.hoverMenu = false;
+ };
+
+ $scope.signout = function() {
+ controllerUtils.logout();
+ };
+
+
+ // Ensures a graceful disconnect
+ window.onbeforeunload = function() {
+ controllerUtils.logout();
+ };
+
+ $scope.$on('$destroy', function() {
+ window.onbeforeunload = undefined;
+ });
+
+ if ($rootScope.wallet) {
+ $scope.$on('$idleWarn', function(a, countdown) {
+ if (!(countdown % 5))
+ notification.warning('Session will be closed', $filter('translate')('Your session is about to expire due to inactivity in') + ' ' + countdown + ' ' + $filter('translate')('seconds'));
+ });
+
+ $scope.$on('$idleTimeout', function() {
+ $scope.signout();
+ notification.warning('Session closed', 'Session closed because a long time of inactivity');
+ });
+ $scope.$on('$keepalive', function() {
+ if ($rootScope.wallet) {
+ $rootScope.wallet.keepAlive();
+ }
+ });
+ $rootScope.$watch('title', function(newTitle, oldTitle) {
+ $scope.title = newTitle;
+ });
+ $rootScope.$on('signout', function() {
+ controllerUtils.logout();
+ });
+ }
+});
diff --git a/js/controllers/home.js b/js/controllers/home.js
index 23119ded1..2f0a87b12 100644
--- a/js/controllers/home.js
+++ b/js/controllers/home.js
@@ -1,12 +1,15 @@
'use strict';
-angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, walletFactory, notification, controllerUtils) {
-
+angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService) {
controllerUtils.redirIfLogged();
+ $scope.retreiving = false;
- $scope.retreiving = true;
- walletFactory.getWallets(function(err,ret) {
- $scope.retreiving = false;
- $scope.hasWallets = (ret && ret.length > 0) ? true : false;
- });
+ $scope.openProfile = function(form) {
+ if (form && form.$invalid) {
+ notification.error('Error', 'Please enter the required fields');
+ return;
+ }
+ $scope.loading = true;
+ identityService.open($scope, form);
+ }
});
diff --git a/js/controllers/import.js b/js/controllers/import.js
index 4c10279e4..b6e17f8c0 100644
--- a/js/controllers/import.js
+++ b/js/controllers/import.js
@@ -1,10 +1,9 @@
'use strict';
angular.module('copayApp.controllers').controller('ImportController',
- function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, notification, isMobile) {
- controllerUtils.redirIfLogged();
+ function($scope, $rootScope, $location, controllerUtils, notification, isMobile, Compatibility) {
- $scope.title = 'Import a backup';
+ $rootScope.title = 'Import a backup';
$scope.importStatus = 'Importing wallet - Reading backup...';
$scope.hideAdv = true;
$scope.is_iOS = isMobile.iOS();
@@ -17,35 +16,27 @@ angular.module('copayApp.controllers').controller('ImportController',
}
var _importBackup = function(encryptedObj) {
- Passphrase.getBase64Async($scope.password, function(passphrase) {
- updateStatus('Importing wallet - Setting things up...');
- var w, errMsg;
+ var password = $scope.password;
+ updateStatus('Importing wallet - Setting things up...');
+ var skipFields = [];
+ if ($scope.skipPublicKeyRing)
+ skipFields.push('publicKeyRing');
- var skipFields = [];
- if ($scope.skipPublicKeyRing)
- skipFields.push('publicKeyRing');
-
- if ($scope.skipTxProposals)
- skipFields.push('txProposals');
-
- // try to import encrypted wallet with passphrase
- try {
- w = walletFactory.import(encryptedObj, passphrase, skipFields);
- } catch (e) {
- errMsg = e.message;
- }
+ if ($scope.skipTxProposals)
+ skipFields.push('txProposals');
+ $rootScope.iden.importEncryptedWallet(encryptedObj, password, skipFields, function(err, w) {
if (!w) {
$scope.loading = false;
- notification.error('Error', errMsg || 'Wrong password');
+ notification.error('Error', err || 'Wrong password');
$rootScope.$digest();
return;
}
// if wallet was never used, we're done
if (!w.isReady()) {
- $rootScope.wallet = w;
- controllerUtils.startNetwork($rootScope.wallet, $scope);
+ controllerUtils.installWalletHandlers($scope, w);
+ controllerUtils.setFocusedWallet(w);
return;
}
@@ -56,10 +47,9 @@ angular.module('copayApp.controllers').controller('ImportController',
$scope.loading = false;
notification.error('Error', 'Error updating indexes: ' + err);
}
- $rootScope.wallet = w;
- controllerUtils.startNetwork($rootScope.wallet, $scope);
+ controllerUtils.installWalletHandlers($scope, w);
+ controllerUtils.setFocusedWallet(w);
});
-
});
};
@@ -75,7 +65,17 @@ angular.module('copayApp.controllers').controller('ImportController',
reader.onloadend = function(evt) {
if (evt.target.readyState == FileReader.DONE) { // DONE == 2
var encryptedObj = evt.target.result;
- _importBackup(encryptedObj);
+ Compatibility.importEncryptedWallet($rootScope.iden, encryptedObj, $scope.password, {},
+ function(err, wallet){
+ if (err) {
+ notification.error('Error', 'Could not read wallet. Please check your password');
+ } else {
+ controllerUtils.installWalletHandlers($scope, wallet);
+ controllerUtils.setFocusedWallet(wallet);
+ return;
+ }
+ }
+ );
}
};
};
@@ -104,7 +104,22 @@ angular.module('copayApp.controllers').controller('ImportController',
reader.readAsBinaryString(backupFile);
}
else {
- _importBackup(backupText);
+ Compatibility.importEncryptedWallet($rootScope.iden, backupText, $scope.password, {},
+ function(err, wallet){
+ if (err) {
+ notification.error('Error', 'Could not read wallet. Please check your password');
+ } else {
+ controllerUtils.installWalletHandlers($scope, wallet);
+ controllerUtils.setFocusedWallet(wallet);
+ return;
+ }
+ }
+ );
+ try {
+ _importBackup(backupText);
+ } catch(e) {
+ Compatibility.importEncryptedWallet(backupText, $scope.password, $scope.skipPublicKeyRing, $scope.skipTxProposals);
+ }
}
};
});
diff --git a/js/controllers/join.js b/js/controllers/join.js
index fffd2e86b..392c43701 100644
--- a/js/controllers/join.js
+++ b/js/controllers/join.js
@@ -1,11 +1,11 @@
'use strict';
angular.module('copayApp.controllers').controller('JoinController',
- function($scope, $rootScope, $timeout, walletFactory, controllerUtils, Passphrase, notification) {
- controllerUtils.redirIfLogged();
+ function($scope, $rootScope, $timeout, controllerUtils, notification) {
$rootScope.fromSetup = false;
$scope.loading = false;
$scope.isMobile = !!window.cordova;
+ $rootScope.title = 'Join a wallet';
// QR code Scanner
var cameraInput;
@@ -120,32 +120,30 @@ angular.module('copayApp.controllers').controller('JoinController',
$scope.loading = true;
- Passphrase.getBase64Async($scope.joinPassword, function(passphrase) {
- walletFactory.joinCreateSession({
- secret: $scope.connectionId,
- nickname: $scope.nickname,
- passphrase: passphrase,
- privateHex: $scope.private,
- }, function(err, w) {
- $scope.loading = false;
- if (err || !w) {
- if (err === 'joinError')
- notification.error('Fatal error connecting to Insight server');
- else if (err === 'walletFull')
- notification.error('The wallet is full');
- else if (err === 'badNetwork')
- notification.error('Network Error', 'Wallet network configuration missmatch');
- else if (err === 'badSecret')
- notification.error('Bad secret', 'The secret string you entered is invalid');
- else if (err === 'connectionError')
- notification.error('Networking Error', 'Could not connect to the Insight server. Check your settings and network configuration');
- else
- notification.error('Unknown error');
- controllerUtils.onErrorDigest();
- } else {
- controllerUtils.startNetwork(w, $scope);
+ $rootScope.iden.joinWallet({
+ secret: $scope.connectionId,
+ nickname: $scope.nickname,
+ privateHex: $scope.private,
+ }, function(err, w) {
+
+ $scope.loading = false;
+ if (err || !w) {
+ if (err === 'joinError')
+ notification.error('Fatal error connecting to Insight server');
+ else if (err === 'walletFull')
+ notification.error('The wallet is full');
+ else if (err === 'badNetwork')
+ notification.error('Network Error', 'Wallet network configuration missmatch');
+ else if (err === 'badSecret')
+ notification.error('Bad secret', 'The secret string you entered is invalid');
+ else {
+ notification.error('Error', err.message || err);
}
- });
+ controllerUtils.onErrorDigest();
+ } else {
+ controllerUtils.installWalletHandlers($scope, w);
+ controllerUtils.setFocusedWallet(w);
+ }
});
}
});
diff --git a/js/controllers/manage.js b/js/controllers/manage.js
new file mode 100644
index 000000000..97c53a0bc
--- /dev/null
+++ b/js/controllers/manage.js
@@ -0,0 +1,15 @@
+'use strict';
+angular.module('copayApp.controllers').controller('ManageController', function($scope, $rootScope, $location, controllerUtils, backupService) {
+ $scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
+
+ $rootScope.title = 'Manage wallets';
+
+ $scope.downloadBackup = function() {
+ backupService.profileDownload($rootScope.iden);
+ };
+
+ $scope.viewBackup = function() {
+ $scope.backupPlainText = backupService.profileEncrypted($rootScope.iden);
+ $scope.hideViewBackup = true;
+ };
+});
diff --git a/js/controllers/more.js b/js/controllers/more.js
index 6b4fb4f51..210fa4c2c 100644
--- a/js/controllers/more.js
+++ b/js/controllers/more.js
@@ -1,10 +1,13 @@
'use strict';
angular.module('copayApp.controllers').controller('MoreController',
- function($scope, $rootScope, $location, $filter, backupService, walletFactory, controllerUtils, notification, rateService) {
+ function($scope, $rootScope, $location, $filter, backupService, controllerUtils, notification, rateService) {
+ controllerUtils.redirIfNotComplete();
var w = $rootScope.wallet;
$scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
+ $rootScope.title = 'Settings';
+
$scope.unitOpts = [{
name: 'Satoshis (100,000,000 satoshis = 1BTC)',
shortName: 'SAT',
@@ -50,6 +53,7 @@ angular.module('copayApp.controllers').controller('MoreController',
break;
}
}
+
$scope.save = function() {
w.changeSettings({
unitName: $scope.selectedUnit.shortName,
@@ -72,18 +76,16 @@ angular.module('copayApp.controllers').controller('MoreController',
}
$scope.downloadBackup = function() {
- backupService.download(w);
+ backupService.walletDownload(w);
}
$scope.viewBackup = function() {
- $scope.backupPlainText = backupService.getBackup(w);
+ $scope.backupPlainText = backupService.walletEncrypted(w);
$scope.hideViewBackup = true;
};
$scope.deleteWallet = function() {
- walletFactory.delete(w.id, function() {
- controllerUtils.logout();
- });
+ controllerUtils.deleteWallet($scope);
};
$scope.purge = function(deleteAll) {
diff --git a/js/controllers/open.js b/js/controllers/open.js
deleted file mode 100644
index e14663319..000000000
--- a/js/controllers/open.js
+++ /dev/null
@@ -1,64 +0,0 @@
-'use strict';
-
-angular.module('copayApp.controllers').controller('OpenController', function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, notification) {
- controllerUtils.redirIfLogged();
-
- if ($rootScope.pendingPayment) {
- notification.info('Login Required', 'Please open wallet to complete payment');
- }
-
- var cmp = function(o1, o2) {
- var v1 = o1.show.toLowerCase(),
- v2 = o2.show.toLowerCase();
- return v1 > v2 ? 1 : (v1 < v2) ? -1 : 0;
- };
- $rootScope.fromSetup = false;
- $scope.loading = false;
- $scope.retreiving = true;
-
- walletFactory.getWallets(function(err, wallets) {
-
- if (err || !wallets || !wallets.length) {
- $location.path('/');
- } else {
- $scope.retreiving = false;
- $scope.wallets = wallets.sort(cmp);
- var lastOpened = _.findWhere($scope.wallets, {
- lastOpened: true
- });
- $scope.selectedWalletId = lastOpened ? lastOpened.id : ($scope.wallets[0] && $scope.wallets[0].id);
-
- setTimeout(function() {
- $rootScope.$digest();
- }, 0);
- }
- });
-
- $scope.openPassword = '';
- $scope.isMobile = !!window.cordova;
-
- $scope.open = function(form) {
- if (form && form.$invalid) {
- notification.error('Error', 'Please enter the required fields');
- return;
- }
-
- $scope.loading = true;
- var password = form.openPassword.$modelValue;
-
- Passphrase.getBase64Async(password, function(passphrase) {
- var w, errMsg;
- walletFactory.open($scope.selectedWalletId, passphrase, function(err, w) {
- if (!w) {
- $scope.loading = false;
- notification.error('Error', err.errMsg || 'Wrong password');
- $rootScope.$digest();
- } else {
- $rootScope.updatingBalance = true;
- controllerUtils.startNetwork(w, $scope);
- }
- });
- });
- };
-
-});
diff --git a/js/controllers/send.js b/js/controllers/send.js
index b670cceff..4759efedf 100644
--- a/js/controllers/send.js
+++ b/js/controllers/send.js
@@ -4,11 +4,13 @@ var preconditions = require('preconditions').singleton();
angular.module('copayApp.controllers').controller('SendController',
function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils, rateService) {
+ controllerUtils.redirIfNotComplete();
+
var w = $rootScope.wallet;
preconditions.checkState(w);
preconditions.checkState(w.settings.unitToSatoshi);
- $scope.title = 'Send';
+ $rootScope.title = 'Send';
$scope.loading = false;
var satToUnit = 1 / w.settings.unitToSatoshi;
$scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit;
@@ -57,6 +59,7 @@ angular.module('copayApp.controllers').controller('SendController',
set: function(newValue) {
this._amount = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
+
this._alternative = parseFloat(
(rateService.toFiat(newValue * w.settings.unitToSatoshi, w.settings.alternativeIsoCode)).toFixed(2), 10);
} else {
@@ -80,16 +83,7 @@ angular.module('copayApp.controllers').controller('SendController',
}
$scope.showAddressBook = function() {
- var flag;
- if (w) {
- for (var k in w.addressBook) {
- if (w.addressBook[k]) {
- flag = true;
- break;
- }
- }
- }
- return flag;
+ return w && _.keys(w.addressBook).length > 0;
};
if ($rootScope.pendingPayment) {
diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js
index 6cccc126f..b2f0b26a6 100644
--- a/js/controllers/sidebar.js
+++ b/js/controllers/sidebar.js
@@ -1,6 +1,6 @@
'use strict';
-angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $sce, $location, $http, $filter, notification, controllerUtils) {
+angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $location, controllerUtils) {
$scope.menu = [{
'title': 'Receive',
@@ -20,55 +20,55 @@ angular.module('copayApp.controllers').controller('SidebarController', function(
'link': 'more'
}];
- $scope.signout = function() {
- logout();
- };
-
- // Ensures a graceful disconnect
- window.onbeforeunload = function() {
- controllerUtils.logout();
- };
-
- $scope.$on('$destroy', function() {
- window.onbeforeunload = undefined;
- });
-
-
$scope.refresh = function() {
var w = $rootScope.wallet;
- w.sendWalletReady();
- if ($rootScope.addrInfos.length > 0) {
- controllerUtils.updateBalance(function() {
- $rootScope.$digest();
- });
+ if (!w) return;
+
+ if (w.isReady()) {
+ w.sendWalletReady();
+ if ($rootScope.addrInfos.length > 0) {
+ controllerUtils.clearBalanceCache(w);
+ controllerUtils.updateBalance(w, function() {
+ $rootScope.$digest();
+ });
+ }
}
};
+ $scope.signout = function() {
+ $scope.$emit('signout');
+ };
+
$scope.isActive = function(item) {
return item.link && item.link == $location.path().split('/')[1];
};
- function logout() {
- controllerUtils.logout();
- }
-
- // ng-repeat defined number of times instead of repeating over array?
- $scope.getNumber = function(num) {
- return new Array(num);
- }
-
if ($rootScope.wallet) {
- $scope.$on('$idleWarn', function(a,countdown) {
- if (!(countdown%5))
- notification.warning('Session will be closed', $filter('translate')('Your session is about to expire due to inactivity in') + ' ' + countdown + ' ' + $filter('translate')('seconds'));
- });
-
- $scope.$on('$idleTimeout', function() {
- $scope.signout();
- notification.warning('Session closed', 'Session closed because a long time of inactivity');
- });
- $scope.$on('$keepalive', function() {
- $rootScope.wallet.keepAlive();
+ $rootScope.$watch('wallet.id', function() {
+ $scope.walletSelection = false;
});
}
+
+ $scope.switchWallet = function(wid) {
+ controllerUtils.setFocusedWallet(wid);
+ };
+
+ $scope.toggleWalletSelection = function() {
+ $scope.walletSelection = !$scope.walletSelection;
+ if (!$scope.walletSelection) return;
+
+ $scope.wallets = [];
+ var wids = _.pluck($rootScope.iden.listWallets(), 'id');
+ _.each(wids, function(wid) {
+ if (controllerUtils.isFocusedWallet(wid)) return;
+ var w = $rootScope.iden.getWalletById(wid);
+ $scope.wallets.push(w);
+ controllerUtils.updateBalance(w, function(err, res) {
+ if (err) return;
+ setTimeout(function() {
+ $scope.$digest();
+ }, 1);
+ });
+ });
+ };
});
diff --git a/js/controllers/transactions.js b/js/controllers/transactions.js
index 914a07137..019a93616 100644
--- a/js/controllers/transactions.js
+++ b/js/controllers/transactions.js
@@ -3,10 +3,12 @@ var bitcore = require('bitcore');
angular.module('copayApp.controllers').controller('TransactionsController',
function($scope, $rootScope, $timeout, controllerUtils, notification, rateService) {
+ controllerUtils.redirIfNotComplete();
+
var w = $rootScope.wallet;
- $scope.title = 'Transactions';
+ $rootScope.title = 'History';
$scope.loading = false;
$scope.lastShowed = false;
@@ -37,72 +39,6 @@ angular.module('copayApp.controllers').controller('TransactionsController',
}, 10);
};
- var _aggregateItems = function(items) {
- var w = $rootScope.wallet;
- if (!items) return [];
-
- var l = items.length;
-
- var ret = [];
- var tmp = {};
- var u = 0;
-
- for (var i = 0; i < l; i++) {
-
- var notAddr = false;
- // non standard input
- if (items[i].scriptSig && !items[i].addr) {
- items[i].addr = 'Unparsed address [' + u+++']';
- items[i].notAddr = true;
- notAddr = true;
- }
-
- // non standard output
- if (items[i].scriptPubKey && !items[i].scriptPubKey.addresses) {
- items[i].scriptPubKey.addresses = ['Unparsed address [' + u+++']'];
- items[i].notAddr = true;
- notAddr = true;
- }
-
- // multiple addr at output
- if (items[i].scriptPubKey && items[i].scriptPubKey.addresses.length > 1) {
- items[i].addr = items[i].scriptPubKey.addresses.join(',');
- ret.push(items[i]);
- continue;
- }
-
- var addr = items[i].addr || (items[i].scriptPubKey && items[i].scriptPubKey.addresses[0]);
-
- if (!tmp[addr]) {
- tmp[addr] = {};
- tmp[addr].valueSat = 0;
- tmp[addr].count = 0;
- tmp[addr].addr = addr;
- tmp[addr].items = [];
- }
- tmp[addr].isSpent = items[i].spentTxId;
-
- tmp[addr].doubleSpentTxID = tmp[addr].doubleSpentTxID || items[i].doubleSpentTxID;
- tmp[addr].doubleSpentIndex = tmp[addr].doubleSpentIndex || items[i].doubleSpentIndex;
- tmp[addr].unconfirmedInput += items[i].unconfirmedInput;
- tmp[addr].dbError = tmp[addr].dbError || items[i].dbError;
- tmp[addr].valueSat += parseInt((items[i].value * bitcore.util.COIN).toFixed(0));
- tmp[addr].items.push(items[i]);
- tmp[addr].notAddr = notAddr;
- tmp[addr].count++;
- }
-
- angular.forEach(tmp, function(v) {
- v.value = (parseInt(v.valueSat || 0).toFixed(0)) * satToUnit;
- rateService.whenAvailable(function() {
- var valueSat = v.value * w.settings.unitToSatoshi;
- v.valueAlt = rateService.toFiat(valueSat, w.settings.alternativeIsoCode);
- });
- ret.push(v);
- });
- return ret;
- };
-
$scope.toogleLast = function() {
$scope.lastShowed = !$scope.lastShowed;
if ($scope.lastShowed) {
@@ -111,37 +47,30 @@ angular.module('copayApp.controllers').controller('TransactionsController',
};
$scope.getTransactions = function() {
+ var self = this;
var w = $rootScope.wallet;
- $scope.loading = true;
- if (w) {
- var addresses = w.getAddressesStr();
- if (addresses.length > 0) {
- $scope.blockchain_txs = $scope.wallet.txCache || [];
- w.blockchain.getTransactions(addresses, function(err, txs) {
- if (err) throw err;
+ if (!w) return;
- $timeout(function() {
- $scope.blockchain_txs = [];
- for (var i = 0; i < txs.length; i++) {
- txs[i].vinSimple = _aggregateItems(txs[i].vin);
- txs[i].voutSimple = _aggregateItems(txs[i].vout);
- txs[i].valueOut = ((txs[i].valueOut * bitcore.util.COIN).toFixed(0)) * satToUnit;
- txs[i].fees = ((txs[i].fees * bitcore.util.COIN).toFixed(0)) * satToUnit;
- $scope.blockchain_txs.push(txs[i]);
- }
- $scope.wallet.txCache = $scope.blockchain_txs;
- $scope.loading = false;
- }, 10);
- });
- } else {
- $timeout(function() {
- $scope.loading = false;
- $scope.lastShowed = false;
- }, 1);
+ $scope.blockchain_txs = w.cached_txs || [];
+ $scope.loading = true;
+ w.getTransactionHistory(function(err, res) {
+ if (err) throw err;
+
+ if (!res) {
+ $scope.loading = false;
+ $scope.lastShowed = false;
+ return;
}
- }
+
+ $scope.blockchain_txs = w.cached_txs = res;
+ $scope.loading = false;
+ setTimeout(function() {
+ $scope.$digest();
+ }, 1);
+ });
};
+
$scope.hasAction = function(actions, action) {
return actions.hasOwnProperty('create');
}
@@ -156,7 +85,7 @@ angular.module('copayApp.controllers').controller('TransactionsController',
$scope.getTransactions();
}
- $scope.amountAlternative = function (amount, txIndex, cb) {
+ $scope.amountAlternative = function(amount, txIndex, cb) {
var w = $rootScope.wallet;
rateService.whenAvailable(function() {
var valueSat = amount * w.settings.unitToSatoshi;
diff --git a/js/directives.js b/js/directives.js
index d9a9a860a..658ad012e 100644
--- a/js/directives.js
+++ b/js/directives.js
@@ -111,14 +111,13 @@ angular.module('copayApp.directives')
};
}
])
- .directive('walletSecret', ['walletFactory',
- function(walletFactory) {
+ .directive('walletSecret', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var validator = function(value) {
var a = new Address(value);
- ctrl.$setValidity('walletSecret', !a.isValid() && Boolean(walletFactory.decodeSecret(value)));
+ ctrl.$setValidity('walletSecret', !a.isValid() && Boolean(copay.Wallet.decodeSecret(value)));
return value;
};
@@ -126,7 +125,7 @@ angular.module('copayApp.directives')
}
};
}
- ])
+ )
.directive('loading', function() {
return {
restrict: 'A',
diff --git a/js/log.js b/js/log.js
index 6dc4305dd..4b278eebf 100644
--- a/js/log.js
+++ b/js/log.js
@@ -1,5 +1,5 @@
var config = require('../config');
-var _ = require('underscore');
+var _ = require('lodash');
/**
* @desc
@@ -22,6 +22,7 @@ var Logger = function(name) {
this.level = 2;
};
+
var levels = {
'debug': 0,
'info': 1,
@@ -34,9 +35,20 @@ var levels = {
_.each(levels, function(level, levelName) {
Logger.prototype[levelName] = function() {
if (level >= levels[this.level]) {
- var str = '[' + levelName + '] ' + this.name + ': ' + arguments[0],
+
+ if (Error.stackTraceLimit && this.level == 'debug') {
+ var old = Error.stackTraceLimit;
+ Error.stackTraceLimit = 2
+ var stack = new Error().stack;
+ var lines = stack.split('\n');
+ var caller = lines[2];
+ caller = ':' + caller.substr(6);
+ Error.stackTraceLimit = old;
+ }
+
+ var str = '[' + levelName + (caller || '') + '] ' + arguments[0],
extraArgs,
- extraArgs = [].slice.call(arguments, 1);
+ extraArgs = [].slice.call(arguments, 1);
if (console[levelName]) {
extraArgs.unshift(str);
console[levelName].apply(console, extraArgs);
diff --git a/js/mobile.js b/js/mobile.js
index 668272b69..b8d28931c 100644
--- a/js/mobile.js
+++ b/js/mobile.js
@@ -23,4 +23,4 @@ function onDeviceReady() {
window.plugins.webintent.getUri(handleBitcoinURI);
window.plugins.webintent.onNewIntent(handleBitcoinURI);
window.handleOpenURL = handleBitcoinURI;
-}
\ No newline at end of file
+}
diff --git a/js/models/Async.js b/js/models/Async.js
index 217eb9869..60f089030 100644
--- a/js/models/Async.js
+++ b/js/models/Async.js
@@ -18,6 +18,17 @@ function Network(opts) {
this.url = opts.url;
this.secretNumber = opts.secretNumber;
this.cleanUp();
+
+ this.socketOptions = {
+ reconnection: true,
+ 'force new connection': true,
+ 'secure': this.url.indexOf('https') === 0,
+ };
+
+ if (opts.transports) {
+ this.socketOptions['transports'] = opts.transports;
+ }
+ this.socket = this.createSocket();
}
nodeUtil.inherits(Network, EventEmitter);
@@ -191,7 +202,7 @@ Network.prototype._onMessage = function(enc) {
this._deletePeer(sender);
return;
}
- log.debug('receiving ' + JSON.stringify(payload));
+ log.debug('Async: receiving ' + JSON.stringify(payload));
var self = this;
switch (payload.type) {
@@ -252,8 +263,12 @@ Network.prototype._setupConnectionHandlers = function(opts, cb) {
self.socket.on('no messages', self.emit.bind(self, 'no messages'));
+
+ var pubkey = self.getKey().public.toString('hex');
self.socket.on('connect', function() {
var pubkey = self.getKey().public.toString('hex');
+ log.debug('Async subscribing to pubkey:', pubkey);
+
self.socket.emit('subscribe', pubkey);
self.socket.on('disconnect', function() {
@@ -310,25 +325,22 @@ Network.prototype.start = function(opts, openCallback) {
preconditions.checkArgument(opts);
preconditions.checkArgument(opts.privkey);
preconditions.checkArgument(opts.copayerId);
-
preconditions.checkState(this.connectedPeers && this.connectedPeers.length === 0);
- if (this.started) return openCallback();
+ if (this.started) {
+ log.debug('Async: Networing already started for this wallet.')
+ return openCallback();
+ }
this.privkey = opts.privkey;
this.setCopayerId(opts.copayerId);
this.maxPeers = opts.maxPeers || this.maxPeers;
-
- this.socket = this.createSocket();
this._setupConnectionHandlers(opts, openCallback);
};
Network.prototype.createSocket = function() {
- return io.connect(this.url, {
- reconnection: true,
- 'force new connection': true,
- 'secure': this.url.indexOf('https') === 0,
- });
+ log.debug('Async: Connecting to socket:', this.url, this.socketOptions);
+ return io.connect(this.url, this.socketOptions);
};
Network.prototype.getOnlinePeerIDs = function() {
diff --git a/js/models/Compatibility.js b/js/models/Compatibility.js
new file mode 100644
index 000000000..c455d4dd0
--- /dev/null
+++ b/js/models/Compatibility.js
@@ -0,0 +1,231 @@
+'use strict';
+
+var Identity = require('./Identity');
+var Wallet = require('./Wallet');
+var cryptoUtils = require('../util/crypto');
+var CryptoJS = require('node-cryptojs-aes').CryptoJS;
+var sjcl = require('../../lib/sjcl');
+var log = require('../log');
+var preconditions = require('preconditions').instance();
+var _ = require('lodash');
+
+var Compatibility = {};
+Compatibility.iterations = 100;
+Compatibility.salt = 'mjuBtGybi/4=';
+
+/**
+ * Reads from localstorage wallets saved previously to 0.8
+ */
+Compatibility._getWalletIds = function(cb) {
+ preconditions.checkArgument(cb);
+ var walletIds = [];
+ var uniq = {};
+ for (key in localStorage) {
+ var split = key.split('::');
+ if (split.length == 2) {
+ var walletId = split[0];
+
+ if (!walletId || walletId === 'nameFor' || walletId === 'lock' || walletId === 'wallet') {
+ continue;
+ }
+
+ if (typeof uniq[walletId] === 'undefined') {
+ walletIds.push(walletId);
+ uniq[walletId] = 1;
+ }
+ }
+ }
+ return cb(walletIds);
+};
+
+/**
+ * @param {string} encryptedWallet - base64-encoded encrypted wallet
+ * @param {string} password
+ * @returns {Object}
+ */
+Compatibility.importLegacy = function(encryptedWallet, password) {
+ var passphrase = this.kdf(password);
+ var ret = Compatibility._decrypt(encryptedWallet, passphrase);
+ if (!ret) return null;
+ return ret;
+};
+
+/**
+ * Decrypts using the CryptoJS library (unknown encryption schema)
+ *
+ * Don't use CryptoJS to encrypt. This still exists for compatibility reasons only.
+ */
+Compatibility._decrypt = function(base64, passphrase) {
+ var decryptedStr = null;
+ try {
+ var decrypted = CryptoJS.AES.decrypt(base64, passphrase);
+ if (decrypted)
+ decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
+ } catch (e) {
+ // Error while decrypting
+ return null;
+ }
+ return decryptedStr;
+};
+
+/**
+ * Reads an item from localstorage, decrypts it with passphrase
+ */
+Compatibility._read = function(k, passphrase, cb) {
+ preconditions.checkArgument(cb);
+
+ var ret = localStorage.getItem(k);
+ if (!ret) return cb(null);
+ var ret = self._decrypt(ret, passphrase);
+ if (!ret) return cb(null);
+
+ ret = ret.toString(CryptoJS.enc.Utf8);
+ ret = JSON.parse(ret);
+ return ret;
+};
+
+Compatibility.getWallets_Old = function(cb) {
+ preconditions.checkArgument(cb);
+
+ var wallets = [];
+ var self = this;
+
+ this._getWalletIds(function(ids) {
+ if (!ids.length) {
+ return cb([]);
+ }
+
+ _.each(ids, function(id) {
+ var name = localStorage.getItem('nameFor::' + id);
+ if (name) {
+ wallets.push({
+ id: id,
+ name: name,
+ });
+ }
+ });
+ return cb(wallets);
+ });
+};
+
+Compatibility.getWallets2 = function(cb) {
+ var self = this;
+ var re = /wallet::([^_]+)(_?(.*))/;
+
+ var keys = [];
+ for (key in localStorage) {
+ keys.push(key);
+ }
+ var wallets = _.compact(_.map(keys, function(key) {
+ if (key.indexOf('wallet::') !== 0)
+ return null;
+ var match = key.match(re);
+ if (match.length != 4)
+ return null;
+ return {
+ id: match[1],
+ name: match[3] ? match[3] : undefined,
+ };
+ }));
+
+ return cb(wallets);
+};
+
+/**
+ * Lists all wallets in localstorage
+ */
+Compatibility.listWalletsPre8 = function(cb) {
+ var self = this;
+ self.getWallets2(function(wallets) {
+ self.getWallets_Old(function(wallets2) {
+ var ids = _.pluck(wallets, 'id');
+ _.each(wallets2, function(w) {
+ if (!_.contains(ids, w.id))
+ wallets.push(w);
+ });
+ return cb(wallets);
+ });
+ })
+};
+
+/**
+ * Retrieves a wallet that predates the 0.8 release
+ */
+Compatibility.readWalletPre8 = function(walletId, password, cb) {
+ var self = this;
+ var passphrase = cryptoUtils.kdf(password);
+ var obj = {};
+
+ for (key in localStorage) {
+ if (key.indexOf('wallet::' + walletId) !== -1) {
+ var ret = self._read(localStorage.getItem(key), passphrase);
+ if (err) return cb(err);
+
+ _.each(Wallet.PERSISTED_PROPERTIES, function(p) {
+ obj[p] = ret[p];
+ });
+
+ if (!_.any(_.values(obj)))
+ return cb(new Error('Wallet not found'));
+
+ var w, err;
+ obj.id = walletId;
+ try {
+ w = self.fromObj(obj);
+ } catch (e) {
+ if (e && e.message && e.message.indexOf('MISSOPTS')) {
+ err = new Error('Could not read: ' + walletId);
+ } else {
+ err = e;
+ }
+ w = null;
+ }
+ return cb(err, w);
+ }
+ }
+};
+
+Compatibility.importEncryptedWallet = function(identity, cypherText, password, opts, cb) {
+ var crypto = opts.cryptoUtil || cryptoUtils;
+ var key = crypto.kdf(password);
+ var obj = crypto.decrypt(key, cypherText);
+ if (!obj) {
+ log.info("Could not decrypt, trying legacy..");
+ obj = Compatibility.importLegacy(cypherText, password);
+ if (!obj) {
+ return cb(new Error('Could not decrypt'))
+ }
+ };
+ try {
+ obj = JSON.parse(obj);
+ } catch (e) {
+ return cb(new Error('Could not read encrypted wallet'));
+ }
+ return identity.importWalletFromObj(obj, opts, cb);
+};
+
+/**
+ * @desc Generate a WordArray expanding a password
+ *
+ * @param {string} password - the password to expand
+ * @returns WordArray 512 bits with the expanded key generated from password
+ */
+Compatibility.kdf = function(password) {
+ var hash = sjcl.hash.sha256.hash(sjcl.hash.sha256.hash(password));
+ var salt = sjcl.codec.base64.toBits(this.salt);
+
+ var crypto2 = function(key, salt, iterations, length, alg) {
+ return sjcl.codec.hex.fromBits(sjcl.misc.pbkdf2(key, salt, iterations, length * 8,
+ alg == 'sha1' ? function(key) {
+ return new sjcl.misc.hmac(key, sjcl.hash.sha1)
+ } : null
+ ))
+ };
+
+ var key512 = crypto2(hash, salt, this.iterations, 64, 'sha1');
+ var sbase64 = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(key512));
+ return sbase64;
+};
+
+
+module.exports = Compatibility;
diff --git a/js/models/HDParams.js b/js/models/HDParams.js
index 04872eedd..dba208e28 100644
--- a/js/models/HDParams.js
+++ b/js/models/HDParams.js
@@ -4,7 +4,7 @@
var preconditions = require('preconditions').singleton();
var HDPath = require('./HDPath');
-var _ = require('underscore');
+var _ = require('lodash');
/**
* @desc
diff --git a/js/models/HDPath.js b/js/models/HDPath.js
index 041912742..7a03fa858 100644
--- a/js/models/HDPath.js
+++ b/js/models/HDPath.js
@@ -3,7 +3,7 @@
// 90.2% typed (by google's closure-compiler account)
var preconditions = require('preconditions').singleton();
-var _ = require('underscore');
+var _ = require('lodash');
/**
* @namespace
diff --git a/js/models/Identity.js b/js/models/Identity.js
new file mode 100644
index 000000000..65d3c1b05
--- /dev/null
+++ b/js/models/Identity.js
@@ -0,0 +1,672 @@
+'use strict';
+var preconditions = require('preconditions').singleton();
+
+var _ = require('lodash');
+var bitcore = require('bitcore');
+var log = require('../log');
+var async = require('async');
+var cryptoUtil = require('../util/crypto');
+
+var version = require('../../version').version;
+var TxProposals = require('./TxProposals');
+var PublicKeyRing = require('./PublicKeyRing');
+var PrivateKey = require('./PrivateKey');
+var Wallet = require('./Wallet');
+var PluginManager = require('./PluginManager');
+var Async = module.exports.Async = require('./Async');
+
+/**
+ * @desc
+ * Identity - stores the state for a wallet in creation
+ *
+ * @param {Object} opts - configuration for this wallet
+ * @param {string} opts.fullName
+ * @param {string} opts.email
+ * @param {string} opts.password
+ * @param {string} opts.storage
+ * @param {string} opts.pluginManager
+ * @param {Object} opts.walletDefaults
+ * @param {string} opts.version
+ * @param {Object} opts.wallets
+ * @param {Object} opts.network
+ * @param {string} opts.network.testnet
+ * @param {string} opts.network.livenet
+ * @constructor
+ */
+function Identity(opts) {
+ preconditions.checkArgument(opts);
+
+ opts = _.extend({}, opts);
+ this.networkOpts = {
+ 'livenet': opts.network.livenet,
+ 'testnet': opts.network.testnet,
+ };
+ this.blockchainOpts = {
+ 'livenet': opts.network.livenet,
+ 'testnet': opts.network.testnet,
+ };
+
+ this.fullName = opts.fullName || opts.email;
+ this.email = opts.email;
+ this.password = opts.password;
+
+ this.storage = opts.storage || opts.pluginManager.get('DB');
+ this.storage.setCredentials(this.email, this.password, {});
+
+ this.walletDefaults = opts.walletDefaults || {};
+ this.version = opts.version || version;
+
+ this.wallets = opts.wallets || {};
+};
+
+Identity.getKeyForEmail = function(email) {
+ return 'profile::' + bitcore.util.sha256ripe160(email).toString('hex');
+};
+
+Identity.prototype.getId = function() {
+ return Identity.getKeyForEmail(this.email);
+};
+
+Identity.prototype.getName = function() {
+ return this.fullName || this.email;
+};
+
+/**
+ * Creates an Identity
+ *
+ * @param opts
+ * @param cb
+ * @return {undefined}
+ */
+Identity.create = function(opts) {
+ opts = _.extend({}, opts);
+
+ return new Identity(opts);
+};
+
+
+/**
+ * Open an Identity from the given storage
+ *
+ * @param {Object} opts
+ * @param {Object} opts.storage
+ * @param {string} opts.email
+ * @param {string} opts.password
+ * @param {Function} cb
+ */
+Identity.open = function(opts, cb) {
+ var storage = opts.storage || opts.pluginManager.get('DB');
+ storage.setCredentials(opts.email, opts.password, opts);
+ storage.getItem(Identity.getKeyForEmail(opts.email), function(err, data) {
+ if (err) {
+ return cb(err);
+ }
+ return Identity.createFromPartialJson(data, opts, cb);
+ });
+};
+
+/**
+ * Creates an Identity, retrieves all Wallets remotely, and activates network
+ *
+ * @param {string} jsonString - a string containing a json object with options to rebuild the identity
+ * @param {Object} opts
+ * @param {Function} cb
+ */
+Identity.createFromPartialJson = function(jsonString, opts, callback) {
+ var exported;
+ try {
+ exported = JSON.parse(jsonString);
+ } catch (e) {
+ return callback('Invalid JSON');
+ }
+ var identity = new Identity(_.extend(opts, exported));
+ async.map(exported.walletIds, function(walletId, callback) {
+ identity.retrieveWalletFromStorage(walletId, {}, function(error, wallet) {
+ if (!error) {
+ identity.wallets[wallet.getId()] = wallet;
+ identity.bindWallet(wallet);
+ wallet.netStart();
+ }
+ callback(error, wallet);
+ });
+ }, function(err) {
+ return callback(err, identity);
+ });
+};
+
+/**
+ * @param {string} walletId
+ * @param {} opts
+ * opts.importWallet
+ * @param {Function} callback
+ */
+Identity.prototype.retrieveWalletFromStorage = function(walletId, opts, callback) {
+ var self = this;
+
+ var importFunction = opts.importWallet || Wallet.fromUntrustedObj;
+
+ this.storage.getItem(Wallet.getStorageKey(walletId), function(error, walletData) {
+ if (error) {
+ return callback(error);
+ }
+ try {
+ log.debug('## OPENING Wallet: ' + walletId);
+ if (_.isString(walletData)) {
+ walletData = JSON.parse(walletData);
+ }
+ var readOpts = {
+ networkOpts: self.networkOpts,
+ blockchainOpts: self.blockchainOpts,
+ skipFields: []
+ };
+
+ return callback(null, importFunction(walletData, readOpts));
+
+ } catch (e) {
+
+ log.debug("ERROR: ", e.message);
+ if (e && e.message && e.message.indexOf('MISSOPTS') !== -1) {
+ return callback(new Error('WERROR: Could not read: ' + walletId + ': ' + e.message));
+ } else {
+ return callback(e);
+ }
+ }
+ });
+};
+
+/**
+ * TODO (matiu): What is this supposed to do?
+ */
+Identity.isAvailable = function(email, opts, cb) {
+ return cb();
+};
+
+/**
+ * @param {Wallet} wallet
+ * @param {Function} cb
+ */
+Identity.prototype.storeWallet = function(wallet, cb) {
+ preconditions.checkArgument(wallet && _.isObject(wallet));
+
+ var val = wallet.toObj();
+ var key = wallet.getStorageKey();
+
+ this.storage.setItem(key, val, function(err) {
+ if (err) {
+ log.debug('Wallet:' + wallet.getName() + ' couldnt be stored');
+ }
+ if (cb)
+ return cb(err);
+ });
+};
+
+Identity.prototype.toObj = function() {
+ return _.extend({
+ walletIds: _.keys(this.wallets)
+ },
+ _.pick(this, 'version', 'fullName', 'password', 'email'));
+};
+
+Identity.prototype.exportEncryptedWithWalletInfo = function(opts) {
+ var crypto = opts.cryptoUtil || cryptoUtil;
+ var key = crypto.kdf(this.password);
+ return crypto.encrypt(key, this.exportWithWalletInfo(opts));
+};
+
+Identity.prototype.exportWithWalletInfo = function(opts) {
+ return _.extend({
+ wallets: _.map(this.wallets, function(wallet) {
+ return wallet.toObj();
+ })
+ },
+ _.pick(this, 'version', 'fullName', 'password', 'email')
+ );
+};
+
+/**
+ * @param {Object} opts
+ * @param {Function} cb
+ */
+Identity.prototype.store = function(opts, cb) {
+ log.debug('Storing profile');
+
+ var self = this;
+ opts = opts || {};
+
+ var storeFunction = opts.failIfExists ? self.storage.createItem : self.storage.setItem;
+
+ storeFunction.call(self.storage, this.getId(), this.toObj(), function(err) {
+ if (err) return cb(err);
+
+ if (opts.noWallets)
+ return cb();
+
+ async.map(self.wallets, self.storeWallet, cb);
+ });
+};
+
+Identity.prototype._cleanUp = function() {
+ // NOP
+};
+
+/**
+ * @desc Closes the wallet and disconnects all services
+ */
+Identity.prototype.close = function(cb) {
+ async.map(this.wallets, function(wallet, callback) {
+ wallet.close(callback);
+ }, cb);
+};
+
+/**
+ * @desc Imports a wallet from an encrypted string
+ * @param {string} cypherText - the encrypted object
+ * @param {string} passphrase - passphrase to decrypt it
+ * @param {string[]} opts.skipFields - fields to ignore when importing
+ * @param {string[]} opts.salt -
+ * @param {string[]} opts.iterations -
+ * @param {string[]} opts.importFunction - for stubbing
+ * @return {Wallet}
+ */
+Identity.prototype.importEncryptedWallet = function(cypherText, password, opts, cb) {
+
+ var crypto = opts.cryptoUtil || cryptoUtil;
+ // TODO set iter and salt using config.js
+ var key = crypto.kdf(password);
+ var obj = crypto.decrypt(key, cypherText);
+ if (!obj) return cb(new Error('Could not decrypt'));
+ try {
+ obj = JSON.parse(obj);
+ } catch (e) {
+ return cb(new Error('Could not decrypt'));
+ }
+ return this.importWalletFromObj(obj, opts, cb)
+};
+
+Identity.prototype.importWalletFromObj = function(obj, opts, cb) {
+ var self = this;
+ preconditions.checkArgument(cb);
+ var importFunction = opts.importWallet || Wallet.fromUntrustedObj;
+
+ var readOpts = {
+ networkOpts: this.networkOpts,
+ blockchainOpts: this.blockchainOpts,
+ skipFields: opts.skipFields,
+ };
+
+ var w = importFunction(obj, readOpts);
+ if (!w) return cb(new Error('Could not decrypt'));
+
+ this._checkVersion(w.version);
+ this.addWallet(w, function(err) {
+ if (err) return cb(err, null);
+ self.wallets[w.getId()] = w;
+ self.bindWallet(w);
+ self.store(null, function(err) {
+ return cb(err, w);
+ });
+ });
+};
+
+/**
+ * @param {Wallet} wallet
+ * @param {Function} cb
+ */
+Identity.prototype.closeWallet = function(wallet, cb) {
+ preconditions.checkState(wallet, 'Wallet not found');
+
+ wallet.close(function(err) {
+ delete self.wallets[wid];
+ return cb(err);
+ });
+};
+
+Identity.importFromEncryptedFullJson = function(str, password, opts, cb) {
+ var crypto = opts.cryptoUtil || cryptoUtil;
+ var key = crypto.kdf(password);
+ return Identity.importFromFullJson(crypto.decript(key, str));
+};
+
+Identity.importFromFullJson = function(str, password, opts, cb) {
+ preconditions.checkArgument(str);
+ var json;
+ try {
+ json = JSON.parse(str);
+ } catch (e) {
+ return cb('Unable to retrieve json from string', str);
+ }
+
+ if (!_.isNumber(json.iterations))
+ return cb('BADSTR: Missing iterations');
+
+ var email = json.email;
+ var iden = new Identity(email, password, opts);
+
+ json.wallets = json.wallets || {};
+ async.map(json.wallets, function(walletData, callback) {
+ iden.importEncryptedWallet(wstr, password, opts, function(err, w) {
+ if (err) return callback(err);
+ log.debug('Wallet ' + w.getId() + ' imported');
+ callback();
+ });
+ }, function(err, results) {
+ if (err) return cb(err);
+
+ iden.store(null, function(err) {
+ return cb(err, iden);
+ });
+ });
+};
+
+Identity.prototype.bindWallet = function(w) {
+ var self = this;
+
+ self.wallets[w.getId()] = w;
+ log.debug('Binding wallet ' + w.getName());
+
+ w.on('txProposalsUpdated', function() {
+ log.debug('> Wallet' + w.getName());
+ self.storeWallet(w);
+ });
+ w.on('newAddresses', function() {
+ log.debug(' Wallet' + w.getName());
+ self.storeWallet(w);
+ });
+ w.on('settingsUpdated', function() {
+ log.debug(' Wallet' + w.getName());
+ self.storeWallet(w);
+ });
+ w.on('txProposalEvent', function() {
+ log.debug(' Wallet' + w.getName());
+ self.storeWallet(w);
+ });
+ w.on('ready', function() {
+ log.debug(' Wallet' + w.getName());
+ self.store({noWallets:true}, function() {
+ self.storeWallet(w);
+ });
+ });
+ w.on('addressBookUpdated', function() {
+ log.debug(' Wallet' + w.getName());
+ self.storeWallet(w);
+ });
+ w.on('publicKeyRingUpdated', function() {
+ log.debug(' Wallet' + w.getName());
+ self.storeWallet(w);
+ });
+};
+
+/**
+ * @desc This method prepares options for a new Wallet
+ *
+ * @param {Object} opts
+ * @param {string} opts.id
+ * @param {PrivateKey=} opts.privateKey
+ * @param {string=} opts.privateKeyHex
+ * @param {number} opts.requiredCopayers
+ * @param {number} opts.totalCopayers
+ * @param {PublicKeyRing=} opts.publicKeyRing
+ * @param {string} opts.nickname
+ * @param {string} opts.password
+ * @param {boolean} opts.spendUnconfirmed this.walletDefaults.spendUnconfirmed
+ * @param {number} opts.reconnectDelay time in milliseconds
+ * @param {number=} opts.version
+ * @param {callback} opts.version
+ * @return {Wallet}
+ */
+Identity.prototype.createWallet = function(opts, cb) {
+ preconditions.checkArgument(cb);
+
+ opts = opts || {};
+ opts.networkName = opts.networkName || 'testnet';
+
+ log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey'));
+
+ var privOpts = {
+ networkName: opts.networkName,
+ };
+
+ if (opts.privateKeyHex && opts.privateKeyHex.length > 1) {
+ privOpts.extendedPrivateKeyString = opts.privateKeyHex;
+ }
+
+ opts.privateKey = opts.privateKey || new PrivateKey(privOpts);
+
+ var requiredCopayers = opts.requiredCopayers || this.walletDefaults.requiredCopayers;
+ var totalCopayers = opts.totalCopayers || this.walletDefaults.totalCopayers;
+ opts.lockTimeoutMin = this.walletDefaults.idleDurationMin;
+
+ opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({
+ networkName: opts.networkName,
+ requiredCopayers: requiredCopayers,
+ totalCopayers: totalCopayers,
+ });
+ opts.publicKeyRing.addCopayer(
+ opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(),
+ opts.nickname || this.getName()
+ );
+ log.debug('\t### PublicKeyRing Initialized');
+
+ opts.txProposals = opts.txProposals || new TxProposals({
+ networkName: opts.networkName,
+ });
+ var walletClass = opts.walletClass || Wallet;
+
+ log.debug('\t### TxProposals Initialized');
+
+
+ opts.networkOpts = this.networkOpts;
+ opts.blockchainOpts = this.blockchainOpts;
+
+ opts.spendUnconfirmed = opts.spendUnconfirmed || this.walletDefaults.spendUnconfirmed;
+ opts.reconnectDelay = opts.reconnectDelay || this.walletDefaults.reconnectDelay;
+ opts.requiredCopayers = requiredCopayers;
+ opts.totalCopayers = totalCopayers;
+ opts.version = opts.version || this.version;
+
+ var self = this;
+
+ var w = new walletClass(opts);
+ this.addWallet(w, function(err) {
+ if (err) return cb(err);
+ self.bindWallet(w);
+ w.netStart();
+ return cb(err, w);
+ });
+};
+
+Identity.prototype.addWallet = function(wallet, cb) {
+ preconditions.checkArgument(wallet);
+ preconditions.checkArgument(wallet.getId);
+ preconditions.checkArgument(cb);
+
+ this.wallets[wallet.getId()] = wallet;
+
+ // TODO (eordano): Consider not saving automatically after this
+ this.storage.setItem(wallet.getStorageKey(), wallet.toObj(), cb);
+};
+
+
+/**
+ * @desc Checks if a version is compatible with the current version
+ * @param {string} inVersion - a version, with major, minor, and revision, period-separated (x.y.z)
+ * @throws {Error} if there's a major version difference
+ */
+Identity.prototype._checkVersion = function(inVersion) {
+ if (inVersion) {
+ var thisV = this.version.split('.');
+ var thisV0 = parseInt(thisV[0]);
+ var inV = inVersion.split('.');
+ var inV0 = parseInt(inV[0]);
+ }
+
+ //We only check for major version differences
+ if (thisV0 < inV0) {
+ throw new Error('Major difference in software versions' +
+ '. Received:' + inVersion +
+ '. Current version:' + this.version +
+ '. Aborting.');
+ }
+};
+
+/**
+ * @param {string} walletId
+ * @returns {Wallet}
+ */
+Identity.prototype.getWalletById = function(walletId) {
+ return this.wallets[walletId];
+};
+
+/**
+ * @returns {Wallet[]}
+ */
+Identity.prototype.listWallets = function() {
+ return _.values(this.wallets);
+};
+
+/**
+ * @desc Deletes a wallet. This involves removing it from the storage instance
+ *
+ * @param {string} walletId
+ * @callback cb
+ * @return {err}
+ */
+Identity.prototype.deleteWallet = function(walletId, cb) {
+ var self = this;
+
+ delete this.wallets[walletId];
+ this.storage.removeItem(Wallet.getStorageKey(walletId), function(err) {
+ if (err) {
+ return cb(err);
+ }
+ self.store(null, cb);
+ });
+};
+
+/**
+ * @desc Pass through to {@link Wallet#secret}
+ */
+Identity.prototype.decodeSecret = function(secret) {
+ try {
+ return Wallet.decodeSecret(secret);
+ } catch (e) {
+ return false;
+ }
+};
+
+Identity.prototype.getLastFocusedWallet = function() {
+ if (_.keys(this.wallets).length == 0) return;
+ return _.max(this.wallets, function(wallet) {
+ return wallet.lastTimestamp || 0;
+ });
+};
+
+/**
+ * @callback walletCreationCallback
+ * @param {?} err - an error, if any, that happened during the wallet creation
+ * @param {Wallet=} wallet - the wallet created
+ */
+
+/**
+ * @desc Start the network functionality.
+ *
+ * Start up the Network instance and try to join a wallet defined by the
+ * parameter secret using the parameter nickname. Encode
+ * information locally using passphrase. privateHex is the
+ * private extended master key. cb has two params: error and wallet.
+ *
+ * @param {object} opts
+ * @param {string} opts.secret - the wallet secret
+ * @param {string} opts.nickname - a nickname for the current user
+ * @param {string} opts.privateHex - the private extended master key
+ * @param {walletCreationCallback} cb - a callback
+ */
+Identity.prototype.joinWallet = function(opts, cb) {
+ preconditions.checkArgument(opts);
+ preconditions.checkArgument(opts.secret);
+ preconditions.checkArgument(cb);
+ var self = this;
+ var decodedSecret = this.decodeSecret(opts.secret);
+ if (!decodedSecret || !decodedSecret.networkName || !decodedSecret.pubKey) {
+ return cb('badSecret');
+ }
+
+ var privOpts = {
+ networkName: decodedSecret.networkName,
+ };
+
+ if (opts.privateHex && opts.privateHex.length > 1) {
+ privOpts.extendedPrivateKeyString = opts.privateHex;
+ }
+
+ //Create our PrivateK
+ var privateKey = new PrivateKey(privOpts);
+ log.debug('\t### PrivateKey Initialized');
+ var joinOpts = {
+ copayerId: privateKey.getId(),
+ privkey: privateKey.getIdPriv(),
+ key: privateKey.getIdKey(),
+ secretNumber: decodedSecret.secretNumber,
+ };
+
+
+ var joinNetwork = opts.Async || new Async(this.networkOpts[decodedSecret.networkName]);
+
+ // This is a hack to reconize if the connection was rejected or the peer wasn't there.
+ var connectedOnce = false;
+ joinNetwork.on('connected', function(sender, data) {
+ connectedOnce = true;
+ });
+
+ joinNetwork.on('connect_error', function() {
+ return cb('connectionError');
+ });
+
+ joinNetwork.on('serverError', function() {
+ return cb('joinError');
+ });
+
+ joinNetwork.start(joinOpts, function() {
+
+ joinNetwork.greet(decodedSecret.pubKey, joinOpts.secretNumber);
+ joinNetwork.on('data', function(sender, data) {
+ if (data.type === 'walletId' && data.opts) {
+ if (!data.networkName || data.networkName !== decodedSecret.networkName) {
+ return cb('badNetwork');
+ }
+ data.opts.networkName = data.networkName;
+
+ var walletOpts = _.clone(data.opts);
+ walletOpts.id = data.walletId;
+ walletOpts.network = joinNetwork;
+
+ walletOpts.privateKey = privateKey;
+ walletOpts.nickname = opts.nickname || self.getName();
+
+ if (opts.password)
+ walletOpts.password = opts.password;
+
+ self.createWallet(walletOpts, function(err, w) {
+ if (w) {
+ w.sendWalletReady(decodedSecret.pubKey);
+ } else {
+ if (!err) {
+ err = 'walletFull';
+ }
+ }
+ if (err)
+ return cb(err);
+
+ self.store({
+ noWallets: true
+ }, function(err) {
+ return cb(err, w);
+ });
+ });
+ }
+ });
+ });
+};
+
+
+module.exports = Identity;
diff --git a/js/models/Insight.js b/js/models/Insight.js
index 5b6e0e32c..39761a8de 100644
--- a/js/models/Insight.js
+++ b/js/models/Insight.js
@@ -42,6 +42,11 @@ var Insight = function(opts) {
'secure': opts.url.indexOf('https') === 0
};
+
+ if (opts.transports) {
+ this.opts['transports'] = opts.transports;
+ }
+
this.socket = this.getSocket();
}
@@ -119,6 +124,7 @@ Insight.prototype.subscribeToBlocks = function() {
/** @private */
Insight.prototype._getSocketIO = function(url, opts) {
+ log.debug('Insight: Connecting to socket:', this.url, this.opts);
return io(this.url, this.opts);
};
@@ -194,10 +200,10 @@ Insight.prototype.subscribe = function(addresses) {
var self = this;
function handlerFor(self, address) {
+console.log('HANDLER [Insight.js.150:address:]',address); //TODO
return function(txid) {
// verify the address is still subscribed
if (!self.subscribed[address]) return;
- log.debug('insight tx event');
self.emit('tx', {
address: address,
@@ -218,6 +224,8 @@ Insight.prototype.subscribe = function(addresses) {
s.emit('subscribe', address);
s.on(address, handler);
+ } else {
+ log.debug('Already subcribed to: ', address);
}
});
};
diff --git a/js/models/Passphrase.js b/js/models/Passphrase.js
deleted file mode 100644
index 53142f0b5..000000000
--- a/js/models/Passphrase.js
+++ /dev/null
@@ -1,78 +0,0 @@
-'use strict';
-
-// 65.7% typed (by google's closure-compiler account)
-
-var sjcl = require('../../lib/sjcl');
-var preconditions = require('preconditions').instance();
-var _ = require('underscore');
-
-/**
- * @desc
- * Class for a Passphrase object, uses PBKDF2 to expand a password
- *
- * @constructor
- * @param {object} config
- * @param {string=} config.salt - 'mjuBtGybi/4=' by default
- * @param {number=} config.iterations - 1000 by default
- */
-function Passphrase(config) {
- preconditions.checkArgument(!config || !config.salt || _.isString(config.salt));
- preconditions.checkArgument(!config || !config.iterations || _.isNumber(config.iterations));
- config = config || {};
- this.salt = config.salt || 'mjuBtGybi/4=';
- this.iterations = config.iterations || 1000;
-};
-
-/**
- * @desc Generate a WordArray expanding a password
- *
- * @param {string} password - the password to expand
- * @returns WordArray 512 bits with the expanded key generated from password
- */
-Passphrase.prototype.get = function(password) {
- var hash = sjcl.hash.sha256.hash(sjcl.hash.sha256.hash(password));
- var salt = sjcl.codec.base64.toBits(this.salt);
-
- var crypto2 = function(key, salt, iterations, length, alg) {
- return sjcl.codec.hex.fromBits(sjcl.misc.pbkdf2(key, salt, iterations, length * 8,
- alg == 'sha1' ? function(key) {
- return new sjcl.misc.hmac(key, sjcl.hash.sha1)
- } : null
- ))
- };
-
- var key512 = crypto2(hash, salt, this.iterations, 64, 'sha1');
-
- return key512;
-};
-
-
-/**
- * @desc Generate a base64 encoded key
- *
- * @param {string} password - the password to expand
- * @returns {string} 512 bits of a base64 encoded passphrase based on password
- */
-Passphrase.prototype.getBase64 = function(password) {
- var key512 = this.get(password);
-
- var sbase64 = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(key512));
- return sbase64;
-};
-
-
-/**
- * @desc Generate a base64 encoded key, without blocking
- *
- * @param {string} password - the password to expand
- * @param {passphraseCallback} cb
- */
-Passphrase.prototype.getBase64Async = function(password, cb) {
- var self = this;
- setTimeout(function() {
- var ret = self.getBase64(password);
- return cb(ret);
- }, 0);
-};
-
-module.exports = Passphrase;
diff --git a/js/models/PluginManager.js b/js/models/PluginManager.js
index 57eaea6b9..2a2788b61 100644
--- a/js/models/PluginManager.js
+++ b/js/models/PluginManager.js
@@ -13,7 +13,12 @@ function PluginManager(config) {
continue;
log.info('Loading plugin: ' + pluginName);
- var pluginClass = require('../plugins/' + pluginName);
+ var pluginClass;
+ if(config.pluginsPath){
+ pluginClass = require(config.pluginsPath + pluginName);
+ } else {
+ pluginClass = require('../plugins/' + pluginName);
+ }
var pluginObj = new pluginClass(config[pluginName]);
pluginObj.init();
this._register(pluginObj, pluginName);
@@ -24,15 +29,15 @@ var KIND_UNIQUE = PluginManager.KIND_UNIQUE = 1;
var KIND_MULTIPLE = PluginManager.KIND_MULTIPLE = 2;
PluginManager.TYPE = {};
-PluginManager.TYPE['STORAGE'] = KIND_UNIQUE;
+PluginManager.TYPE['DB'] = KIND_UNIQUE;
PluginManager.prototype._register = function(obj, name) {
preconditions.checkArgument(obj.type, 'Plugin has not type:' + name);
var type = obj.type;
var kind = PluginManager.TYPE[type];
- preconditions.checkArgument(kind, 'Plugin has unknown type' + name);
- preconditions.checkState(kind !== PluginManager.KIND_UNIQUE || !this.registered[type], 'Plugin kind already registered: ' + name);
+ preconditions.checkArgument(kind, 'Unknown plugin type:' + name);
+ preconditions.checkState(kind !== PluginManager.KIND_UNIQUE || !this.registered[type], 'Plugin kind already registered:' + name);
if (kind === PluginManager.KIND_UNIQUE) {
this.registered[type] = obj;
diff --git a/js/models/PrivateKey.js b/js/models/PrivateKey.js
index e8dcd8737..9ae522814 100644
--- a/js/models/PrivateKey.js
+++ b/js/models/PrivateKey.js
@@ -7,7 +7,7 @@ var HK = bitcore.HierarchicalKey;
var WalletKey = bitcore.WalletKey;
var networks = bitcore.networks;
var util = bitcore.util;
-var _ = require('underscore');
+var _ = require('lodash');
var preconditions = require('preconditions').instance();
var HDPath = require('./HDPath');
diff --git a/js/models/PublicKeyRing.js b/js/models/PublicKeyRing.js
index 9512986ed..0f4679060 100644
--- a/js/models/PublicKeyRing.js
+++ b/js/models/PublicKeyRing.js
@@ -1,7 +1,7 @@
'use strict';
var preconditions = require('preconditions').instance();
-var _ = require('underscore');
+var _ = require('lodash');
var log = require('../log');
var bitcore = require('bitcore');
var HK = bitcore.HierarchicalKey;
@@ -23,7 +23,6 @@ var HDParams = require('./HDParams');
* @param {Object[]} [opts.indexes] - an array to be deserialized using {@link HDParams#fromList}
* (defaults to all indexes in zero)
* @param {Object=} opts.nicknameFor - nicknames for other copayers
- * @param {boolean[]} [opts.copayersBackup] - whether other copayers have backed up their wallets
*/
function PublicKeyRing(opts) {
opts = opts || {};
@@ -43,7 +42,6 @@ function PublicKeyRing(opts) {
this.publicKeysCache = {};
this.nicknameFor = opts.nicknameFor || {};
this.copayerIds = [];
- this.copayersBackup = opts.copayersBackup || [];
this.addressToPath = {};
};
@@ -63,7 +61,6 @@ function PublicKeyRing(opts) {
* @param {Object[]} data.indexes - an array of objects that can be turned into
* an array of HDParams
* @param {Object} data.nicknameFor - a registry of nicknames for other copayers
- * @param {boolean[]} data.copayersBackup - whether copayers have backed up their wallets
* @param {string[]} data.copayersExtPubKeys - the extended public keys of copayers
* @returns {Object} a trimmed down version of PublicKeyRing that can be used
* as a parameter
@@ -71,7 +68,7 @@ function PublicKeyRing(opts) {
PublicKeyRing.trim = function(data) {
var opts = {};
['walletId', 'networkName', 'requiredCopayers', 'totalCopayers',
- 'indexes', 'nicknameFor', 'copayersBackup', 'copayersExtPubKeys'
+ 'indexes', 'nicknameFor', 'copayersExtPubKeys'
].forEach(function(k) {
opts[k] = data[k];
});
@@ -119,7 +116,6 @@ PublicKeyRing.prototype.toObj = function() {
requiredCopayers: this.requiredCopayers,
totalCopayers: this.totalCopayers,
indexes: HDParams.serialize(this.indexes),
- copayersBackup: this.copayersBackup,
copayersExtPubKeys: this.copayersHK.map(function(b) {
return b.extendedPublicKeyString();
@@ -497,7 +493,7 @@ PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayer
var isOwned = index.copayerIndex === HDPath.SHARED_INDEX || index.copayerIndex === copayerIndex;
var ret = [];
var appendAddressInfo = function(address, isChange) {
- ret.unshift({
+ ret.push({
address: address,
addressStr: address.toString(),
isChange: isChange,
@@ -685,48 +681,6 @@ PublicKeyRing.prototype._mergePubkeys = function(inPKR) {
return hasChanged;
};
-/**
- * @desc
- * Mark backup as done for us
- *
- * @TODO: REVIEW FUNCTIONALITY - it used to have a parameter that was not used at all!
- *
- * @return {boolean} true if everybody has backed up their wallet
- */
-PublicKeyRing.prototype.setBackupReady = function() {
- if (this.isBackupReady()) return false;
-
- var cid = this.myCopayerId();
- this.copayersBackup.push(cid);
- return true;
-};
-
-/**
- * @desc returns true if a copayer has backed up his wallet
- * @param {string=} copayerId - the pubkey of a copayer, defaults to our own's
- * @return {boolean} if this copayer has backed up
- */
-PublicKeyRing.prototype.isBackupReady = function(copayerId) {
- var cid = copayerId || this.myCopayerId();
- return this.copayersBackup.indexOf(cid) != -1;
-};
-
-/**
- * @desc returns true if all copayers have backed up their wallets
- * @return {boolean}
- */
-PublicKeyRing.prototype.isFullyBackup = function() {
- return this.remainingBackups() == 0;
-};
-
-/**
- * @desc returns the amount of backups remaining
- * @return {boolean}
- */
-PublicKeyRing.prototype.remainingBackups = function() {
- return this.totalCopayers - this.copayersBackup.length;
-};
-
/**
* @desc
* Merges this public key ring with another one, optionally ignoring the
@@ -742,7 +696,6 @@ PublicKeyRing.prototype.merge = function(inPKR, ignoreId) {
var hasChanged = false;
hasChanged |= this.mergeIndexes(inPKR.indexes);
hasChanged |= this._mergePubkeys(inPKR);
- hasChanged |= this._mergeBackups(inPKR.copayersBackup);
return !!hasChanged;
};
@@ -768,22 +721,5 @@ PublicKeyRing.prototype.mergeIndexes = function(indexes) {
return !!hasChanged
}
-/**
- * @desc merges information about backups done by another copy of PublicKeyRing
- * @param {string[]} backups - another copy of backups
- * @return {boolean} true if the internal state has changed
- */
-PublicKeyRing.prototype._mergeBackups = function(backups) {
- var self = this;
- var hasChanged = false;
-
- backups.forEach(function(cid) {
- var isNew = self.copayersBackup.indexOf(cid) == -1;
- if (isNew) self.copayersBackup.push(cid);
- hasChanged |= isNew;
- });
-
- return !!hasChanged
-};
module.exports = PublicKeyRing;
diff --git a/js/models/Storage.js b/js/models/Storage.js
deleted file mode 100644
index 3747971de..000000000
--- a/js/models/Storage.js
+++ /dev/null
@@ -1,360 +0,0 @@
-'use strict';
-var preconditions = require('preconditions').singleton();
-var CryptoJS = require('node-cryptojs-aes').CryptoJS;
-var bitcore = require('bitcore');
-var preconditions = require('preconditions').instance();
-var _ = require('underscore');
-var CACHE_DURATION = 1000 * 60 * 5;
-var id = 0;
-
-function Storage(opts) {
- opts = opts || {};
-
- this.wListCache = {};
- this.__uniqueid = ++id;
- if (opts.password)
- this.setPassphrase(opts.password);
-
- try {
- this.storage = opts.storage || localStorage;
- this.sessionStorage = opts.sessionStorage || sessionStorage;
- } catch (e) {
- console.log('Error in storage:', e); //TODO
- };
-
- preconditions.checkState(this.storage, 'No storage defined');
- preconditions.checkState(this.sessionStorage, 'No sessionStorage defined');
-}
-
-var pps = {};
-Storage.prototype._getPassphrase = function() {
-
- if (!pps[this.__uniqueid])
- throw new Error('NOPASSPHRASE: No passphrase set');
-
- return pps[this.__uniqueid];
-}
-
-Storage.prototype.setPassphrase = function(password) {
- pps[this.__uniqueid] = password;
-}
-
-Storage.prototype._encrypt = function(string) {
- var encrypted = CryptoJS.AES.encrypt(string, this._getPassphrase());
- var encryptedBase64 = encrypted.toString();
- return encryptedBase64;
-};
-
-Storage.prototype._decrypt = function(base64) {
- var decryptedStr = null;
- try {
- var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase());
- if (decrypted)
- decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
- } catch (e) {
- // Error while decrypting
- return null;
- }
- return decryptedStr;
-};
-
-
-Storage.prototype._read = function(k, cb) {
- preconditions.checkArgument(cb);
-
- var self = this;
- this.storage.getItem(k, function(ret) {
- if (!ret) return cb(null);
- var ret = self._decrypt(ret);
- if (!ret) return cb(null);
-
- ret = ret.toString(CryptoJS.enc.Utf8);
- ret = JSON.parse(ret);
- return cb(ret);
- });
-};
-
-Storage.prototype._write = function(k, v, cb) {
- preconditions.checkArgument(cb);
-
- v = JSON.stringify(v);
- v = this._encrypt(v);
- this.storage.setItem(k, v, cb);
-};
-
-// get value by key
-Storage.prototype.getGlobal = function(k, cb) {
- preconditions.checkArgument(cb);
-
- this.storage.getItem(k, function(item) {
- cb(item == 'undefined' ? undefined : item);
- });
-};
-
-// set value for key
-Storage.prototype.setGlobal = function(k, v, cb) {
- preconditions.checkArgument(cb);
- this.storage.setItem(k, typeof v === 'object' ? JSON.stringify(v) : v, cb);
-};
-
-// remove value for key
-Storage.prototype.removeGlobal = function(k, cb) {
- preconditions.checkArgument(cb);
- this.storage.removeItem(k, cb);
-};
-
-Storage.prototype.getSessionId = function(cb) {
- preconditions.checkArgument(cb);
- var self = this;
-
- self.sessionStorage.getItem('sessionId', function(sessionId) {
- if (sessionId)
- return cb(sessionId);
-
- sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex');
- self.sessionStorage.setItem('sessionId', sessionId, function() {
- return cb(sessionId);
- });
- });
-};
-
-Storage.prototype.setSessionId = function(sessionId, cb) {
- this.sessionStorage.setItem('sessionId', sessionId, cb);
-};
-
-Storage.prototype._readHelper = function(walletId, k, cb) {
- var wk = this._key(walletId, k);
- this._read(wk, function(v) {
- return cb(v, k);
- });
-};
-
-Storage.prototype.readWallet_Old = function(walletId, cb) {
- var self = this;
- this.storage.allKeys(function(allKeys) {
- var obj = {};
- var keys = _.filter(allKeys, function(k) {
- if (k.indexOf(walletId + '::') === 0) return true;
- });
- if (keys.length === 0) return cb(new Error('Wallet ' + walletId + ' not found'));
- var count = keys.length;
- _.each(keys, function(k) {
- self._read(k, function(v) {
- obj[k.split('::')[1]] = v;
- if (--count === 0) return cb(null, obj);
- })
- });
- });
-};
-
-Storage.prototype.readWallet = function(walletId, cb) {
- var self = this;
- this.storage.allKeys(function(allKeys) {
- var keys = _.filter(allKeys, function(k) {
- if ((k === 'wallet::' + walletId) || k.indexOf('wallet::' + walletId) === 0) return true;
- });
- if (keys.length === 0) return cb(new Error('Wallet ' + walletId + ' not found'));
- self._read(keys[0], function(v) {
- if (_.isNull(v)) return cb(new Error('Could not decrypt wallet data'));
- return cb(null, v);
- })
- });
-};
-
-Storage.prototype.getMany = function(walletId, keys, cb) {
- preconditions.checkArgument(cb);
-
- var self = this;
- var ret = {};
-
- var l = keys.length,
- i = 0;
-
- for (var ii in keys) {
- this._readHelper(walletId, keys[ii], function(v, k) {
- ret[k] = v;
- if (++i == l) {
- return cb(ret);
- }
- });
- }
-};
-
-Storage.prototype._getWalletIds = function(cb) {
- preconditions.checkArgument(cb);
- var walletIds = [];
- var uniq = {};
- this.storage.allKeys(function(keys) {
- for (var ii in keys) {
- var key = keys[ii];
- var split = key.split('::');
- if (split.length == 2) {
- var walletId = split[0];
-
- if (!walletId || walletId === 'nameFor' || walletId === 'lock' || walletId === 'wallet')
- continue;
-
- if (typeof uniq[walletId] === 'undefined') {
- walletIds.push(walletId);
- uniq[walletId] = 1;
- }
- }
- }
- return cb(walletIds);
- });
-};
-
-Storage.prototype.getWallets_Old = function(cb) {
- preconditions.checkArgument(cb);
-
- if (this.wListCache.ts > Date.now())
- return cb(this.wListCache.data)
-
- var wallets = [];
- var self = this;
-
- this._getWalletIds(function(ids) {
- var l = ids.length,
- i = 0;
- if (!l)
- return cb([]);
-
- _.each(ids, function(id) {
- self.getGlobal('nameFor::' + id, function(name) {
- wallets.push({
- id: id,
- name: name,
- });
- if (++i == l) {
- self.wListCache.data = wallets;
- self.wListCache.ts = Date.now() + CACHE_DURATION;
- return cb(wallets);
- }
- });
- });
- });
-};
-
-Storage.prototype.getWallets2 = function(cb) {
- var self = this;
- var re = /wallet::([^_]+)(_?(.*))/;
-
- this.storage.allKeys(function(allKeys) {
- var wallets = _.compact(_.map(allKeys, function(key) {
- if (key.indexOf('wallet::') !== 0)
- return null;
- var match = key.match(re);
- if (match.length != 4)
- return null;
- return {
- id: match[1],
- name: match[3] ? match[3] : undefined,
- };
- }));
-
- return cb(wallets);
- });
-};
-
-Storage.prototype.getWallets = function(cb) {
- var self = this;
- self.getWallets2(function(wallets) {
- self.getWallets_Old(function(wallets2) {
- var ids = _.pluck(wallets, 'id');
- _.each(wallets2, function(w) {
- if (!_.contains(ids, w.id))
- wallets.push(w);
- });
- return cb(wallets);
- });
- })
-};
-
-
-Storage.prototype.deleteWallet_Old = function(walletId, cb) {
- preconditions.checkArgument(walletId);
- preconditions.checkArgument(cb);
- var err;
- var self = this;
-
- var toDelete = {};
-
- this.storage.allKeys(function(allKeys) {
- for (var ii in allKeys) {
- var key = allKeys[ii];
- var split = key.split('::');
- if (split.length == 2 && split[0] === walletId) {
- toDelete[key] = 1;
- };
- }
- var l = Object.keys(toDelete).length,
- j = 0;
- if (!l)
- return cb(new Error('WNOTFOUND: Wallet not found'));
-
- toDelete['nameFor::' + walletId] = 1;
- l++;
-
- for (var i in toDelete) {
- self.removeGlobal(i, function() {
- if (++j == l)
- return cb(err);
- });
-
- }
- });
-};
-
-Storage.prototype.deleteWallet = function(walletId, cb) {
- preconditions.checkArgument(walletId);
- preconditions.checkArgument(cb);
-
- var self = this;
- this.getWallets2(function(wallets) {
- var w = _.findWhere(wallets, {
- id: walletId
- });
- if (!w)
- return cb(new Error('WNOTFOUND: Wallet not found'));
- self.removeGlobal('wallet::' + walletId + (w.name ? '_' + w.name : ''), function() {
- return cb();
- });
- });
-};
-
-Storage.prototype.setLastOpened = function(walletId, cb) {
- this.setGlobal('lastOpened', walletId, cb);
-};
-
-Storage.prototype.getLastOpened = function(cb) {
- this.getGlobal('lastOpened', cb);
-};
-
-Storage.prototype.setFromObj = function(walletId, obj, cb) {
- preconditions.checkArgument(cb);
- var self = this;
-
- var key = 'wallet::' + walletId + ((obj.opts && obj.opts.name) ? '_' + obj.opts.name : '');
- self._write(key, obj, function() {
- return cb();
- });
-};
-
-
-// remove all values
-Storage.prototype.clearAll = function(cb) {
- this.storage.clear(cb);
-};
-
-Storage.prototype.import = function(base64) {
- var decryptedStr = this._decrypt(base64);
- return JSON.parse(decryptedStr);
-};
-
-Storage.prototype.export = function(obj) {
- var string = JSON.stringify(obj);
- return this._encrypt(string);
-};
-
-
-module.exports = Storage;
diff --git a/js/models/TxProposal.js b/js/models/TxProposal.js
index 6570db64e..ae9de2dd9 100644
--- a/js/models/TxProposal.js
+++ b/js/models/TxProposal.js
@@ -1,7 +1,7 @@
'use strict';
var bitcore = require('bitcore');
-var _ = require('underscore');
+var _ = require('lodash');
var util = bitcore.util;
var Transaction = bitcore.Transaction;
var BuilderMockV0 = require('./BuilderMockV0');;
@@ -25,7 +25,6 @@ function TxProposal(opts) {
this.version = opts.version;
this.builder = opts.builder;
this.createdTs = opts.createdTs;
- this.createdTs = opts.createdTs;
this._inputSigners = [];
// CopayerIds
diff --git a/js/models/Wallet.js b/js/models/Wallet.js
index b9943a434..12935d98b 100644
--- a/js/models/Wallet.js
+++ b/js/models/Wallet.js
@@ -1,13 +1,15 @@
'use strict';
var EventEmitter = require('events').EventEmitter;
-var _ = require('underscore');
-var async = require('async');
+var _ = require('lodash');
var preconditions = require('preconditions').singleton();
var inherits = require('inherits');
var events = require('events');
+var async = require('async');
+var cryptoUtil = require('../util/crypto');
var bitcore = require('bitcore');
+var BIP21 = bitcore.BIP21;
var bignum = bitcore.Bignum;
var coinUtil = bitcore.util;
var buffertools = bitcore.buffertools;
@@ -24,7 +26,8 @@ var PublicKeyRing = require('./PublicKeyRing');
var TxProposal = require('./TxProposal');
var TxProposals = require('./TxProposals');
var PrivateKey = require('./PrivateKey');
-var WalletLock = require('./WalletLock');
+var Async = require('./Async');
+var Insight = module.exports.Insight = require('./Insight');
var copayConfig = require('../../config');
/**
@@ -35,7 +38,6 @@ var copayConfig = require('../../config');
* @TODO: Split this leviathan.
*
* @param {Object} opts
- * @param {Storage} opts.storage - an object that can persist the wallet
* @param {Network} opts.network - used to send and retrieve messages from
* copayers
* @param {Blockchain} opts.blockchain - source of truth for what happens in
@@ -57,9 +59,18 @@ var copayConfig = require('../../config');
*/
function Wallet(opts) {
var self = this;
+ preconditions.checkArgument(opts);
+
+ opts.reconnectDelay = opts.reconnectDelay || 500;
+
+ var networkName = Wallet.obtainNetworkName(opts);
+ preconditions.checkState((opts.network && opts.blockchain) || networkName);
+
+ opts.network = opts.network || Wallet._newAsync(opts.networkOpts[networkName]);
+ opts.blockchain = opts.blockchain || Wallet._newInsight(opts.blockchainOpts[networkName]);;
//required params
- ['storage', 'network', 'blockchain',
+ ['network', 'blockchain',
'requiredCopayers', 'totalCopayers', 'spendUnconfirmed',
'publicKeyRing', 'txProposals', 'privateKey', 'version',
'reconnectDelay'
@@ -68,31 +79,33 @@ function Wallet(opts) {
self[k] = opts[k];
});
+
this.id = opts.id || Wallet.getRandomId();
- this.secretNumber = opts.secretNumber || Wallet.getRandomNumber();
- this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin);
+ this.secretNumber = opts.secretNumber || Wallet.getRandomSecretNumber();
+ // TODO
+ // this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin);
this.settings = opts.settings || copayConfig.wallet.settings;
this.name = opts.name;
this.publicKeyRing.walletId = this.id;
this.txProposals.walletId = this.id;
- this.network.maxPeers = this.totalCopayers;
- this.network.secretNumber = this.secretNumber;
this.registeredPeerIds = [];
this.addressBook = opts.addressBook || {};
this.publicKey = this.privateKey.publicHex;
- this.lastTimestamp = opts.lastTimestamp || undefined;
+ this.lastTimestamp = opts.lastTimestamp || 0;
this.lastMessageFrom = {};
- //to avoid confirmation of copayer's backups if is imported from a file
- this.isImported = opts.isImported || false;
-
-
- //to avoid waiting others copayers to make a backup and login immediatly
- this.forcedLogin = opts.forcedLogin || false;
-
this.paymentRequests = opts.paymentRequests || {};
+ var networkName = Wallet.obtainNetworkName(opts);
+
+ preconditions.checkArgument(this.network.setHexNonce, 'Incorrect network parameter');
+ preconditions.checkArgument(this.blockchain.getTransaction, 'Incorrect blockchain parameter');
+
+
+ this.network.maxPeers = this.totalCopayers;
+ this.network.secretNumber = this.secretNumber;
+
//network nonces are 8 byte buffers, representing a big endian number
//one nonce for oneself, and then one nonce for each copayer
this.network.setHexNonce(opts.networkNonce);
@@ -101,6 +114,12 @@ function Wallet(opts) {
inherits(Wallet, events.EventEmitter);
+Wallet.prototype.emitAndKeepAlive = function(args) {
+ log.debug('Wallet Emitting:', arguments);
+ this.keepAlive();
+ this.emit.apply(this, arguments);
+};
+
/**
* @TODO: Document this. Its usage is kind of weird
*
@@ -128,8 +147,8 @@ Wallet.PERSISTED_PROPERTIES = [
'txProposals',
'privateKey',
'addressBook',
- 'backupOffered',
'lastTimestamp',
+ 'secretNumber',
];
Wallet.COPAYER_PAIR_LIMITS = {
@@ -147,6 +166,24 @@ Wallet.COPAYER_PAIR_LIMITS = {
12: 1,
};
+Wallet.getStorageKey = function(str) {
+ return 'wallet::' + str;
+};
+
+Wallet.prototype.getStorageKey = function() {
+ return Wallet.getStorageKey(this.getId());
+};
+
+/* for stubbing */
+Wallet._newInsight = function(opts) {
+ return new Insight(opts);
+};
+
+/* for stubbing */
+Wallet._newAsync = function(opts) {
+ return new Async(opts);
+};
+
/**
* @desc Retrieve a random id for the wallet
* @TODO: Discuss changing to a UUID
@@ -158,11 +195,11 @@ Wallet.getRandomId = function() {
};
/**
- * @desc Get a random 8 byte number and encode it as a hexa string
- * @return {string}
+ * @desc Retrieve a random secret number to secure wallet secret
+ * @return {string} 5 bytes, hexa encoded
*/
-Wallet.getRandomNumber = function() {
- var r = bitcore.SecureRandom.getPseudoRandomBuffer(5).toString('hex');
+Wallet.getRandomSecretNumber = function() {
+ var r = bitcore.SecureRandom.getPseudoRandomBuffer(5).toString('hex')
return r;
};
@@ -177,6 +214,19 @@ Wallet.getMaxRequiredCopayers = function(totalCopayers) {
return Wallet.COPAYER_PAIR_LIMITS[totalCopayers];
};
+/**
+ * @desc obtain network name from serialized wallet
+ * @param {Object} wallet object
+ * @return {string} network name
+ */
+Wallet.obtainNetworkName = function(obj) {
+ return obj.networkName ||
+ (obj.opts ? obj.opts.networkName : null) ||
+ (obj.publicKeyRing ? (obj.publicKeyRing.networkName || obj.publicKeyRing.network.name) : null) ||
+ (obj.privateKey ? obj.privateKey.networkName : null);
+};
+
+
/**
* @desc Set the copayer id for the owner of this wallet
@@ -186,6 +236,23 @@ Wallet.prototype.seedCopayer = function(pubKey) {
this.seededCopayerId = pubKey;
};
+
+Wallet.prototype._newAddresses = function(dontUpdateUx) {
+ if (this.publicKeyRing.isComplete()) {
+ this.subscribeToAddresses();
+
+ };
+ this.emitAndKeepAlive('newAddresses', dontUpdateUx);
+};
+
+
+Wallet.prototype._publicKeyRingUpdated = function(isComplete) {
+ if (isComplete) {
+ this.subscribeToAddresses();
+ };
+ this.emitAndKeepAlive('publicKeyRingUpdated');
+};
+
/**
* @desc Handles an 'indexes' message.
*
@@ -196,15 +263,13 @@ Wallet.prototype.seedCopayer = function(pubKey) {
*
* @param {string} senderId - the sender id
* @param {Object} data - the data recived, {@see HDParams#fromList}
- * @emits publicKeyRingUpdated
*/
Wallet.prototype._onIndexes = function(senderId, data) {
- log.debug('RECV INDEXES:', data);
+ log.debug('Wallet:' + this.id + ' RECV INDEXES:', data);
var inIndexes = HDParams.fromList(data.indexes);
var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes);
if (hasChanged) {
- this.emit('publicKeyRingUpdated');
- this.store();
+ this._newAddresses();
}
};
@@ -221,7 +286,7 @@ Wallet.prototype._onIndexes = function(senderId, data) {
*/
Wallet.prototype.changeSettings = function(settings) {
this.settings = settings;
- this.store();
+ this.emitAndKeepAlive('settingsUpdated');
};
/**
@@ -241,11 +306,10 @@ Wallet.prototype.changeSettings = function(settings) {
* @param {Object} data - the data recived, {@see HDParams#fromList}
* @param {Object} data.publicKeyRing - data to be deserialized into a {@link PublicKeyRing}
* using {@link PublicKeyRing#fromObj}
- * @emits publicKeyRingUpdated
* @emits connectionError
*/
Wallet.prototype._onPublicKeyRing = function(senderId, data) {
- log.debug('RECV PUBLICKEYRING:', data);
+ log.debug('Wallet:' + this.id + ' RECV PUBLICKEYRING:', data);
var inPKR = PublicKeyRing.fromObj(data.publicKeyRing);
var wasIncomplete = !this.publicKeyRing.isComplete();
@@ -254,11 +318,10 @@ Wallet.prototype._onPublicKeyRing = function(senderId, data) {
try {
hasChanged = this.publicKeyRing.merge(inPKR, true);
} catch (e) {
- log.debug('## WALLET ERROR', e);
- this.emit('connectionError', e.message);
+ log.debug('Wallet:' + this.id + '## WALLET ERROR', e);
+ this.emitAndKeepAlive('connectionError', e.message);
return;
}
-
if (hasChanged) {
if (wasIncomplete) {
this.sendPublicKeyRing();
@@ -266,8 +329,8 @@ Wallet.prototype._onPublicKeyRing = function(senderId, data) {
if (this.publicKeyRing.isComplete()) {
this._lockIncomming();
}
- this.emit('publicKeyRingUpdated');
- this.store();
+
+ this._publicKeyRingUpdated(this.publicKeyRing.isComplete());
}
};
@@ -300,7 +363,7 @@ Wallet.prototype._processProposalEvents = function(senderId, m) {
};
}
if (ev)
- this.emit('txProposalEvent', ev);
+ this.emitAndKeepAlive('txProposalEvent', ev);
};
@@ -383,7 +446,7 @@ Wallet.prototype._checkSentTx = function(ntxid, cb) {
*/
Wallet.prototype._onTxProposal = function(senderId, data) {
var self = this;
- log.debug('RECV TXPROPOSAL: ', data);
+ log.debug('Wallet:' + this.id + ' RECV TXPROPOSAL: ', data);
var m;
try {
@@ -407,8 +470,7 @@ Wallet.prototype._onTxProposal = function(senderId, data) {
if (ret) {
if (!m.txp.getSent()) {
m.txp.setSent(m.ntxid);
- self.emit('txProposalsUpdated');
- self.store();
+ self.emitAndKeepAlive('txProposalsUpdated');
}
}
});
@@ -418,8 +480,7 @@ Wallet.prototype._onTxProposal = function(senderId, data) {
}
}
- this.emit('txProposalsUpdated');
- this.store();
+ this.emitAndKeepAlive('txProposalsUpdated');
}
this._processProposalEvents(senderId, m);
};
@@ -436,7 +497,7 @@ Wallet.prototype._onTxProposal = function(senderId, data) {
*/
Wallet.prototype._onReject = function(senderId, data) {
preconditions.checkState(data.ntxid);
- log.debug('RECV REJECT:', data);
+ log.debug('Wallet:' + this.id + ' RECV REJECT:', data);
var txp = this.txProposals.get(data.ntxid);
@@ -447,10 +508,7 @@ Wallet.prototype._onReject = function(senderId, data) {
throw new Error('Received Reject for an already signed TX from:' + senderId);
txp.setRejected(senderId);
- this.store();
-
- this.emit('txProposalsUpdated');
- this.emit('txProposalEvent', {
+ this.emitAndKeepAlive('txProposalEvent', {
type: 'rejected',
cId: senderId,
txId: data.ntxid,
@@ -469,13 +527,11 @@ Wallet.prototype._onReject = function(senderId, data) {
*/
Wallet.prototype._onSeen = function(senderId, data) {
preconditions.checkState(data.ntxid);
- log.debug('RECV SEEN:', data);
+ log.debug('Wallet:' + this.id + ' RECV SEEN:', data);
var txp = this.txProposals.get(data.ntxid);
txp.setSeen(senderId);
- this.store();
- this.emit('txProposalsUpdated');
- this.emit('txProposalEvent', {
+ this.emitAndKeepAlive('txProposalEvent', {
type: 'seen',
cId: senderId,
txId: data.ntxid,
@@ -497,7 +553,7 @@ Wallet.prototype._onSeen = function(senderId, data) {
*/
Wallet.prototype._onAddressBook = function(senderId, data) {
preconditions.checkState(data.addressBook);
- log.debug('RECV ADDRESSBOOK:', data);
+ log.debug('Wallet:' + this.id + ' RECV ADDRESSBOOK:', data);
var rcv = data.addressBook;
var hasChange;
for (var key in rcv) {
@@ -510,8 +566,7 @@ Wallet.prototype._onAddressBook = function(senderId, data) {
}
}
if (hasChange) {
- this.emit('addressBookUpdated');
- this.store();
+ this.emitAndKeepAlive('addressBookUpdated');
}
};
@@ -519,11 +574,14 @@ Wallet.prototype._onAddressBook = function(senderId, data) {
* @desc Updates the wallet's last modified timestamp and triggers a save
* @param {number} ts - the timestamp
*/
-Wallet.prototype.updateTimestamp = function(ts) {
+Wallet.prototype.updateTimestamp = function(ts, callback) {
preconditions.checkArgument(ts);
preconditions.checkArgument(_.isNumber(ts));
this.lastTimestamp = ts;
- this.store();
+ // we dont store here
+ if (callback) {
+ return callback(null);
+ }
};
/**
@@ -531,8 +589,9 @@ Wallet.prototype.updateTimestamp = function(ts) {
* Triggers a call to {@link Wallet#sendWalletReady}
*/
Wallet.prototype._onNoMessages = function() {
- log.debug('No messages at the server. Requesting peer sync from: ' + this.lastTimestamp + 1); //TODO
+ log.debug('Wallet:' + this.id + ' No messages at the server. Requesting peer sync from: ' + (this.lastTimestamp + 1));
this.sendWalletReady(null, parseInt((this.lastTimestamp + 1) / 1000));
+ this.updateTimestamp(parseInt(Date.now() / 1000));
};
/**
@@ -550,12 +609,13 @@ Wallet.prototype._onData = function(senderId, data, ts) {
preconditions.checkArgument(data.type);
preconditions.checkArgument(ts);
preconditions.checkArgument(_.isNumber(ts));
- log.debug('RECV', senderId, data);
+ log.debug('Wallet:' + this.id + ' RECV', senderId, data);
+
+ this.updateTimestamp(ts);
if (data.type !== 'walletId' && this.id !== data.walletId) {
- log.debug('Received corrupt message:', data)
- this.emit('corrupt', senderId);
- this.updateTimestamp(ts);
+ log.debug('Wallet:' + this.id + ' Received corrupt message:', data)
+ this.emitAndKeepAlive('corrupt', senderId);
return;
}
@@ -565,8 +625,9 @@ Wallet.prototype._onData = function(senderId, data, ts) {
this.sendWalletReady(senderId);
break;
case 'walletReady':
+
if (this.lastMessageFrom[senderId] !== 'walletReady') {
- log.debug('peer Sync received. since: ' + (data.sinceTs || 0));
+ log.debug('Wallet:' + this.id + ' peer Sync received. since: ' + (data.sinceTs || 0));
this.sendPublicKeyRing(senderId);
this.sendAddressBook(senderId);
this.sendAllTxProposals(senderId, data.sinceTs); // send old txps
@@ -597,9 +658,8 @@ Wallet.prototype._onData = function(senderId, data, ts) {
default:
throw new Error('unknown message type received: ' + data.type + ' from: ' + senderId)
}
-
this.lastMessageFrom[senderId] = data.type;
- this.updateTimestamp(ts);
+
};
/**
@@ -609,11 +669,12 @@ Wallet.prototype._onData = function(senderId, data, ts) {
*/
Wallet.prototype._onConnect = function(newCopayerId) {
if (newCopayerId) {
- log.debug('#### Setting new COPAYER:', newCopayerId);
+ log.debug('Wallet:' + this.id + '#### Setting new COPAYER:', newCopayerId);
this.sendWalletId(newCopayerId);
}
- var peerID = this.network.peerFromCopayer(newCopayerId)
- this.emit('connect', peerID);
+
+ var peerID = this.network.peerFromCopayer(newCopayerId);
+ this.emitAndKeepAlive('connect', peerID);
};
/**
@@ -624,6 +685,15 @@ Wallet.prototype.getNetworkName = function() {
return this.publicKeyRing.network.name;
};
+/**
+ * @return {bool}
+ */
+Wallet.prototype.isTestnet = function() {
+ return this.publicKeyRing.network.name === 'testnet';
+};
+
+
+
/**
* @desc Serialize options into an object
* @return {Object} with keys id, spendUnconfirmed,
@@ -682,8 +752,6 @@ Wallet.prototype.getMyCopayerNickname = function() {
* @return {string} my own pubkey, base58 encoded
*/
Wallet.prototype.getSecretNumber = function() {
- if (this.secretNumber) return this.secretNumber;
- this.secretNumber = Wallet.getRandomNumber();
return this.secretNumber;
};
@@ -708,14 +776,19 @@ Wallet.prototype.getSecret = function() {
* @return {Object}
*/
Wallet.decodeSecret = function(secretB) {
- var secret = Base58Check.decode(secretB);
- var pubKeyBuf = secret.slice(0, 33);
- var secretNumber = secret.slice(33, 38);
- var networkName = secret.slice(38, 39).toString('hex') === '00' ? 'livenet' : 'testnet';
- return {
- pubKey: pubKeyBuf.toString('hex'),
- secretNumber: secretNumber.toString('hex'),
- networkName: networkName,
+ try {
+ var secret = Base58Check.decode(secretB);
+ var pubKeyBuf = secret.slice(0, 33);
+ var secretNumber = secret.slice(33, 38);
+ var networkName = secret.slice(38, 39).toString('hex') === '00' ? 'livenet' : 'testnet';
+ return {
+ pubKey: pubKeyBuf.toString('hex'),
+ secretNumber: secretNumber.toString('hex'),
+ networkName: networkName,
+ }
+ } catch (e) {
+ log.debug(e.message);
+ return false;
}
};
@@ -727,34 +800,33 @@ Wallet.prototype._lockIncomming = function() {
this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds());
};
-
-
Wallet.prototype._setBlockchainListeners = function() {
var self = this;
- this.blockchain.removeAllListeners();
+ self.blockchain.removeAllListeners();
- this.blockchain.on('reconnect', function(attempts) {
- log.debug('blockchain reconnect event');
- self.emit('insightReconnected');
+ log.debug('Setting Blockchain listeners for', this.getId());
+ self.blockchain.on('reconnect', function(attempts) {
+ log.debug('Wallet:' + self.id + 'blockchain reconnect event');
+ self.emitAndKeepAlive('insightReconnected');
});
- this.blockchain.on('disconnect', function() {
- log.debug('blockchain disconnect event');
- self.emit('insightError');
+ self.blockchain.on('disconnect', function() {
+ log.debug('Wallet:' + self.id + 'blockchain disconnect event');
+ self.emitAndKeepAlive('insightError');
});
- this.blockchain.on('tx', function(tx) {
- log.debug('blockchain tx event');
+ self.blockchain.on('tx', function(tx) {
+ log.debug('Wallet:' + self.id + ' blockchain tx event');
var addresses = self.getAddressesInfo();
var addr = _.findWhere(addresses, {
addressStr: tx.address
});
if (addr) {
- self.emit('tx', tx.address, addr.isChange);
+ self.emitAndKeepAlive('tx', tx.address, addr.isChange);
}
});
if (!self.spendUnconfirmed) {
- self.blockchain.on('block', self.emit.bind(self, 'balanceUpdated'));
+ self.blockchain.on('block', self.emitAndKeepAlive.bind(self, 'balanceUpdated'));
}
}
@@ -765,7 +837,6 @@ Wallet.prototype._setBlockchainListeners = function() {
* @emits data
*
* @emits ready
- * @emits publicKeyRingUpdated
* @emits txProposalsUpdated
*
* @TODO: FIX PROTOCOL -- emit with a space is shitty
@@ -779,6 +850,23 @@ Wallet.prototype.netStart = function() {
net.on('connect', self._onConnect.bind(self));
net.on('data', self._onData.bind(self));
net.on('no messages', self._onNoMessages.bind(self));
+ net.on('connect_error', function() {
+ self.emitAndKeepAlive('connectionError');
+ });
+
+ if (this.publicKeyRing.isComplete()) {
+ this._lockIncomming();
+ }
+
+
+
+ if (net.started) {
+ log.debug('Wallet:' + self.id + ' Wallet networking was ready')
+ self.emitAndKeepAlive('ready', net.getPeer());
+ return;
+ }
+
+
var myId = self.getMyCopayerId();
var myIdPriv = self.getMyCopayerIdPriv();
@@ -787,26 +875,19 @@ Wallet.prototype.netStart = function() {
copayerId: myId,
privkey: myIdPriv,
maxPeers: self.totalCopayers,
- lastTimestamp: this.lastTimestamp,
+ lastTimestamp: this.lastTimestamp || 0,
secretNumber: self.secretNumber,
};
- if (this.publicKeyRing.isComplete()) {
- this._lockIncomming();
- }
-
- net.on('connect_error', function() {
- self.emit('connectionError');
- });
+ log.debug('Wallet:' + self.id + ' Starting networking: ' + startOpts.copayerId);
net.start(startOpts, function() {
+ log.debug('Wallet:' + self.id + ' Networking ready:', net.copayerId);
self._setBlockchainListeners();
- self.emit('ready', net.getPeer());
+ self.emitAndKeepAlive('ready', net.getPeer());
setTimeout(function() {
- self.emit('publicKeyRingUpdated', true);
- // no connection logic for now
- self.emit('txProposalsUpdated');
- }, 10);
+ self._newAddresses(true);
+ }, 0);
});
};
@@ -858,26 +939,19 @@ Wallet.prototype.getRegisteredPeerIds = function() {
Wallet.prototype.keepAlive = function() {
var self = this;
- this.lock.keepAlive(function(err) {
- if (err) {
- log.debug(err);
- self.emit('locked', null, 'Wallet appears to be openned on other browser instance. Closing this one.');
- }
- });
+
+ // this.lock.keepAlive(function(err) {
+ // if (err) {
+ // log.debug(err);
+ // self.emitAndKeepAlive('locked', null, 'Wallet appears to be openned on other browser instance. Closing this one.');
+ // }
+ // });
};
-/**
- * @desc Store the wallet's state
- * @param {function} callback (err)
- */
-Wallet.prototype.store = function(cb) {
- var self = this;
- this.keepAlive();
- this.storage.setFromObj(this.id, this.toObj(), function(err) {
- log.debug('Wallet stored');
- if (cb)
- cb(err);
- });
+
+
+Wallet.prototype.getId = function() {
+ return this.id;
};
/**
@@ -896,12 +970,24 @@ Wallet.prototype.toObj = function() {
txProposals: this.txProposals.toObj(),
privateKey: this.privateKey ? this.privateKey.toObj() : undefined,
addressBook: this.addressBook,
- lastTimestamp: this.lastTimestamp,
+ lastTimestamp: this.lastTimestamp || 0,
+ secretNumber: this.secretNumber,
};
return walletObj;
};
+
+Wallet.fromUntrustedObj = function(obj, readOpts) {
+ obj = _.clone(obj);
+ var o = {};
+ _.each(Wallet.PERSISTED_PROPERTIES, function(p) {
+ o[p] = obj[p];
+ });
+
+ return Wallet.fromObj(o, readOpts);
+};
+
/**
* @desc Retrieve the wallet state from a trusted object
*
@@ -913,31 +999,58 @@ Wallet.prototype.toObj = function() {
* @param {number} o.lastTimestamp - last time this wallet object was deserialized
* @param {Object} o.txProposals - TxProposals to be deserialized by {@link TxProposals#fromObj}
* @param {string} o.nickname - user's nickname
- * @param {Storage} storage - a Storage instance to store the data of the wallet
- * @param {Network} network - a Network instance to communicate with peers
- * @param {Blockchain} blockchain - a Blockchain instance to retrieve state from the blockchain
+ *
+ * @param readOpts.network
+ * @param readOpts.blockchain
+ * @param readOpts.{string[]} skipFields - parameters to ignore when importing
*/
-Wallet.fromObj = function(o, storage, network, blockchain) {
+Wallet.fromObj = function(o, readOpts) {
+ preconditions.checkArgument(readOpts.networkOpts);
+ preconditions.checkArgument(readOpts.blockchainOpts);
+
+ var networkOpts = readOpts.networkOpts;
+ var blockchainOpts = readOpts.blockchainOpts;
+ var skipFields = readOpts.skipFields || [];
+
+
+ if (skipFields) {
+ _.each(skipFields, function(k) {
+ if (o[k]) {
+ delete o[k];
+ } else {
+ throw new Error('unknown field:' + k);
+ }
+ });
+ }
+
+ var networkName = Wallet.obtainNetworkName(o);
+
+
+ // TODO Why moving everything to opts. This needs refactoring.
+ //
// clone opts
var opts = JSON.parse(JSON.stringify(o.opts));
opts.addressBook = o.addressBook;
opts.settings = o.settings;
+
if (o.privateKey) {
opts.privateKey = PrivateKey.fromObj(o.privateKey);
} else {
opts.privateKey = new PrivateKey({
- networkName: opts.networkName
+ networkName: networkName
});
}
+ opts.secretNumber = o.secretNumber;
+
if (o.publicKeyRing) {
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
} else {
opts.publicKeyRing = new PublicKeyRing({
- networkName: opts.networkName,
+ networkName: networkName,
requiredCopayers: opts.requiredCopayers,
totalCopayers: opts.totalCopayers,
});
@@ -951,29 +1064,18 @@ Wallet.fromObj = function(o, storage, network, blockchain) {
opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts);
} else {
opts.txProposals = new TxProposals({
- networkName: this.networkName,
+ networkName: networkName,
});
}
- opts.lastTimestamp = o.lastTimestamp;
+ opts.lastTimestamp = o.lastTimestamp || 0;
- opts.storage = storage;
- opts.network = network;
- opts.blockchain = blockchain;
- opts.isImported = true;
+ opts.blockchainOpts = readOpts.blockchainOpts;
+ opts.networkOpts = readOpts.networkOpts;
return new Wallet(opts);
};
-/**
- * @desc Return a base64 encrypted version of the wallet
- * @return {string} base64 encoded string
- */
-Wallet.prototype.toEncryptedObj = function() {
- var walletObj = this.toObj();
- return this.storage.export(walletObj);
-};
-
/**
* @desc Send a message to other peers
* @param {string[]} recipients - the pubkey of the recipients of the message
@@ -1002,7 +1104,7 @@ Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) {
*/
Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
preconditions.checkArgument(ntxid);
- log.debug('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals);
+ log.debug('Wallet:' + this.id + ' ### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals);
this.send(recipients, {
type: 'txProposal',
txProposal: this.txProposals.get(ntxid).toObjTrim(),
@@ -1016,7 +1118,7 @@ Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
*/
Wallet.prototype.sendSeen = function(ntxid) {
preconditions.checkArgument(ntxid);
- log.debug('### SENDING seen: ' + ntxid + ' TO: All');
+ log.debug('Wallet:' + this.id + ' ### SENDING seen: ' + ntxid + ' TO: All');
this.send(null, {
type: 'seen',
ntxid: ntxid,
@@ -1030,7 +1132,7 @@ Wallet.prototype.sendSeen = function(ntxid) {
*/
Wallet.prototype.sendReject = function(ntxid) {
preconditions.checkArgument(ntxid);
- log.debug('### SENDING reject: ' + ntxid + ' TO: All');
+ log.debug('Wallet:' + this.id + ' ### SENDING reject: ' + ntxid + ' TO: All');
this.send(null, {
type: 'reject',
ntxid: ntxid,
@@ -1043,7 +1145,7 @@ Wallet.prototype.sendReject = function(ntxid) {
* @param {string[]} [recipients] - the pubkeys of the recipients
*/
Wallet.prototype.sendWalletReady = function(recipients, sinceTs) {
- log.debug('### SENDING WalletReady TO:', recipients || 'All');
+ log.debug('Wallet:' + this.id + ' ### SENDING WalletReady TO:', recipients || 'All');
this.send(recipients, {
type: 'walletReady',
@@ -1058,7 +1160,7 @@ Wallet.prototype.sendWalletReady = function(recipients, sinceTs) {
* @param {string[]} [recipients] - the pubkeys of the recipients
*/
Wallet.prototype.sendWalletId = function(recipients) {
- log.debug('### SENDING walletId TO:', recipients || 'All', this.id);
+ log.debug('Wallet:' + this.id + ' ### SENDING walletId TO:', recipients || 'All', this.id);
this.send(recipients, {
type: 'walletId',
@@ -1073,7 +1175,7 @@ Wallet.prototype.sendWalletId = function(recipients) {
* @param {string[]} [recipients] - the pubkeys of the recipients
*/
Wallet.prototype.sendPublicKeyRing = function(recipients) {
- log.debug('### SENDING publicKeyRing TO:', recipients || 'All', this.publicKeyRing.toObj());
+ log.debug('Wallet:' + this.id + ' ### SENDING publicKeyRing TO:', recipients || 'All', this.publicKeyRing.toObj());
var publicKeyRing = this.publicKeyRing.toObj();
this.send(recipients, {
@@ -1089,7 +1191,7 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) {
*/
Wallet.prototype.sendIndexes = function(recipients) {
var indexes = HDParams.serialize(this.publicKeyRing.indexes);
- log.debug('### INDEXES TO:', recipients || 'All', indexes);
+ log.debug('Wallet:' + this.id + ' ### INDEXES TO:', recipients || 'All', indexes);
this.send(recipients, {
type: 'indexes',
@@ -1103,7 +1205,8 @@ Wallet.prototype.sendIndexes = function(recipients) {
* @param {string[]} recipients - the pubkeys of the recipients
*/
Wallet.prototype.sendAddressBook = function(recipients) {
- log.debug('### SENDING addressBook TO:', recipients || 'All', this.addressBook);
+ if (!Object.keys(this.addressBook).length) return;
+ log.debug('Wallet:' + this.id + ' ### SENDING addressBook TO:', recipients || 'All', this.addressBook);
this.send(recipients, {
type: 'addressbook',
addressBook: this.addressBook,
@@ -1141,7 +1244,7 @@ Wallet.prototype._doGenerateAddress = function(isChange) {
Wallet.prototype.generateAddress = function(isChange, cb) {
var addr = this._doGenerateAddress(isChange);
this.sendIndexes();
- this.store();
+ this._newAddresses();
if (cb) return cb(addr);
return addr;
};
@@ -1182,7 +1285,7 @@ Wallet.prototype.purgeTxProposals = function(deleteAll) {
} else {
this.txProposals.deletePending(this.maxRejectCount());
}
- this.store();
+ this.emitAndKeepAlive('txProposalsUpdated');
var n = this.txProposals.length();
return m - n;
@@ -1196,8 +1299,7 @@ Wallet.prototype.purgeTxProposals = function(deleteAll) {
Wallet.prototype.reject = function(ntxid) {
var txp = this.txProposals.reject(ntxid, this.getMyCopayerId());
this.sendReject(ntxid);
- this.store();
- this.emit('txProposalsUpdated');
+ this.emitAndKeepAlive('txProposalsUpdated');
};
/**
@@ -1238,8 +1340,7 @@ Wallet.prototype.sign = function(ntxid, cb) {
if (txp.countSignatures() > before) {
txp.signedBy[myId] = Date.now();
self.sendTxProposal(ntxid);
- self.store();
- self.emit('txProposalsUpdated');
+ self.emitAndKeepAlive('txProposalsUpdated');
ret = true;
}
if (cb) return cb(ret);
@@ -1264,26 +1365,26 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
var tx = txp.builder.build();
if (!tx.isComplete())
throw new Error('Tx is not complete. Can not broadcast');
- log.debug('Broadcasting Transaction');
+ log.debug('Wallet:' + this.id + ' Broadcasting Transaction');
var scriptSig = tx.ins[0].getScript();
var size = scriptSig.serialize().length;
var txHex = tx.serialize().toString('hex');
- log.debug('Raw transaction: ', txHex);
+ log.debug('Wallet:' + this.id + ' Raw transaction: ', txHex);
var self = this;
this.blockchain.broadcast(txHex, function(err, txid) {
- log.debug('BITCOIND txid:', txid);
+ log.debug('Wallet:' + self.id + ' BITCOIND txid:', txid);
if (txid) {
self.txProposals.get(ntxid).setSent(txid);
self.sendTxProposal(ntxid);
- self.store();
+ self.emitAndKeepAlive('txProposalsUpdated');
return cb(txid);
} else {
- log.debug('Sent failed. Checking if the TX was sent already');
+ log.debug('Wallet:' + self.id + ' Sent failed. Checking if the TX was sent already');
self._checkSentTx(ntxid, function(txid) {
if (txid)
- self.store();
+ self.emitAndKeepAlive('txProposalsUpdated');
return cb(txid);
});
@@ -1493,7 +1594,6 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) {
if (ntxid) {
self.sendIndexes();
self.sendTxProposal(ntxid);
- self.store();
self.emit('txProposalsUpdated');
}
@@ -1625,8 +1725,8 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
log.debug('Sending to server was not met with a returned tx.');
log.debug('XHR status: ' + status);
return self._checkSentTx(ntxid, function(txid) {
- log.debug('[Wallet.js.1581:txid:%s]', txid);
- if (txid) self.store();
+ if (txid)
+ self.emitAndKeepAlive('txProposalsUpdated');
return cb(txid, txp.merchant);
});
});
@@ -1658,7 +1758,8 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
log.debug('Sending to server was not met with a returned tx.');
return this._checkSentTx(ntxid, function(txid) {
log.debug('[Wallet.js.1613:txid:%s]', txid);
- if (txid) self.store();
+ if (txid)
+ self.emitAndKeepAlive('txProposalUpdated');
return cb(txid, txp.merchant);
});
}
@@ -1685,13 +1786,13 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
if (txid) {
self.txProposals.get(ntxid).setSent(txid);
self.sendTxProposal(ntxid);
- self.store();
+ self.emitAndKeepAlive('txProposalsUpdated');
return cb(txid, txp.merchant);
} else {
log.debug('Sent failed. Checking if the TX was sent already');
self._checkSentTx(ntxid, function(txid) {
if (txid)
- self.store();
+ self.emitAndKeepAlive('txProposalsUpdated');
return cb(txid, txp.merchant);
});
@@ -2018,6 +2119,7 @@ Wallet.prototype.getAddressesStr = function(opts) {
Wallet.prototype.subscribeToAddresses = function() {
var addrInfo = this.publicKeyRing.getAddressesInfo();
this.blockchain.subscribe(_.pluck(addrInfo, 'addressStr'));
+ log.debug('Subscribed to ' + addrInfo.length + ' addresses'); //TODO
};
/**
@@ -2175,8 +2277,7 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) {
});
if (proposalsChanged) {
- self.emit('txProposalsUpdated');
- self.store();
+ self.emitAndKeepAlive('txProposalsUpdated');
}
return cb();
@@ -2211,12 +2312,19 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb)
self.sendIndexes();
self.sendTxProposal(ntxid);
- self.store();
- self.emit('txProposalsUpdated');
+ self.emitAndKeepAlive('txProposalsUpdated');
return cb(null, ntxid);
});
};
+// TODO (eordano): Move this to bitcore
+var sanitize = function(address) {
+ if (/^bitcoin:/g.test(address)) {
+ return new BIP21(address).address;
+ }
+ return new Address(address);
+};
+
/**
* @desc Create a transaction proposal
* @TODO: Document more
@@ -2225,8 +2333,9 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
var pkr = this.publicKeyRing;
var priv = this.privateKey;
opts = opts || {};
+ toAddress = sanitize(toAddress);
- preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName(), 'networkname mismatch');
+ preconditions.checkArgument(toAddress.network().name === this.getNetworkName(), 'networkname mismatch');
preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete');
preconditions.checkState(priv, 'no private key');
if (comment) preconditions.checkArgument(comment.length <= 100);
@@ -2245,11 +2354,11 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
try {
b = new Builder(opts)
- .setUnspent(utxos)
- .setOutputs([{
- address: toAddress,
- amountSatStr: amountSatStr,
- }]);
+ .setUnspent(utxos)
+ .setOutputs([{
+ address: toAddress.data,
+ amountSatStr: amountSatStr,
+ }]);
} catch (e) {
log.debug(e.message);
return;
@@ -2295,11 +2404,10 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
*
* Triggers a wallet {@link Wallet#store} call
* @param {Function} callback - called when all indexes have been updated. Receives an error, if any, as first argument
- * @emits publicKeyRingUpdated
*/
Wallet.prototype.updateIndexes = function(callback) {
var self = this;
- log.debug('Updating indexes...');
+ log.debug('Wallet:' + this.id + ' Updating indexes...');
var tasks = this.publicKeyRing.indexes.map(function(index) {
return function(callback) {
@@ -2309,9 +2417,8 @@ Wallet.prototype.updateIndexes = function(callback) {
async.parallel(tasks, function(err) {
if (err) callback(err);
- log.debug('Indexes updated');
- self.emit('publicKeyRingUpdated');
- self.store();
+ log.debug('Wallet:' + self.id + ' Indexes updated');
+ self._newAddresses();
callback();
});
};
@@ -2416,13 +2523,14 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb)
* @desc Closes the wallet and disconnects all services
*/
Wallet.prototype.close = function(cb) {
- var self = this;
- log.debug('## CLOSING');
- this.lock.release(function() {
- self.network.cleanUp();
- self.blockchain.destroy();
- if (cb) return cb();
- });
+ this.network.cleanUp();
+ this.blockchain.destroy();
+
+ log.debug('## CLOSING Wallet: ' + this.id);
+ // TODO
+ // this.lock.release(function() {
+ if (cb) return cb();
+ // });
};
/**
@@ -2468,7 +2576,7 @@ Wallet.prototype.setAddressBook = function(key, label) {
};
this.addressBook[key] = newEntry;
this.sendAddressBook();
- this.store();
+ this.emitAndKeepAlive('addressBookUpdated');
};
/**
@@ -2498,7 +2606,7 @@ Wallet.prototype.verifyAddressbookEntry = function(rcvEntry, senderId, key) {
Wallet.prototype.toggleAddressBookEntry = function(key) {
if (!key) throw new Error('Key is required');
this.addressBook[key].hidden = !this.addressBook[key].hidden;
- this.store();
+ this.emitAndKeepAlive('addressBookUpdated');
};
/**
@@ -2518,24 +2626,11 @@ Wallet.prototype.requiresMultipleSignatures = function() {
};
/**
- * @desc Returns true if the keyring is complete and all users have backed up the wallet
+ * @desc Returns true if the keyring is complete
* @return {boolean}
*/
Wallet.prototype.isReady = function() {
- var ret = this.publicKeyRing.isComplete() && (this.publicKeyRing.isFullyBackup() || this.isImported || this.forcedLogin);
- return ret;
-};
-
-/**
- * @desc Mark that our backup is ready and send a sync to other users.
- *
- * Also backs up the wallet
- */
-Wallet.prototype.setBackupReady = function(forcedLogin) {
- this.forcedLogin = forcedLogin;
- this.publicKeyRing.setBackupReady();
- this.sendPublicKeyRing();
- this.store();
+ return this.publicKeyRing.isComplete();
};
/**
@@ -2653,4 +2748,123 @@ Wallet.request = function(options, callback) {
return ret;
};
+
+Wallet.prototype.getTransactionHistory = function(cb) {
+ var self = this;
+
+ var addresses = self.getAddressesInfo();
+ var proposals = self.getTxProposals();
+ var satToUnit = 1 / self.settings.unitToSatoshi;
+
+ function extractInsOuts(tx) {
+ // Inputs
+ var inputs = _.map(tx.vin, function(item) {
+ var addr = _.findWhere(addresses, {
+ addressStr: item.addr
+ });
+ return {
+ type: 'in',
+ address: addr ? addr.addressStr : item.addr,
+ isMine: !_.isUndefined(addr),
+ isChange: addr ? !!addr.isChange : false,
+ amountSat: item.valueSat,
+ }
+ });
+ var outputs = _.map(tx.vout, function(item) {
+ var addr;
+ var itemAddr;
+ // If classic multisig, ignore
+ if (item.scriptPubKey && item.scriptPubKey.addresses.length == 1) {
+ itemAddr = item.scriptPubKey.addresses[0];
+ addr = _.findWhere(addresses, {
+ addressStr: itemAddr,
+ });
+ }
+
+ return {
+ type: 'out',
+ address: addr ? addr : itemAddr,
+ isMine: !_.isUndefined(addr),
+ isChange: addr ? !!addr.isChange : false,
+ label: self.addressBook[itemAddr] ? self.addressBook[itemAddr].label : undefined,
+ amountSat: parseInt((item.value * bitcore.util.COIN).toFixed(0)),
+ }
+ });
+
+ return inputs.concat(outputs);
+ };
+
+ function sum(items, filter) {
+ return _.reduce(_.where(items, filter),
+ function(memo, item) {
+ return memo + item.amountSat;
+ }, 0);
+ };
+
+ function decorateTx(tx) {
+ var items = extractInsOuts(tx);
+
+ var amountIn = sum(items, {
+ type: 'in',
+ isMine: true
+ });
+
+ var amountOut = sum(items, {
+ type: 'out',
+ isMine: true,
+ isChange: false,
+ });
+
+ var amountOutChange = sum(items, {
+ type: 'out',
+ isMine: true,
+ isChange: true,
+ });
+
+ var fees = parseInt((tx.fees * bitcore.util.COIN).toFixed(0));
+ var amount;
+ if (amountIn == (amountOut + amountOutChange + (amountIn > 0 ? fees : 0))) {
+ tx.action = 'moved';
+ amount = amountOut;
+ } else {
+ amount = amountIn - amountOut - amountOutChange - (amountIn > 0 ? fees : 0);
+ tx.action = amount > 0 ? 'sent' : 'received';
+ }
+
+ var firstOut = _.findWhere(items, {
+ type: 'out'
+ });
+
+ var proposal = _.findWhere(proposals, {
+ sentTxid: tx.txid
+ });
+ tx.comment = proposal ? proposal.comment : undefined;
+ tx.labelTo = firstOut ? firstOut.label : undefined;
+ tx.amountSat = Math.abs(amount);
+ tx.amount = tx.amountSat * satToUnit;
+ tx.sentTs = proposal ? proposal.sentTs : undefined;
+ tx.minedTs = tx.time * 1000;
+ };
+
+ if (addresses.length > 0) {
+ var addressesStr = _.pluck(addresses, 'addressStr');
+ self.blockchain.getTransactions(addressesStr, function(err, txs) {
+ if (err) return cb(err);
+
+ var history = _.map(txs, function(tx) {
+ decorateTx(tx);
+ return tx;
+ });
+ return cb(null, history);
+ });
+ }
+};
+
+Wallet.prototype.exportEncrypted = function(password, opts) {
+ opts = opts || {};
+ var crypto = opts.cryptoUtil || cryptoUtil;
+ var key = crypto.kdf(password);
+ return crypto.encrypt(key, this.toObj());
+};
+
module.exports = Wallet;
diff --git a/js/models/WalletFactory.js b/js/models/WalletFactory.js
deleted file mode 100644
index 7db1a0890..000000000
--- a/js/models/WalletFactory.js
+++ /dev/null
@@ -1,516 +0,0 @@
-'use strict';
-var preconditions = require('preconditions').singleton();
-
-var TxProposals = require('./TxProposals');
-var PublicKeyRing = require('./PublicKeyRing');
-var PrivateKey = require('./PrivateKey');
-var Wallet = require('./Wallet');
-var _ = require('underscore');
-var log = require('../log');
-var PluginManager = require('./PluginManager');
-var Async = module.exports.Async = require('./Async');
-var Insight = module.exports.Insight = require('./Insight');
-var preconditions = require('preconditions').singleton();
-var Storage = module.exports.Storage = require('./Storage');
-
-/**
- * @desc
- * WalletFactory - stores the state for a wallet in creation
- *
- * @param {Object} config - configuration for this wallet
- *
- * @TODO: Don't pass a class for these three components
- * -- send a factory or instance, the 'new' call considered harmful for refactoring
- * -- arguable, since all of them is called with an object as argument.
- * -- Still, could it be hard to refactor? (for example, what if we want to fail hard if a network call gets interrupted?)
- * @param {Storage} config.Storage - the class to instantiate to store the wallet (StorageLocalEncrypted by default)
- * @param {Object} config.storage - the configuration to be sent to the Storage constructor
- * @param {Network} config.Network - the class to instantiate to make network requests to copayers (the Async module by default)
- * @param {Object} config.network - the configurations to be sent to the Network and Blockchain constructors
- * @param {Blockchain} config.Blockchain - the class to instantiate to get information about the blockchain (Insight by default)
- * @TODO: Investigate what parameters go inside this object
- * @param {Object} config.wallet - default configuration for the wallet
- * @TODO: put `version` inside of the config object
- * @param {string} version - the version of copay for which this wallet was generated (for example, 0.4.7)
- * @constructor
- */
-
-function WalletFactory(config, version, pluginManager) {
- var self = this;
- preconditions.checkArgument(config);
- preconditions.checkArgument(config.network);
-
- this.Storage = config.Storage || Storage;
- this.Network = config.Network || Async;
- this.Blockchain = config.Blockchain || Insight;
-
- var storageOpts = {};
-
- if (pluginManager) {
- storageOpts = {
- storage: pluginManager.get('STORAGE')
- };
- }
-
- this.storage = new this.Storage(storageOpts);
-
- this.networks = {
- 'livenet': new this.Network(config.network.livenet),
- 'testnet': new this.Network(config.network.testnet),
- };
- this.blockchains = {
- 'livenet': new this.Blockchain(config.network.livenet),
- 'testnet': new this.Blockchain(config.network.testnet),
- };
-
- this.walletDefaults = config.wallet || {};
- this.version = version;
-};
-
-
-/**
- * @desc obtain network name from serialized wallet
- * @param {Object} wallet object
- * @return {string} network name
- */
-WalletFactory.prototype.obtainNetworkName = function(obj) {
- return obj.networkName ||
- obj.opts.networkName ||
- obj.publicKeyRing.networkName ||
- obj.privateKey.networkName;
-};
-
-/**
- * @desc Deserialize an object to a Wallet
- * @param {Object} wallet object
- * @param {string[]} skipFields - fields to skip when importing
- * @return {Wallet}
- */
-WalletFactory.prototype.fromObj = function(inObj, skipFields) {
- var networkName = this.obtainNetworkName(inObj);
- preconditions.checkState(networkName);
- preconditions.checkArgument(inObj);
-
- var obj = JSON.parse(JSON.stringify(inObj));
-
- // not stored options
- obj.opts = obj.opts || {};
- obj.opts.reconnectDelay = this.walletDefaults.reconnectDelay;
-
- skipFields = skipFields || [];
- skipFields.forEach(function(k) {
- if (obj[k]) {
- delete obj[k];
- } else
- throw new Error('unknown field:' + k);
- });
-
- var w = Wallet.fromObj(obj, this.storage, this.networks[networkName], this.blockchains[networkName]);
- if (!w) return false;
- this._checkVersion(w.version);
- return w;
-};
-
-/**
- * @desc Imports a wallet from an encrypted base64 object
- * @param {string} base64 - the base64 encoded object
- * @param {string} passphrase - passphrase to decrypt it
- * @param {string[]} skipFields - fields to ignore when importing
- * @return {Wallet}
- */
-WalletFactory.prototype.fromEncryptedObj = function(base64, passphrase, skipFields) {
- this.storage.setPassphrase(passphrase);
- var walletObj = this.storage.import(base64);
- if (!walletObj) return false;
- return this.fromObj(walletObj, skipFields);
-};
-
-/**
- * @TODO: import is a reserved keyword! DONT USE IT
- * @TODO: this is essentialy the same method as {@link WalletFactory#fromEncryptedObj}!
- * @desc Imports a wallet from an encrypted base64 object
- * @param {string} base64 - the base64 encoded object
- * @param {string} passphrase - passphrase to decrypt it
- * @param {string[]} skipFields - fields to ignore when importing
- * @return {Wallet}
- */
-WalletFactory.prototype.import = function(base64, passphrase, skipFields) {
- var self = this;
- return self.fromEncryptedObj(base64, passphrase, skipFields);
-};
-
-WalletFactory.prototype.migrateWallet = function(walletId, passphrase, cb) {
- var self = this;
-
- self.storage.setPassphrase(passphrase);
- self.read_Old(walletId, null, function(err, wallet) {
- if (err) return cb(err);
-
- wallet.store(function(err) {
- if (err) return cb(err);
-
- self.storage.deleteWallet_Old(walletId, function(err) {
- if (err) return cb(err);
-
- self.storage.removeGlobal('nameFor::' + walletId, function() {
- return cb();
- });
- });
- });
- });
-
-};
-
-
-/**
- * @desc Retrieve a wallet from storage
- * @param {string} walletId - the wallet id
- * @param {string[]} skipFields - parameters to ignore when importing
- * @param {function} callback - {err, Wallet}
- */
-WalletFactory.prototype.read = function(walletId, skipFields, cb) {
- var self = this,
- err;
- var obj = {};
-
- this.storage.readWallet(walletId, function(err, ret) {
- if (err) return cb(err);
-
- _.each(Wallet.PERSISTED_PROPERTIES, function(p) {
- obj[p] = ret[p];
- });
-
- if (!_.any(_.values(obj)))
- return cb(new Error('Wallet not found'));
-
- var w, err;
- obj.id = walletId;
- try {
- w = self.fromObj(obj, skipFields);
- } catch (e) {
- if (e && e.message && e.message.indexOf('MISSOPTS')) {
- err = new Error('Could not read: ' + walletId);
- } else {
- err = e;
- }
- w = null;
- }
- return cb(err, w);
- });
-};
-
-WalletFactory.prototype.read_Old = function(walletId, skipFields, cb) {
- var self = this,
- err;
- var obj = {};
-
- this.storage.readWallet_Old(walletId, function(err, ret) {
- if (err) return cb(err);
-
- _.each(Wallet.PERSISTED_PROPERTIES, function(p) {
- obj[p] = ret[p];
- });
-
- if (!_.any(_.values(obj)))
- return cb(new Error('Wallet not found'));
-
- var w, err;
- obj.id = walletId;
- try {
- w = self.fromObj(obj, skipFields);
- } catch (e) {
- if (e && e.message && e.message.indexOf('MISSOPTS')) {
- err = new Error('Could not read: ' + walletId);
- } else {
- err = e;
- }
- w = null;
- }
- return cb(err, w);
- });
-};
-
-/**
- * @desc This method instantiates a wallet. Usefull for stubbing.
- *
- * @param {opts} opts, ready for new Wallet(opts)
- *
- */
-
-
-WalletFactory.prototype._getWallet = function(opts) {
- return new Wallet(opts);
-};
-
-/**
- * @desc This method prepares options for a new Wallet
- *
- * @param {Object} opts
- * @param {string} opts.id
- * @param {PrivateKey=} opts.privateKey
- * @param {string=} opts.privateKeyHex
- * @param {number} opts.requiredCopayers
- * @param {number} opts.totalCopayers
- * @param {PublicKeyRing=} opts.publicKeyRing
- * @param {string} opts.nickname
- * @param {string} opts.passphrase
- * @TODO: Figure out what is this parameter
- * @param {?} opts.spendUnconfirmed this.walletDefaults.spendUnconfirmed ??
- * @TODO: Figure out in what unit is this reconnect delay.
- * @param {number} opts.reconnectDelay milliseconds?
- * @param {number=} opts.version
- * @param {callback} opts.version
- * @return {Wallet}
- */
-WalletFactory.prototype.create = function(opts, cb) {
- preconditions.checkArgument(cb);
-
- opts = opts || {};
- opts.networkName = opts.networkName || 'testnet';
-
- log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey'));
-
- var privOpts = {
- networkName: opts.networkName,
- };
-
- if (opts.privateKeyHex && opts.privateKeyHex.length > 1) {
- privOpts.extendedPrivateKeyString = opts.privateKeyHex;
- }
-
- opts.privateKey = opts.privateKey || new PrivateKey(privOpts);
-
- var requiredCopayers = opts.requiredCopayers || this.walletDefaults.requiredCopayers;
- var totalCopayers = opts.totalCopayers || this.walletDefaults.totalCopayers;
- opts.lockTimeoutMin = this.walletDefaults.idleDurationMin;
-
- opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({
- networkName: opts.networkName,
- requiredCopayers: requiredCopayers,
- totalCopayers: totalCopayers,
- });
- opts.publicKeyRing.addCopayer(
- opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(),
- opts.nickname
- );
- log.debug('\t### PublicKeyRing Initialized');
-
- opts.txProposals = opts.txProposals || new TxProposals({
- networkName: opts.networkName,
- });
- log.debug('\t### TxProposals Initialized');
-
-
- opts.storage = this.storage;
- opts.network = this.networks[opts.networkName];
- opts.blockchain = this.blockchains[opts.networkName];
-
- opts.spendUnconfirmed = opts.spendUnconfirmed || this.walletDefaults.spendUnconfirmed;
- opts.reconnectDelay = opts.reconnectDelay || this.walletDefaults.reconnectDelay;
- opts.requiredCopayers = requiredCopayers;
- opts.totalCopayers = totalCopayers;
- opts.version = opts.version || this.version;
-
- this.storage.setPassphrase(opts.passphrase);
- var w = this._getWallet(opts);
- var self = this;
- w.store(function(err) {
- if (err) return cb(err);
- self.storage.setLastOpened(w.id, function(err) {
- return cb(err, w);
- });
- });
-};
-
-/**
- * @desc Checks if a version is compatible with the current version
- * @param {string} inVersion - a version, with major, minor, and revision, period-separated (x.y.z)
- * @throws {Error} if there's a major version difference
- */
-WalletFactory.prototype._checkVersion = function(inVersion) {
- var thisV = this.version.split('.');
- var thisV0 = parseInt(thisV[0]);
- var inV = inVersion.split('.');
- var inV0 = parseInt(inV[0]);
-
- //We only check for major version differences
- if (thisV0 < inV0) {
- throw new Error('Major difference in software versions' +
- '. Received:' + inVersion +
- '. Current version:' + this.version +
- '. Aborting.');
- }
-};
-
-/**
- * @desc Retrieve a wallet from the storage
- * @param {string} walletId - the id of the wallet
- * @param {string} passphrase - the passphrase to decode it
- * @param {function} callback (err, {Wallet})
- * @return
- */
-WalletFactory.prototype.open = function(walletId, passphrase, cb) {
- preconditions.checkArgument(cb);
- var self = this;
- self.storage.setPassphrase(passphrase);
-
- self.migrateWallet(walletId, passphrase, function() {
- self.read(walletId, null, function(err, w) {
- if (err) return cb(err);
-
- w.store(function(err) {
- self.storage.setLastOpened(walletId, function() {
- return cb(err, w);
- });
- });
- });
- });
-};
-
-WalletFactory.prototype.getWallets = function(cb) {
- var self = this;
- this.storage.getWallets(function(wallets) {
- wallets.forEach(function(i) {
- i.show = i.name ? ((i.name + ' <' + i.id + '>')) : i.id;
- });
- self.storage.getLastOpened(function(lastId) {
- var last = _.findWhere(wallets, {
- id: lastId
- });
- if (last)
- last.lastOpened = true;
- return cb(null, wallets);
- })
- });
-};
-
-/**
- * @desc Deletes this wallet. This involves removing it from the storage instance
- * @TODO: delete is a reserved javascript keyword. NEVER USE IT.
- * @param {string} walletId
- * @TODO: Why is there a callback?
- * @callback cb
- * @return {?} the result of the callback
- */
-WalletFactory.prototype.delete = function(walletId, cb) {
- var self = this;
- self.storage.deleteWallet(walletId, function(err) {
- if (err) return cb(err);
- self.storage.setLastOpened(null, function(err) {
- return cb(err);
- });
- });
-};
-
-/**
- * @desc Pass through to {@link Wallet#secret}
- */
-WalletFactory.prototype.decodeSecret = function(secret) {
- try {
- return Wallet.decodeSecret(secret);
- } catch (e) {
- return false;
- }
-};
-
-/**
- * @callback walletCreationCallback
- * @param {?} err - an error, if any, that happened during the wallet creation
- * @param {Wallet=} wallet - the wallet created
- */
-
-/**
- * @desc Start the network functionality.
- *
- * Start up the Network instance and try to join a wallet defined by the
- * parameter secret using the parameter nickname. Encode
- * information locally using passphrase. privateHex is the
- * private extended master key. cb has two params: error and wallet.
- *
- * @param {object} opts
- * @param {string} opts.secret - the wallet secret
- * @param {string} opts.passphrase - a passphrase to use to encrypt the wallet for persistance
- * @param {string} opts.nickname - a nickname for the current user
- * @param {string} opts.privateHex - the private extended master key
- * @param {walletCreationCallback} cb - a callback
- */
-WalletFactory.prototype.joinCreateSession = function(opts, cb) {
- preconditions.checkArgument(opts);
- preconditions.checkArgument(opts.secret);
- preconditions.checkArgument(opts.passphrase);
- preconditions.checkArgument(opts.nickname);
- preconditions.checkArgument(cb);
- var self = this;
- var decodedSecret = this.decodeSecret(opts.secret);
- if (!decodedSecret || !decodedSecret.networkName || !decodedSecret.pubKey) {
- return cb('badSecret');
- }
-
- var privOpts = {
- networkName: decodedSecret.networkName,
- };
-
- if (opts.privateHex && opts.privateHex.length > 1) {
- privOpts.extendedPrivateKeyString = opts.privateHex;
- }
-
- //Create our PrivateK
- var privateKey = new PrivateKey(privOpts);
- log.debug('\t### PrivateKey Initialized');
- var joinOpts = {
- copayerId: privateKey.getId(),
- privkey: privateKey.getIdPriv(),
- key: privateKey.getIdKey(),
- secretNumber: decodedSecret.secretNumber,
- };
-
- var joinNetwork = this.networks[decodedSecret.networkName];
- joinNetwork.cleanUp();
-
- // This is a hack to reconize if the connection was rejected or the peer wasn't there.
- var connectedOnce = false;
- joinNetwork.on('connected', function(sender, data) {
- connectedOnce = true;
- });
-
- joinNetwork.on('connect_error', function() {
- return cb('connectionError');
- });
-
- joinNetwork.on('serverError', function() {
- return cb('joinError');
- });
-
- joinNetwork.start(joinOpts, function() {
-
- joinNetwork.greet(decodedSecret.pubKey, joinOpts.secretNumber);
- joinNetwork.on('data', function(sender, data) {
- if (data.type === 'walletId' && data.opts) {
- if (!data.networkName || data.networkName !== decodedSecret.networkName) {
- return cb('badNetwork');
- }
- data.opts.networkName = data.networkName;
-
- var walletOpts = _.clone(data.opts);
- walletOpts.id = data.walletId;
-
- walletOpts.privateKey = privateKey;
- walletOpts.nickname = opts.nickname;
- walletOpts.passphrase = opts.passphrase;
-
- self.create(walletOpts, function(err, w) {
-
- if (w) {
- w.sendWalletReady(decodedSecret.pubKey);
- } else {
- if (!err) err = 'walletFull';
- log.info(err);
- }
- return cb(err, w);
- });
- }
- });
- });
-};
-
-module.exports = WalletFactory;
diff --git a/js/models/WalletLock.js b/js/models/WalletLock.js
deleted file mode 100644
index 0b1fe503d..000000000
--- a/js/models/WalletLock.js
+++ /dev/null
@@ -1,101 +0,0 @@
-'use strict';
-
-var preconditions = require('preconditions').singleton();
-
-function WalletLock(storage, walletId, timeoutMin) {
- preconditions.checkArgument(storage);
- preconditions.checkArgument(walletId);
-
- this.storage = storage;
- this.timeoutMin = timeoutMin || 5;
- this.key = WalletLock._keyFor(walletId);
-}
-
-
-WalletLock.prototype.init = function(cb) {
- preconditions.checkArgument(cb);
- var self = this;
-
- self.storage.getSessionId(function(sid) {
- preconditions.checkState(sid);
-
- self.sessionId = sid;
- cb();
- });
-};
-
-WalletLock._keyFor = function(walletId) {
- return 'lock' + '::' + walletId;
-};
-
-WalletLock.prototype._isLockedByOther = function(cb) {
- var self = this;
-
- this.storage.getGlobal(this.key, function(json) {
- var wl = json ? JSON.parse(json) : null;
- if (!wl || !wl.expireTs)
- return cb(false);
-
- var expiredSince = Date.now() - wl.expireTs;
- if (expiredSince >= 0)
- return cb(false);
-
- var isMyself = wl.sessionId === self.sessionId;
-
- if (isMyself)
- return cb(false);
-
- // Seconds remainding
- return cb(parseInt(-expiredSince / 1000));
- });
-};
-
-
-WalletLock.prototype._setLock = function(cb) {
- preconditions.checkArgument(cb);
- preconditions.checkState(this.sessionId);
- var self = this;
-
- this.storage.setGlobal(this.key, {
- sessionId: this.sessionId,
- expireTs: Date.now() + this.timeoutMin * 60 * 1000,
- }, function() {
-
- cb(null);
- });
-};
-
-
-WalletLock.prototype._doKeepAlive = function(cb) {
- preconditions.checkArgument(cb);
- preconditions.checkState(this.sessionId);
-
- var self = this;
-
- this._isLockedByOther(function(t) {
- if (t)
- return cb(new Error('LOCKED: Wallet is locked for ' + t + ' srcs'));
-
- self._setLock(cb);
- });
-};
-
-
-
-WalletLock.prototype.keepAlive = function(cb) {
- var self = this;
-
- if (!self.sessionId) {
- return self.init(self._doKeepAlive.bind(self, cb));
- };
-
- return this._doKeepAlive(cb);
-};
-
-
-WalletLock.prototype.release = function(cb) {
- this.storage.removeGlobal(this.key, cb);
-};
-
-
-module.exports = WalletLock;
diff --git a/js/plugins/EncryptedInsightStorage.js b/js/plugins/EncryptedInsightStorage.js
new file mode 100644
index 000000000..6e759f794
--- /dev/null
+++ b/js/plugins/EncryptedInsightStorage.js
@@ -0,0 +1,37 @@
+var cryptoUtil = require('../util/crypto');
+var InsightStorage = require('./InsightStorage');
+var inherits = require('inherits');
+
+function EncryptedInsightStorage(config) {
+ InsightStorage.apply(this, [config]);
+}
+inherits(EncryptedInsightStorage, InsightStorage);
+
+EncryptedInsightStorage.prototype.getItem = function(name, callback) {
+ var key = cryptoUtil.kdf(this.password + this.email);
+ InsightStorage.prototype.getItem.apply(this, [name,
+ function(err, body) {
+ if (err) {
+ return callback(err);
+ }
+ var decryptedJson = cryptoUtil.decrypt(key, body);
+ if (!decryptedJson) {
+ return callback('PNOTFOUND');
+ }
+ return callback(null, decryptedJson);
+ }
+ ]);
+};
+
+EncryptedInsightStorage.prototype.setItem = function(name, value, callback) {
+ var key = cryptoUtil.kdf(this.password + this.email);
+ var record = cryptoUtil.encrypt(key, value);
+ InsightStorage.prototype.setItem.apply(this, [name, record, callback]);
+};
+
+EncryptedInsightStorage.prototype.removeItem = function(name, callback) {
+ var key = cryptoUtil.kdf(this.password + this.email);
+ InsightStorage.prototype.removeItem.apply(this, [name, callback]);
+};
+
+module.exports = EncryptedInsightStorage;
diff --git a/js/plugins/EncryptedLocalStorage.js b/js/plugins/EncryptedLocalStorage.js
new file mode 100644
index 000000000..698e999d6
--- /dev/null
+++ b/js/plugins/EncryptedLocalStorage.js
@@ -0,0 +1,32 @@
+var cryptoUtil = require('../util/crypto');
+var LocalStorage = require('./LocalStorage');
+var inherits = require('inherits');
+
+function EncryptedLocalStorage(config) {
+ LocalStorage.apply(this, [config]);
+}
+inherits(EncryptedLocalStorage, LocalStorage);
+
+EncryptedLocalStorage.prototype.getItem = function(name, callback) {
+ var key = cryptoUtil.kdf(this.password + this.email);
+ LocalStorage.prototype.getItem.apply(this, [name,
+ function(err, body) {
+ var decryptedJson = cryptoUtil.decrypt(key, body);
+ if (!decryptedJson) {
+ return callback('PNOTFOUND');
+ }
+ return callback(null, decryptedJson);
+ }
+ ]);
+};
+
+EncryptedLocalStorage.prototype.setItem = function(name, value, callback) {
+ var key = cryptoUtil.kdf(this.password + this.email);
+ if (!_.isString(value)) {
+ value = JSON.stringify(value);
+ }
+ var record = cryptoUtil.encrypt(key, value);
+ LocalStorage.prototype.setItem.apply(this, [name, record, callback]);
+};
+
+module.exports = EncryptedLocalStorage;
diff --git a/plugins/GoogleDrive.js b/js/plugins/GoogleDrive.js
similarity index 95%
rename from plugins/GoogleDrive.js
rename to js/plugins/GoogleDrive.js
index 4c8783ac6..599314961 100644
--- a/plugins/GoogleDrive.js
+++ b/js/plugins/GoogleDrive.js
@@ -3,7 +3,7 @@
var preconditions = require('preconditions').singleton();
var loaded = 0;
var SCOPES = 'https://www.googleapis.com/auth/drive';
-var log = require('../js/log');
+var log = require('../log');
function GoogleDrive(config) {
preconditions.checkArgument(config && config.clientId, 'No clientId at GoogleDrive config');
@@ -12,7 +12,7 @@ function GoogleDrive(config) {
this.home = config.home || 'copay';
this.idCache = {};
- this.type = 'STORAGE';
+ this.type = 'DB';
this.scripts = [{
then: this.initLoaded.bind(this),
@@ -56,6 +56,9 @@ GoogleDrive.prototype.checkAuth = function() {
this.handleAuthResult.bind(this));
};
+GoogleDrive.prototype.setCredentils = function(email, password, opts, callback) {
+};
+
/**
* Called when authorization server replies.
*/
@@ -92,6 +95,16 @@ GoogleDrive.prototype._httpGet = function(theUrl) {
return xmlHttp.responseText;
}
+GoogleDrive.prototype.createItem = function(name, value, callback) {
+ this.getItem(name, function(err, retrieved) {
+ if (err || !retrieved) {
+ return this.setItem(name, value, callback);
+ } else {
+ return callback('EEXISTS');
+ }
+ });
+};
+
GoogleDrive.prototype.getItem = function(k, cb) {
//console.log('[googleDrive.js.95:getItem:]', k); //TODO
var self = this;
@@ -313,10 +326,4 @@ GoogleDrive.prototype.allKeys = function(cb) {
});
};
-GoogleDrive.prototype.key = function(k) {
- var v = localStorage.key(k);
- return v;
-};
-
-
module.exports = GoogleDrive;
diff --git a/js/plugins/InsightStorage.js b/js/plugins/InsightStorage.js
new file mode 100644
index 000000000..9ecbddd15
--- /dev/null
+++ b/js/plugins/InsightStorage.js
@@ -0,0 +1,86 @@
+var request = require('request');
+var cryptoUtil = require('../util/crypto');
+var querystring = require('querystring');
+var Identity = require('../models/Identity');
+
+function InsightStorage(config) {
+ this.type = 'DB';
+ this.storeUrl = config.url || 'https://insight.is/api/email';
+ this.request = config.request || request;
+}
+
+InsightStorage.prototype.init = function () {};
+
+InsightStorage.prototype.setCredentials = function(email, password, opts) {
+ this.email = email;
+ this.password = password;
+};
+
+InsightStorage.prototype.createItem = function(name, value, callback) {
+ var self = this;
+ this.getItem(name, function(err, retrieved) {
+ if (err || !retrieved) {
+ return self.setItem(name, value, callback);
+ } else {
+ return callback('EEXISTS');
+ }
+ });
+};
+
+InsightStorage.prototype.getItem = function(name, callback) {
+ var key = cryptoUtil.kdf(this.password + this.email);
+ var secret = cryptoUtil.kdf(key, this.password);
+ var encodedEmail = encodeURIComponent(this.email);
+ var retrieveUrl = this.storeUrl + '/retrieve/' + encodedEmail;
+ this.request.get(retrieveUrl + '?' + querystring.encode({secret: secret, key: name}),
+ function(err, response, body) {
+ if (err) {
+ return callback('Connection error');
+ }
+ if (response.statusCode !== 200) {
+ return callback('Connection error');
+ }
+ return callback(null, body);
+ }
+ );
+};
+
+InsightStorage.prototype.setItem = function(name, value, callback) {
+ var key = cryptoUtil.kdf(this.password + this.email);
+ var secret = cryptoUtil.kdf(key, this.password);
+ var registerUrl = this.storeUrl + '/register';
+ this.request.post({
+ url: registerUrl,
+ body: querystring.encode({
+ key: name,
+ email: this.email,
+ secret: secret,
+ record: value
+ })
+ }, function(err, response, body) {
+ if (err) {
+ return callback('Connection error');
+ }
+ if (response.statusCode !== 200) {
+ return callback('Unable to store data on insight');
+ }
+ return callback();
+ });
+};
+
+InsightStorage.prototype.removeItem = function(name, callback) {
+ this.setItem(name, '', callback);
+};
+
+InsightStorage.prototype.clear = function(callback) {
+ // NOOP
+ callback();
+};
+
+InsightStorage.prototype.allKeys = function(callback) {
+ // NOOP
+ // TODO: Add functionality?
+ callback();
+};
+
+module.exports = InsightStorage;
diff --git a/plugins/LocalStorage.js b/js/plugins/LocalStorage.js
similarity index 57%
rename from plugins/LocalStorage.js
rename to js/plugins/LocalStorage.js
index 0035fc12b..6715d8cf1 100644
--- a/plugins/LocalStorage.js
+++ b/js/plugins/LocalStorage.js
@@ -1,15 +1,29 @@
'use strict';
function LocalStorage() {
- this.type = 'STORAGE';
+ this.type = 'DB';
};
LocalStorage.prototype.init = function() {
};
+LocalStorage.prototype.setCredentials = function(email, password, opts) {
+ this.email = email;
+ this.password = password;
+};
LocalStorage.prototype.getItem = function(k,cb) {
- return cb(localStorage.getItem(k));
+ return cb(null, localStorage.getItem(k));
+};
+
+/**
+ * Same as setItem, but fails if an item already exists
+ */
+LocalStorage.prototype.createItem = function(name, value, callback) {
+ if (localStorage.getItem(name)) {
+ return callback('EEXISTS');
+ }
+ return this.setItem(name, value, callback);
};
LocalStorage.prototype.setItem = function(k,v,cb) {
@@ -34,8 +48,7 @@ LocalStorage.prototype.allKeys = function(cb) {
for(var i=0; i 1 ? wallet.getMyCopayerNickname() : '';
};
-BackupService.prototype.getBackup = function(wallet) {
- return wallet.toEncryptedObj();
-};
-
-BackupService.prototype.getFilename = function(wallet) {
- var walletName = this.getName(wallet);
- var copayerName = this.getCopayer(wallet);
- return (copayerName ? copayerName + '-' : '') + walletName + '-keybackup.json.aes';
-};
-
-BackupService.prototype.download = function(wallet) {
- var ew = this.getBackup(wallet);
- var filename = this.getFilename(wallet);
-
- this.notifications.success('Backup created', 'Encrypted backup file saved');
+BackupService.prototype._download = function(ew, walletName, filename) {
var blob = new Blob([ew], {
type: 'text/plain;charset=utf-8'
});
+
+
// show a native save dialog if we are in the shell
// and pass the wallet to the shell to convert to node Buffer
if (window.cshell) {
@@ -49,9 +33,33 @@ BackupService.prototype.download = function(wallet) {
attachments: ['base64:' + filename + '//' + btoa(ew)]
});
}
+ this.notifications.success('Backup created', 'Encrypted backup file saved');
// otherwise lean on the browser implementation
saveAs(blob, filename);
};
+BackupService.prototype.walletEncrypted = function(wallet) {
+ return wallet.exportEncrypted(this.$rootScope.iden.password);
+}
+
+BackupService.prototype.walletDownload = function(wallet) {
+ var ew = this.walletEncrypted(wallet);
+ var walletName = wallet.getName();
+ var copayerName = this.getCopayer(wallet);
+ var filename = (copayerName ? copayerName + '-' : '') + walletName + '-keybackup.json.aes';
+ this._download(ew, walletName, filename)
+};
+
+BackupService.prototype.profileEncrypted = function(iden) {
+ return iden.exportEncryptedWithWalletInfo(iden.password);
+}
+
+BackupService.prototype.profileDownload = function(iden) {
+ var ew = this.profileEncrypted(iden);
+ var name = iden.fullName;
+ var filename = name + '-profile.json';
+ this._download(ew, name, filename)
+};
+
angular.module('copayApp.services').service('backupService', BackupService);
diff --git a/js/services/compatibility.js b/js/services/compatibility.js
new file mode 100644
index 000000000..3eec17b51
--- /dev/null
+++ b/js/services/compatibility.js
@@ -0,0 +1,5 @@
+'use strict';
+
+angular.module('copayApp.services').factory('Compatibility', function() {
+ return require('copay').Compatibility;
+});
diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js
index 5bf9aafe8..0d42459ce 100644
--- a/js/services/controllerUtils.js
+++ b/js/services/controllerUtils.js
@@ -5,18 +5,40 @@ angular.module('copayApp.services')
.factory('controllerUtils', function($rootScope, $sce, $location, $filter, notification, $timeout, uriHandler, rateService) {
var root = {};
+
+ root.redirIfNotComplete = function() {
+ var w = $rootScope.wallet;
+ if (w) {
+ if (!w.isReady()) {
+ $location.path('/copayers');
+ }
+ } else {
+ $location.path('/');
+ }
+ };
+
+
root.redirIfLogged = function() {
- if ($rootScope.wallet) {
- $location.path('receive');
+ var w = $rootScope.wallet;
+ if (w) {
+ if (!w.isReady()) {
+ $location.path('/copayers');
+ } else {
+ $location.path('receive');
+ }
}
};
root.logout = function() {
- if ($rootScope.wallet)
- $rootScope.wallet.close();
- $rootScope.wallet = null;
+ if ($rootScope.iden) {
+ $rootScope.iden.store(null, function(){
+ $rootScope.iden.close();
+ });
+ }
+
delete $rootScope['wallet'];
+ delete $rootScope['iden'];
// Clear rootScope
for (var i in $rootScope) {
@@ -30,7 +52,6 @@ angular.module('copayApp.services')
root.onError = function(scope) {
if (scope) scope.loading = false;
- root.logout();
}
root.onErrorDigest = function(scope, msg) {
@@ -40,79 +61,89 @@ angular.module('copayApp.services')
}
};
- root.installWalletHandlers = function(w, $scope) {
- w.on('connectionError', function() {
- var message = "Could not connect to the Insight server. Check your settings and network configuration";
- notification.error('Networking Error', message);
- root.onErrorDigest($scope);
+
+ root.isFocusedWallet = function(wid) {
+ return wid === $rootScope.wallet.getId();
+ };
+
+
+ root.updateTxsAndBalance = _.debounce(function(w) {
+ root.updateTxs({
+ wallet: w
});
- w.on('ready', function() {
- $scope.loading = false;
+ root.updateBalance(w, function() {
+ $rootScope.$digest();
+ })
+ }, 3000);
+
+ root.installWalletHandlers = function($scope, w) {
+
+ var wid = w.getId();
+ w.on('connectionError', function() {
+ if (root.isFocusedWallet(wid)) {
+ var message = "Could not connect to the Insight server. Check your settings and network configuration";
+ notification.error('Networking Error', message);
+ root.onErrorDigest($scope);
+ }
});
w.on('corrupt', function(peerId) {
- notification.error('Error', $filter('translate')('Received corrupt message from ') + peerId);
+ if (root.isFocusedWallet(wid)) {
+ notification.error('Error', $filter('translate')('Received corrupt message from ') + peerId);
+ }
});
w.on('ready', function(myPeerID) {
- $rootScope.wallet = w;
+ $scope.loading = false;
if ($rootScope.initialConnection) {
$rootScope.initialConnection = false;
if ($rootScope.pendingPayment) {
$location.path('send');
} else {
- $location.path('receive');
+ root.redirIfLogged();
}
}
});
- w.on('publicKeyRingUpdated', function(dontDigest) {
- root.updateAddressList();
- if (!dontDigest) {
- $rootScope.$digest();
- }
- });
-
w.on('tx', function(address, isChange) {
if (!isChange) {
- notification.funds('Funds received!', address);
+ notification.funds('Funds received on ' + w.getName(), address);
}
- root.updateBalance(function() {
+ root.updateBalance(w, function() {
$rootScope.$digest();
});
});
w.on('balanceUpdated', function() {
- root.updateBalance(function() {
+ root.updateBalance(w, function() {
$rootScope.$digest();
});
});
w.on('insightReconnected', function() {
$rootScope.reconnecting = false;
- root.updateAddressList();
- root.updateBalance(function() {
+ root.updateAddressList(w.getId());
+ root.updateBalance(w, function() {
$rootScope.$digest();
});
});
w.on('insightError', function() {
- $rootScope.reconnecting = true;
- $rootScope.$digest();
+ if (root.isFocusedWallet(wid)) {
+ $rootScope.reconnecting = true;
+ $rootScope.$digest();
+ }
+ });
+ w.on('newAddresses', function() {
+ root.updateTxsAndBalance(w);
});
- var updateTxsAndBalance = _.debounce(function() {
- root.updateTxs();
- root.updateBalance(function() {
- $rootScope.$digest();
- })
- }, 3000);
-
- w.on('txProposalsUpdated', function(dontDigest) {
- updateTxsAndBalance();
+ w.on('txProposalsUpdated', function() {
+ root.updateTxsAndBalance(w);
});
w.on('txProposalEvent', function(e) {
+ // TODO: add wallet name notification
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
switch (e.type) {
case 'signed':
@@ -127,8 +158,10 @@ angular.module('copayApp.services')
}
});
w.on('addressBookUpdated', function(dontDigest) {
- if (!dontDigest) {
- $rootScope.$digest();
+ if (root.isFocusedWallet(wid)) {
+ if (!dontDigest) {
+ $rootScope.$digest();
+ }
}
});
w.on('connect', function(peerID) {
@@ -139,81 +172,167 @@ angular.module('copayApp.services')
};
- root.setupRootVariables = function() {
+ root.setupGlobalVariables = function(iden) {
+ notification.enableHtml5Mode(); // for chrome: if support, enable it
uriHandler.register();
$rootScope.unitName = config.unitName;
$rootScope.txAlertCount = 0;
$rootScope.initialConnection = true;
$rootScope.reconnecting = false;
$rootScope.isCollapsed = true;
- $rootScope.$watch('txAlertCount', function(txAlertCount) {
- if (txAlertCount && txAlertCount > 0) {
- notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : $filter('translate')('You have') + ' ' + $rootScope.txAlertCount + ' ' + $filter('translate')('pending transaction proposals'), txAlertCount);
- }
+ $rootScope.iden = iden;
+
+ // TODO
+ // $rootScope.$watch('txAlertCount', function(txAlertCount) {
+ // if (txAlertCount && txAlertCount > 0) {
+ //
+ // notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : $filter('translate')('You have') + ' ' + $rootScope.txAlertCount + ' ' + $filter('translate')('pending transaction proposals'), txAlertCount);
+ // }
+ // });
+ };
+
+
+ root.rebindWallets = function($scope, iden) {
+ _.each(iden.listWallets(), function(wallet) {
+ preconditions.checkState(wallet);
+ root.installWalletHandlers($scope, wallet);
});
};
- root.startNetwork = function(w, $scope) {
- root.setupRootVariables();
- root.installWalletHandlers(w, $scope);
- root.updateAddressList();
- notification.enableHtml5Mode(); // for chrome: if support, enable it
- w.netStart();
+ root.setFocusedWallet = function(w) {
+ if (!_.isObject(w))
+ w = $rootScope.iden.getWalletById(w);
+ preconditions.checkState(w && _.isObject(w));
+ $rootScope.wallet = w;
+ w.updateTimestamp(new Date().getTime(), function() {
+ root.redirIfLogged();
+ root.updateBalance(w, function() {
+ $rootScope.$digest();
+ })
+ });
};
- // TODO movie this to wallet
- root.updateAddressList = function() {
- var w = $rootScope.wallet;
- if (w && w.isReady()) {
- w.subscribeToAddresses();
- $rootScope.addrInfos = w.getAddressesInfo();
+ root.bindProfile = function($scope, iden, w) {
+ root.setupGlobalVariables(iden);
+ root.rebindWallets($scope, iden);
+ if (w) {
+ root.setFocusedWallet(w);
+ } else {
+ $location.path('/manage');
}
};
- root.updateBalance = function(cb) {
- var w = $rootScope.wallet;
- if (!w) return root.onErrorDigest();
- if (!w.isReady()) return;
+ // On the focused wallet
+ root.updateAddressList = function(wid) {
- $rootScope.balanceByAddr = {};
- $rootScope.updatingBalance = true;
+ if (!wid || root.isFocusedWallet(wid)) {
+ var w = $rootScope.wallet;
+
+ if (w && w.isReady()) {
+ $rootScope.addrInfos = w.getAddressesInfo();
+ }
+ }
+ };
+
+ var _balanceCache = {};
+ root.clearBalanceCache = function(w) {
+ delete _balanceCache[w.getId()];
+ };
+
+
+ root._computeBalance = function(w, cb) {
+ cb = cb || function() {};
+ var satToUnit = 1 / w.settings.unitToSatoshi;
+ var COIN = bitcore.util.COIN;
w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat) {
- if (err) throw err;
+ if (err) return cb(err);
- var satToUnit = 1 / w.settings.unitToSatoshi;
- var COIN = bitcore.util.COIN;
+ var r = {};
+ r.totalBalance = balanceSat * satToUnit;
+ r.totalBalanceBTC = (balanceSat / COIN);
+ r.availableBalance = safeBalanceSat * satToUnit;
+ r.availableBalanceBTC = (safeBalanceSat / COIN);
- $rootScope.totalBalance = balanceSat * satToUnit;
- $rootScope.totalBalanceBTC = (balanceSat / COIN);
- $rootScope.availableBalance = safeBalanceSat * satToUnit;
- $rootScope.availableBalanceBTC = (safeBalanceSat / COIN);
-
- $rootScope.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
- $rootScope.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
+ r.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
+ r.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
var balanceByAddr = {};
for (var ii in balanceByAddrSat) {
balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit;
}
- $rootScope.balanceByAddr = balanceByAddr;
+ r.balanceByAddr = balanceByAddr;
root.updateAddressList();
- $rootScope.updatingBalance = false;
+ r.updatingBalance = false;
rateService.whenAvailable(function() {
- $rootScope.totalBalanceAlternative = rateService.toFiat(balanceSat, w.settings.alternativeIsoCode);
- $rootScope.alternativeIsoCode = w.settings.alternativeIsoCode;
- $rootScope.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, w.settings.alternativeIsoCode);
- $rootScope.alternativeConversionRate = rateService.toFiat(100000000, w.settings.alternativeIsoCode);
- return cb ? cb() : null;
+ r.totalBalanceAlternative = rateService.toFiat(balanceSat, w.settings.alternativeIsoCode);
+ r.alternativeIsoCode = w.settings.alternativeIsoCode;
+ r.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, w.settings.alternativeIsoCode);
+ r.alternativeConversionRate = rateService.toFiat(100000000, w.settings.alternativeIsoCode);
+ return cb(null, r)
+ });
+ });
+ };
+
+ root._updateScope = function(w, data, $scope, cb) {
+ $scope.totalBalance = data.totalBalance;
+ $scope.totalBalanceBTC = data.totalBalanceBTC;
+ $scope.availableBalance = data.availableBalance;
+ $scope.availableBalanceBTC = data.availableBalanceBTC;
+
+ $scope.lockedBalance = data.lockedBalance;
+ $scope.lockedBalanceBTC = data.lockedBalanceBTC;
+
+ $scope.balanceByAddr = data.balanceByAddr;
+
+ $scope.totalBalanceAlternative = data.totalBalanceAlternative;
+ $scope.alternativeIsoCode = data.alternativeIsoCode;
+ $scope.lockedBalanceAlternative = data.lockedBalanceAlternative;
+ $scope.alternativeConversionRate = data.alternativeConversionRate;
+
+ if (cb) return cb();
+ };
+
+ root.updateBalance = function(w, cb) {
+ w = w || $rootScope.wallet;
+ if (!w) return root.onErrorDigest();
+ if (!w.isReady()) return;
+ console.log('## Updating balance of:' + w.id)
+
+ w.balanceInfo = {};
+ var scope = root.isFocusedWallet(w.id) ? $rootScope : w.balanceInfo;
+
+ root.updateAddressList();
+
+ var wid = w.getId();
+
+ if (_balanceCache[wid]) {
+ root._updateScope(w, _balanceCache[wid], scope, function() {
+ if (root.isFocusedWallet(w.id)) {
+ setTimeout(function() {
+ $rootScope.$digest();
+ }, 1);
+ }
+ });
+ } else {
+ scope.updatingBalance = true;
+ }
+
+ root._computeBalance(w, function(err, res) {
+ if (err) throw err;
+ _balanceCache[wid] = res;
+ root._updateScope(w, _balanceCache[wid], scope, function() {
+ scope.updatingBalance = false;
+ if (cb) cb();
});
});
};
root.updateTxs = function(opts) {
- var w = $rootScope.wallet;
+ var w = opts.wallet || $rootScope.wallet;
if (!w) return;
opts = opts || $rootScope.txsOpts || {};
@@ -269,6 +388,16 @@ angular.module('copayApp.services')
$rootScope.pendingTxCount = pendingForUs;
};
+ root.deleteWallet = function($scope, w) {
+ w = w || $rootScope.wallet;
+ $rootScope.iden.deleteWallet(w.id, function() {
+ notification.info('Wallet deleted', $filter('translate')('Wallet deleted'));
+ $rootScope.wallet = null;
+ var lastFocused = $rootScope.iden.getLastFocusedWallet();
+ root.bindProfile($scope, $rootScope.iden, lastFocused);
+ });
+ };
+
function getActionList(actions) {
var peers = Object.keys(actions).map(function(i) {
return {
diff --git a/js/services/identityService.js b/js/services/identityService.js
new file mode 100644
index 000000000..fcf876a1a
--- /dev/null
+++ b/js/services/identityService.js
@@ -0,0 +1,66 @@
+'use strict';
+
+angular.module('copayApp.services')
+ .factory('identityService', function($rootScope, $location, pluginManager, controllerUtils) {
+ var root = {};
+
+ root.create = function(scope, form) {
+ var iden = copay.Identity.create({
+ email: form.email.$modelValue,
+ password: form.password.$modelValue,
+ pluginManager: pluginManager,
+ network: config.network,
+ networkName: config.networkName,
+ walletDefaults: config.wallet,
+ passphraseConfig: config.passphraseConfig,
+ });
+
+ var walletOptions = {
+ nickname: iden.fullName,
+ networkName: config.networkName,
+ requiredCopayers: 1,
+ totalCopayers: 1,
+ password: iden.password,
+ name: 'My wallet',
+ };
+ iden.createWallet(walletOptions, function(err, wallet) {
+ if (err) {
+ controllerUtils.onErrorDigest(
+ scope, 'Could not create default wallet');
+ } else {
+ iden.store({failIfExists: true}, function(err) {
+ if (err) {
+ controllerUtils.onErrorDigest(scope, 'User already exists!');
+ } else {
+ controllerUtils.bindProfile(scope, iden, wallet.id);
+ }
+ });
+ }
+ scope.loading = false;
+ });
+ };
+
+
+ root.open = function(scope, form) {
+ copay.Identity.open({
+ email: form.email.$modelValue,
+ password: form.password.$modelValue,
+ pluginManager: pluginManager,
+ network: config.network,
+ networkName: config.networkName,
+ walletDefaults: config.wallet,
+ passphraseConfig: config.passphraseConfig,
+ }, function(err, iden) {
+ if (err && !iden) {
+ controllerUtils.onErrorDigest(
+ scope, (err.toString() || '').match('PNOTFOUND') ? 'Invalid email or password' : 'Unknown error');
+ } else {
+ var firstWallet = iden.getLastFocusedWallet();
+ controllerUtils.bindProfile(scope, iden, firstWallet);
+ }
+ scope.loading = false;
+ });
+ }
+
+ return root;
+ });
diff --git a/js/services/notifications.js b/js/services/notifications.js
index ba8cb49f9..3f5b324ea 100644
--- a/js/services/notifications.js
+++ b/js/services/notifications.js
@@ -9,11 +9,11 @@ factory('notification', ['$timeout',
var settings = {
info: {
- duration: 5000,
+ duration: 6000,
enabled: true
},
funds: {
- duration: 5000,
+ duration: 7000,
enabled: true
},
version: {
@@ -21,11 +21,11 @@ factory('notification', ['$timeout',
enabled: true
},
warning: {
- duration: 5000,
+ duration: 7000,
enabled: true
},
error: {
- duration: 5000,
+ duration: 7000,
enabled: true
},
success: {
@@ -42,12 +42,12 @@ factory('notification', ['$timeout',
},
details: true,
localStorage: false,
- html5Mode: false,
+ html5Mode: true,
html5DefaultIcon: 'img/favicon.ico'
};
function html5Notify(icon, title, content, ondisplay, onclose) {
- if (window.webkitNotifications.checkPermission() === 0) {
+ if (window.webkitNotifications && window.webkitNotifications.checkPermission() === 0) {
if (!icon) {
icon = 'img/favicon.ico';
}
@@ -185,6 +185,7 @@ factory('notification', ['$timeout',
'timestamp': +new Date(),
'userData': userData
};
+
notifications.push(notification);
if (settings.html5Mode) {
@@ -193,7 +194,10 @@ factory('notification', ['$timeout',
}, function() {
// inner on close function
});
- } else {
+ }
+
+ //this is done because html5Notify() changes the variable settings.html5Mode
+ if (!settings.html5Mode) {
queue.push(notification);
$timeout(function removeFromQueueTimeout() {
queue.splice(queue.indexOf(notification), 1);
@@ -202,11 +206,14 @@ factory('notification', ['$timeout',
// Mobile notification
if (window && window.navigator && window.navigator.vibrate) {
- window.navigator.vibrate([200,100,200]);
+ window.navigator.vibrate([200, 100, 200]);
};
if (document.hidden && (type == 'info' || type == 'funds')) {
- new window.Notification(title, {body: content, icon:'img/notification.png'});
+ new window.Notification(title, {
+ body: content,
+ icon: 'img/notification.png'
+ });
}
this.save();
@@ -234,8 +241,7 @@ factory('notification', ['$timeout',
};
}
-]).
-directive('notifications', function(notification, $compile) {
+]).directive('notifications', function(notification, $compile) {
/**
*
* It should also parse the arguments passed to it that specify
diff --git a/js/services/passphrase.js b/js/services/passphrase.js
deleted file mode 100644
index 00ff9e2be..000000000
--- a/js/services/passphrase.js
+++ /dev/null
@@ -1,4 +0,0 @@
-'use strict';
-
-angular.module('copayApp.services')
- .value('Passphrase', new copay.Passphrase(config.passphrase));
diff --git a/js/services/pluginManager.js b/js/services/pluginManager.js
index 1b85d15a6..2a86a03c9 100644
--- a/js/services/pluginManager.js
+++ b/js/services/pluginManager.js
@@ -1,6 +1,6 @@
'use strict';
-angular.module('copayApp.services').factory('pluginManager', function(angularLoad){
+angular.module('copayApp.services').factory('pluginManager', function(angularLoad) {
var pm = new copay.PluginManager(config);
var scripts = pm.scripts;
diff --git a/js/services/rate.js b/js/services/rate.js
index 68f96c313..6599905c1 100644
--- a/js/services/rate.js
+++ b/js/services/rate.js
@@ -1,18 +1,19 @@
'use strict';
+var MINS_IN_HOUR = 60;
+var MILLIS_IN_SECOND = 1000;
+
var RateService = function(request) {
this.isAvailable = false;
this.UNAVAILABLE_ERROR = 'Service is not available - check for service.isAvailable or use service.whenAvailable';
this.SAT_TO_BTC = 1 / 1e8;
this.BTC_TO_SAT = 1e8;
- var MINS_IN_HOUR = 60;
- var MILLIS_IN_SECOND = 1000;
var rateServiceConfig = config.rate;
var updateFrequencySeconds = rateServiceConfig.updateFrequencySeconds || 60 * MINS_IN_HOUR;
var rateServiceUrl = rateServiceConfig.url || 'https://bitpay.com/api/rates';
this.queued = [];
this.alternatives = [];
- var that = this;
+ var self = this;
var backoffSeconds = 5;
var retrieve = function() {
request.get({
@@ -27,15 +28,15 @@ var RateService = function(request) {
var rates = {};
listOfCurrencies.forEach(function(element) {
rates[element.code] = element.rate;
- that.alternatives.push({
+ self.alternatives.push({
name: element.name,
isoCode: element.code,
rate: element.rate
});
});
- that.isAvailable = true;
- that.rates = rates;
- that.queued.forEach(function(callback) {
+ self.isAvailable = true;
+ self.rates = rates;
+ self.queued.forEach(function(callback) {
setTimeout(callback, 1);
});
setTimeout(retrieve, updateFrequencySeconds * MILLIS_IN_SECOND);
diff --git a/js/services/walletFactory.js b/js/services/walletFactory.js
deleted file mode 100644
index a592470d2..000000000
--- a/js/services/walletFactory.js
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict';
-angular.module('copayApp.services').factory('walletFactory', function(pluginManager){
- return new copay.WalletFactory(config, copay.version, pluginManager);
-});
-
diff --git a/js/util/crypto.js b/js/util/crypto.js
new file mode 100644
index 000000000..46da407df
--- /dev/null
+++ b/js/util/crypto.js
@@ -0,0 +1,68 @@
+/**
+ * Small module for some helpers that wrap sjcl with some good practices.
+ */
+var sjcl = require('sjcl');
+var log = require('../log.js');
+var _ = require('lodash');
+
+var defaultSalt = 'mjuBtGybi/4=';
+var defaultIterations = 100;
+
+module.exports = {
+
+ /**
+ * @param {string} password
+ * @param {string} salt - base64 encoded, defaults to 'mjuBtGybi/4='
+ * @param {number} iterations - defaults to 100
+ * @param {number} length - bits, defaults to 512 bits
+ * @returns {string} base64 encoded pbkdf2 derivation using sha1 for hmac
+ */
+ kdf: function(password, salt, iterations, length) {
+ return sjcl.codec.base64.fromBits(
+ this.kdfbinary(password, salt, iterations, length)
+ );
+ },
+
+ /**
+ * @param {string} password
+ * @param {string} salt - base64 encoded, defaults to 'mjuBtGybi/4='
+ * @param {number} iterations - defaults to 100
+ * @param {number} length - bits, defaults to 512 bits
+ * @returns {string} base64 encoded pbkdf2 derivation using sha1 for hmac
+ */
+ kdfbinary: function(password, salt, iterations, length) {
+ iterations = iterations || defaultIterations;
+ length = length || 512;
+ salt = sjcl.codec.base64.toBits(salt || defaultSalt);
+
+ var hash = sjcl.hash.sha256.hash(sjcl.hash.sha256.hash(password));
+ var prff = function(key) {
+ return new sjcl.misc.hmac(hash, sjcl.hash.sha1);
+ };
+
+ return sjcl.misc.pbkdf2(hash, salt, iterations, length, prff);
+ },
+
+ /**
+ * Encrypts symmetrically using a passphrase
+ */
+ encrypt: function(key, message) {
+ if (!_.isString(message)) {
+ message = JSON.stringify(message);
+ }
+ return sjcl.encrypt(key, message);
+ },
+
+ /**
+ * Decrypts symmetrically using a passphrase
+ */
+ decrypt: function(key, cyphertext) {
+ var output = {};
+ try {
+ return sjcl.decrypt(key, cyphertext);
+ } catch (e) {
+ log.info('Decryption failed due to error: ' + e.message);
+ return null;
+ }
+ }
+};
diff --git a/karma.conf.js b/karma.conf.js
index f739e3203..64334a0f7 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -31,7 +31,7 @@ module.exports = function(config) {
'lib/angular-load/angular-load.min.js',
'lib/angular-gettext/dist/angular-gettext.min.js',
'lib/inherits/inherits.js',
- 'lib/underscore/underscore.js',
+ 'lib/lodash/dist/lodash.js',
'lib/file-saver/FileSaver.js',
'lib/socket.io-client/socket.io.js',
'lib/sjcl.js',
@@ -46,25 +46,22 @@ module.exports = function(config) {
'js/log.js',
'js/routes.js',
'js/services/*.js',
+ 'js/util/*.js',
'js/directives.js',
'js/filters.js',
'js/controllers/*.js',
'js/translations.js',
'js/init.js',
- 'test/mocks/FakeWallet.js',
'test/mocks/FakeBlockchainSocket.js',
'test/mocks/FakePayProServer.js',
- 'test/mocks/FakeLocalStorage.js',
'test/mocha.conf.js',
//test files
'setup/karma.js',
- 'test/*.js',
-
'test/unit/**/*.js',
-
+ 'test/*.js',
],
diff --git a/package.json b/package.json
index ba0e576e3..daf2d6cfe 100644
--- a/package.json
+++ b/package.json
@@ -23,10 +23,11 @@
"dependencies": {
"browser-request": "^0.3.2",
"inherits": "^2.0.1",
+ "lodash": "^2.4.1",
"optimist": "^0.6.1",
"preconditions": "^1.0.7",
- "request": "^2.40.0",
- "underscore": "^1.7.0"
+ "querystring": "^0.2.0",
+ "request": "^2.40.0"
},
"scripts": {
"start": "node server.js",
@@ -80,6 +81,7 @@
"mocha": "^1.18.2",
"mocha-lcov-reporter": "^0.0.1",
"mock-fs": "^2.3.1",
+ "sjcl": "*",
"node-cryptojs-aes": "^0.4.0",
"request": "^2.40.0",
"shelljs": "^0.3.0",
diff --git a/setup/karma.js b/setup/karma.js
index c66adef61..583e4c910 100644
--- a/setup/karma.js
+++ b/setup/karma.js
@@ -12,5 +12,5 @@ if (!!window) {
}
window.is_browser = true;
- window._ = require('underscore');
+ window._ = require('lodash');
}
diff --git a/setup/node.js b/setup/node.js
index 7fdf6c8eb..d1fafcbeb 100644
--- a/setup/node.js
+++ b/setup/node.js
@@ -13,4 +13,4 @@ global.requireMock = function(name) {
}
global.is_browser = typeof process == 'undefined' || typeof process.versions === 'undefined';
-global._ = require('underscore');
+global._ = require('lodash');
diff --git a/test/Compatibility.js b/test/Compatibility.js
new file mode 100644
index 000000000..f90035a07
--- /dev/null
+++ b/test/Compatibility.js
@@ -0,0 +1,59 @@
+
+var compat = require('../js/models/Compatibility');
+
+describe('Compatibility', function() {
+
+ describe('#import', function() {
+ it('should not be able to decrypt with wrong password', function() {
+ var wo = compat.importLegacy(encryptedLegacy1, 'badpassword');
+ should.not.exist(wo);
+ });
+
+ it('should generate passphrases acording to old algorightm', function() {
+ var passphrase = compat.kdf(legacyPassword1);
+ passphrase.should.equal(legacyPassphrase1);
+ });
+
+
+
+ it('should be able to decrypt an old backup', function() {
+ var str = compat.importLegacy(encryptedLegacy1, legacyPassword1);
+ should.exist(str);
+ var wo = JSON.parse(str);
+ wo.opts.id.should.equal('48ba2f1ffdfe9708');
+ wo.opts.spendUnconfirmed.should.equal(true);
+ wo.opts.requiredCopayers.should.equal(1);
+ wo.opts.totalCopayers.should.equal(1);
+ wo.opts.name.should.equal('pepe wallet');
+ wo.opts.version.should.equal('0.4.7');
+ wo.publicKeyRing.walletId.should.equal('48ba2f1ffdfe9708');
+ wo.publicKeyRing.networkName.should.equal('testnet');
+ wo.publicKeyRing.requiredCopayers.should.equal(1);
+ wo.publicKeyRing.totalCopayers.should.equal(1);
+ wo.publicKeyRing.indexes.length.should.equal(2);
+ JSON.stringify(wo.publicKeyRing.indexes[0]).should.equal('{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1}');
+ JSON.stringify(wo.publicKeyRing.indexes[1]).should.equal('{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}');
+ wo.publicKeyRing.copayersBackup.length.should.equal(1);
+ wo.publicKeyRing.copayersBackup[0].should.equal('0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f');
+ wo.publicKeyRing.copayersExtPubKeys.length.should.equal(1);
+ wo.publicKeyRing.copayersExtPubKeys[0].should.equal('tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6');
+ wo.privateKey.extendedPrivateKeyString.should.equal('tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm');
+ wo.privateKey.networkName.should.equal('testnet');
+ });
+
+ it('should be able import an old backup', function(done) {
+ var iden = sinon.stub();
+ iden.importWalletFromObj = sinon.stub().yields(null);
+
+ compat.importEncryptedWallet(iden, encryptedLegacy1, legacyPassword1, {}, function(err){
+ var s = iden.importWalletFromObj;
+ s.getCall(0).args[0].opts.id.should.equal('48ba2f1ffdfe9708');
+ done();
+ });
+ })
+ });
+});
+
+var legacyPassword1 = '1';
+var legacyPassphrase1 = '1DUpLRbuVpgLkcEY8gY8iod/SmA7+OheGZJ9PtvmTlvNE0FkEWpCKW9STdzXYJqbn0wiAapE4ojHNYj2hjYYAQ==';
+var encryptedLegacy1 = 'U2FsdGVkX19yGM1uBAIzQa8Po/dvUicmxt1YyRk/S97PcZ6I6rHMp9dMagIrehg4Qd6JHn/ustmFHS7vmBYj0EBpf6rdXiQezaWnVAJS9/xYjAO36EFUbl+NmUanuwujAxgYdSP/sNssRLeInvExmZYW993EEclxkwL6YUyX66kKsxGQo2oWng0NreBJNhFmrbOEWeFje2PiWP57oUjKsurFzwpluAAarUTYSLud+nXeabC7opzOP5yqniWBMJz0Ou8gpNCWCMhG/P9F9ccVPY7juyd0Hf41FVse8nd2++axKB57+paozLdO+HRfV6zkMqC3h8gWY7LkS75j3bvqcTw9LhXmzE0Sz21n9yDnRpA4chiAvtwQvvBGgj1pFMKhNQU6Obac9ZwKYzUTgdDn3Uzg1UlDzgyOh9S89rbRTV84WB+hXwhuVluWzbNNYV3vXe5PFrocVktIrtS3xQh+k/7my4A6/gRRrzNYpKrUASJqDS/9u9WBkG35xD63J/qXjtG2M0YPwbI57BK1IK4K510b8V72lz5U2XQrIC4ldBwni1rpSavwCJV9xF6hUdOmNV8fZsVHP0NeN1PYlLkSb2QgfuoWnkcsJerwuFR7GZC/i6efrswtpO0wMEQr/J0CLbeXlHAru6xxjCBhWoJvZpMGw72zgnDLoyMNsEVglNhx/VlV9ZMYkkdaEYAxPOEIyZdQ5MS+2jEAlXf818n/xzJSVrniCn9be8EPePvkw35pivprvy09vbW4cKsWBKvgIyoT6A3OhUOCCS8E9cg0WAjjav2EymrbKmGWRHaiD+EoJqaDg6s20zhHn1YEa/YwvGGSB5+Hg8baLHD8ZASvxz4cFFAAVZrBUedRFgHzqwaMUlFXLgueivWUj7RXlIw6GuNhLoo1QkhZMacf23hrFxxQYvGBRw1hekBuDmcsGWljA28udBxBd5f9i+3gErttMLJ6IPaud590uvrxRIclu0Sz9R2EQX64YJxqDtLpMY0PjddSMu8vaDRpK9/ZSrnz/xrXsyabaafz4rE/ItFXjwFUFkvtmuauHTz6nmuKjVfxvNLNAiKb/gI7vQyUhnTbKIApe7XyJsjedNDtZqsPoJRIzdDmrZYxGStbAZ7HThqFJlSJ9NPNhH+E2jm3TwL5mwt0fFZ5h+p497lHMtIcKffESo7KNa2juSVNMDREk0NcyxGXGiVB2FWl4sLdvyhcsVq0I7tmW6OGZKRf8W49GCJXq6Ie69DJ9LB1DO67NV1jsYbsLx9uhE2yEmpWZ3jkoCV/Eas4grxt0CGN6EavzQ==';
diff --git a/test/Identity.js b/test/Identity.js
new file mode 100644
index 000000000..718a68e21
--- /dev/null
+++ b/test/Identity.js
@@ -0,0 +1,411 @@
+'use strict';
+
+
+var _ = require('lodash');
+var chai = chai || require('chai');
+var sinon = sinon || require('sinon');
+var should = chai.should();
+var PluginManager = require('../js/models/PluginManager');
+var Insight = require('../js/models/Insight');
+
+var Identity = copay.Identity;
+var Wallet = copay.Wallet;
+var Passphrase = copay.Passphrase;
+
+var FakeBlockchain = require('./mocks/FakeBlockchain');
+
+var PERSISTED_PROPERTIES = (copay.Wallet || require('../js/models/Wallet')).PERSISTED_PROPERTIES;
+
+function assertObjectEqual(a, b) {
+ PERSISTED_PROPERTIES.forEach(function(k) {
+ if (a[k] && b[k]) {
+ _.omit(a[k], 'name').should.be.deep.equal(b[k], k + ' differs');
+ }
+ })
+}
+
+
+describe('Identity model', function() {
+ var wallet;
+ var email = 'hola@hola.com';
+ var password = 'password';
+ var blockchain;
+
+ var config = {
+ walletDefaults: {
+ requiredCopayers: 3,
+ totalCopayers: 5,
+ spendUnconfirmed: 1,
+ reconnectDelay: 100,
+
+ },
+ blockchain: {
+ host: 'test.insight.is',
+ port: 80,
+ schema: 'https'
+ },
+ networkName: 'testnet',
+ passphrase: {
+ iterations: 100,
+ storageSalt: 'mjuBtGybi/4=',
+ },
+
+ // network layer config
+ network: {
+ testnet: {
+ url: 'https://test-insight.bitpay.com:443'
+ },
+ livenet: {
+ url: 'https://insight.bitpay.com:443'
+ },
+ },
+ version: '0.0.1'
+ };
+
+ function getDefaultParams() {
+ var params = _.cloneDeep(config);
+ _.extend(params, {
+ email: email,
+ password: password
+ });
+ params.storage = sinon.stub();
+ params.storage.setCredentials = sinon.stub();
+ params.storage.getItem = sinon.stub();
+ params.storage.setItem = sinon.stub();
+ params.storage.setItem.onFirstCall().callsArgWith(2, null);
+ params.storage.setItem.onSecondCall().callsArgWith(2, null);
+ return params;
+ }
+
+ function getNewWallet(args) {
+ var w = sinon.stub();
+ w.getId = sinon.stub().returns('wid');
+ w.getStorageKey = sinon.stub().returns('wkey');
+ w.toObj = sinon.stub().returns({
+ obj: 1
+ });
+ w.getName = sinon.stub().returns('name');
+ w.on = sinon.stub();
+ w.netStart = sinon.stub();
+ w.args = args;
+ return w;
+
+ }
+
+
+ var walletClass = function(args) {
+ return getNewWallet(args);
+ };
+
+ function createIdentity(done) {
+
+ // TODO (eordano): Change this to proper dependency injection
+ var blockchain = new FakeBlockchain(config.blockchain);
+ var params = getDefaultParams();
+ blockchain.on = sinon.stub();
+ Wallet._newInsight = sinon.stub().returns(blockchain);
+
+ return {
+ blockchain: blockchain,
+ storage: params.storage,
+ wallet: wallet,
+ params: params
+ };
+ };
+
+ describe('new Identity()', function() {
+ it('returns an identity', function() {
+ var iden = new Identity(getDefaultParams());
+ should.exist(iden);
+ iden.walletDefaults.should.deep.equal(config.walletDefaults);
+ });
+ });
+
+ describe('Identity.create()', function() {
+ it('should create', function() {
+ var args = createIdentity();
+ args.blockchain.on = sinon.stub();
+ var old = Identity.prototype.createWallet;
+ Identity.prototype.createWallet = sinon.stub().yields(null, getNewWallet());
+ var iden = Identity.create(args.params);
+ should.exist(iden);
+ should.exist(iden.wallets);
+ Identity.prototype.createWallet = old;
+ });
+ });
+
+ describe('#open', function(done) {
+ it.skip('should return last focused wallet', function(done) {
+ var wallets = [{
+ id: 'wallet1',
+ store: sinon.stub().yields(null),
+ netStart: sinon.stub(),
+ }, {
+ id: 'wallet2',
+ store: sinon.stub().yields(null),
+ netStart: sinon.stub(),
+ }, {
+ id: 'wallet3',
+ store: sinon.stub().yields(null),
+ netStart: sinon.stub(),
+ }];
+ var args = createIdentity();
+ Identity.create(args.params, function(err, identity) {
+ // TODO: Add checks for what is this testing
+ done();
+ });
+ });
+ });
+
+ describe.skip('#storeWallet', function() {
+ // TODO test storeWallet
+ });
+
+
+ describe('#createWallet', function() {
+ var iden = null;
+ var args = null;
+ beforeEach(function() {
+ args = createIdentity();
+ args.params.noWallets = true;
+ var old = Identity.prototype.createWallet;
+ Identity.prototype.createWallet = sinon.stub().yields(null, getNewWallet());
+ iden = Identity.create(args.params);
+ Identity.prototype.createWallet = old;
+ });
+ it('should be able to create wallets with given pk', function(done) {
+ var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m';
+ args.storage.setItem = sinon.stub();
+ args.storage.setItem.onFirstCall().callsArg(2);
+ args.storage.setItem.onSecondCall().callsArg(2);
+ should.exist(walletClass, 'check walletClass stub');
+ iden.createWallet({
+ privateKeyHex: priv,
+ walletClass: walletClass,
+ }, function(err, w) {
+ should.not.exist(err);
+ w.args.privateKey.toObj().extendedPrivateKeyString.should.equal(priv);
+ done();
+ });
+ });
+
+ it('should be able to create wallets with random pk', function(done) {
+ args.storage.setItem = sinon.stub();
+ args.storage.setItem.onCall(0).callsArg(2);
+ args.storage.setItem.onCall(1).callsArg(2);
+ args.storage.setItem.onCall(2).callsArg(2);
+ args.storage.setItem.onCall(3).callsArg(2);
+ iden.createWallet({
+ walletClass: walletClass,
+ }, function(err, w1) {
+ should.exist(w1);
+
+ iden.createWallet({
+ walletClass: walletClass,
+ }, function(err, w2) {
+ should.exist(w2);
+ w2.args.privateKey.toObj().extendedPrivateKeyString.should.not.equal(
+ w1.args.privateKey.toObj().extendedPrivateKeyString
+ ); + done();
+ });
+ });
+ });
+ });
+
+ describe('#retrieveWalletFromStorage', function() {
+
+
+ it('should return wallet', function(done) {
+ var args = createIdentity();
+ args.storage.getItem.onFirstCall().callsArgWith(1, null, '{"wallet": "fakeData"}');
+ var backup = Wallet.fromUntrustedObj;
+ args.params.noWallets = true;
+
+ sinon.stub().returns(args.wallet);
+
+ var opts = {
+ importWallet: sinon.stub().returns(getNewWallet()),
+ };
+
+ var iden = Identity.create(args.params);
+ iden.retrieveWalletFromStorage('dummy', opts, function(err, wallet) {
+ should.not.exist(err);
+ opts.importWallet.calledOnce.should.equal(true);
+ should.exist(wallet);
+ done();
+ });
+ });
+ });
+
+ describe('#importWallet', function() {
+ it('should import a wallet, call the right encryption functions', function(done) {
+ var args = createIdentity();
+ args.storage.getItem.onFirstCall().callsArgWith(1, null, '{"wallet": "fakeData"}');
+ var backup = Wallet.fromUntrustedObj;
+ args.params.noWallets = true;
+ sinon.stub().returns(args.wallet);
+
+ var fakeCrypto = {
+ kdf: sinon.stub().returns('passphrase'),
+ decrypt: sinon.stub().returns('{"walletId":123}'),
+ };
+
+ var opts = {
+ importWallet: sinon.stub().returns(getNewWallet()),
+ cryptoUtil: fakeCrypto,
+ };
+
+ var iden = Identity.create(args.params);
+ iden.importEncryptedWallet(123, 'password', opts, function(err) {
+ should.not.exist(err);
+ fakeCrypto.kdf.getCall(0).args[0].should.equal('password');
+ fakeCrypto.decrypt.getCall(0).args[0].should.equal('passphrase');
+ fakeCrypto.decrypt.getCall(0).args[1].should.equal(123);
+ done();
+ });
+ });
+ });
+
+
+
+ describe('#export', function() {
+
+ });
+
+ describe('#import', function() {
+
+ });
+
+ /**
+ * TODO (eordano): Move this to a different test file
+ *
+ describe('#pluginManager', function() {
+ it('should create a new PluginManager object', function() {
+ var pm = new PluginManager({plugins: { FakeLocalStorage: true }, pluginsPath: '../../test/mocks/'});
+ should.exist(pm);
+ });
+ });
+
+ describe('#Insight', function() {
+ it('should parse a uri', function() {
+ var uri = Insight.setCompleteUrl('http://someurl.bitpay.com:443');
+ should.exist(uri);
+ });
+ });
+ */
+
+ describe('#joinWallet', function() {
+ var opts = {
+ secret: '8WtTuiFTkhP5ao7AF2QErSwV39Cbur6pdMebKzQXFqL59RscXM',
+ nickname: 'test',
+ password: 'pass'
+ };
+ var iden = null;
+ var args = null;
+ var net = null;
+
+ beforeEach(function() {
+ args = createIdentity();
+ args.params.Async = net = sinon.stub();
+
+ net.cleanUp = sinon.spy();
+ net.on = sinon.stub();
+ net.start = sinon.spy();
+ var old = Identity.prototype.createWallet;
+ Identity.prototype.createWallet = sinon.stub().yields(null, getNewWallet());
+
+ iden = Identity.create(args.params);
+ Identity.prototype.createWallet = old;
+ });
+
+ it('should yield bad network error', function(done) {
+ var net = sinon.stub();
+
+ net.greet = sinon.stub();
+ net.cleanUp = sinon.stub();
+ net.start = sinon.stub().yields(null);
+ net.on = sinon.stub();
+ net.on.withArgs('data').yields('senderId', {
+ type: 'walletId',
+ networkName: 'aWeirdNetworkName',
+ opts: {},
+ });
+
+ opts.privHex = undefined;
+ opts.Async = net;
+ iden.joinWallet(opts, function(err, w) {
+ err.should.equal('badNetwork');
+ done();
+ });
+ });
+
+
+ it('should callback with a join error in case of a problem', function(done) {
+ opts.privHex = undefined;
+ var net = sinon.stub();
+ net.greet = sinon.stub();
+ net.cleanUp = sinon.stub();
+ net.start = sinon.stub().yields(null);
+
+ net.on = sinon.stub();
+ net.on.withArgs('serverError').yields(null);
+ net.on.withArgs('data').yields('senderId', {
+ type: 'walletId',
+ networkName: iden.networkName,
+ });
+ opts.Async = net;
+
+ iden.joinWallet(opts, function(err, w) {
+ err.should.equal('joinError');
+ done();
+ });
+ });
+
+ it('should return walletFull', function(done) {
+ net = sinon.stub();
+ net.on = sinon.stub();
+ net.start = sinon.stub();
+ net.start.onFirstCall().callsArg(1);
+ net.greet = sinon.stub();
+ iden.createWallet = sinon.stub();
+ iden.createWallet.onFirstCall().yields(null,null);
+ net.on.withArgs('data').yields('senderId', {
+ type: 'walletId',
+ networkName: 'testnet',
+ opts: {},
+ });
+ opts.privHex = undefined;
+ opts.Async = net;
+
+ iden.joinWallet(opts, function(err, w) {
+ err.should.equal('walletFull');
+ done();
+ });
+ });
+
+ it('should accept a priv key as an input', function(done) {
+ net = sinon.stub();
+ net.on = sinon.stub();
+ net.start = sinon.stub();
+ net.start.onFirstCall().callsArg(1);
+ net.greet = sinon.stub();
+ iden.createWallet = sinon.stub();
+ var fakeWallet = {
+ sendWalletReady: _.noop
+ };
+ iden.createWallet.onFirstCall().yields(null, fakeWallet);
+ net.on.withArgs('data').yields('senderId', {
+ type: 'walletId',
+ networkName: 'testnet',
+ opts: {},
+ });
+
+ opts.privHex = 'tprv8ZgxMBicQKsPf7MCvCjnhnr4uiR2Z2gyNC27vgd9KUu98F9mM1tbaRrWMyddVju36GxLbeyntuSadBAttriwGGMWUkRgVmUUCg5nFioGZsd';
+ opts.Async = net;
+ iden.joinWallet(opts, function(err, w) {
+ w.should.equal(fakeWallet);
+ done();
+ });
+ });
+ });
+});
diff --git a/test/Passphrase.js b/test/Passphrase.js
deleted file mode 100644
index 5a4ce8948..000000000
--- a/test/Passphrase.js
+++ /dev/null
@@ -1,30 +0,0 @@
-'use strict';
-
-var Passphrase = copay.Passphrase;
-
-describe('Passphrase model', function() {
-
- it('should create an instance', function() {
- var p = new Passphrase();
- should.exist(p);
- });
-
- it('should generate key from password', function(done) {
- var p = new Passphrase({
- salt: 'mjuBtGybi/4=',
- iterations: 10,
- });
- var pass = '123456';
- var k = p.get(pass);
- var k64 = p.getBase64(pass);
-
- // Note: hashes were generated using CryptoJS
- k.toString().should.equal('2283fe11b9a189b82f1c09200806920cbdd8ef752f53dea910f90ab526f441acdbd5128555647a7e390a1a9fea042226963ccd0f7851030b3d6e282ccebaa17e');
- k64.toString().should.equal('IoP+EbmhibgvHAkgCAaSDL3Y73UvU96pEPkKtSb0Qazb1RKFVWR6fjkKGp/qBCImljzND3hRAws9bigszrqhfg==');
-
- p.getBase64Async(pass, function(ret) {
- ret.toString().should.equal('IoP+EbmhibgvHAkgCAaSDL3Y73UvU96pEPkKtSb0Qazb1RKFVWR6fjkKGp/qBCImljzND3hRAws9bigszrqhfg==');
- done();
- });
- });
-});
diff --git a/test/PayPro.js b/test/PayPro.js
index 9741225d3..969c64dcf 100644
--- a/test/PayPro.js
+++ b/test/PayPro.js
@@ -10,9 +10,6 @@ var Address = bitcore.Address;
var PayPro = bitcore.PayPro;
var bignum = bitcore.Bignum;
var startServer = copay.FakePayProServer; // TODO should be require('./mocks/FakePayProServer');
-var localMock = requireMock('FakeLocalStorage');
-var sessionMock = requireMock('FakeLocalStorage');
-var Storage = copay.Storage;
var server;
@@ -21,8 +18,7 @@ var walletConfig = {
totalCopayers: 1,
spendUnconfirmed: true,
reconnectDelay: 100,
- networkName: 'testnet',
- storage: requireMock('FakeLocalStorage').storageParams,
+ networkName: 'testnet'
};
var getNewEpk = function() {
@@ -57,11 +53,8 @@ describe('PayPro (in Wallet) model', function() {
networkName: c.networkName,
});
- var storage = new Storage(walletConfig.storage);
- storage.setPassphrase('xxx');
var network = new Network(walletConfig.network);
var blockchain = new Blockchain(walletConfig.blockchain);
- c.storage = storage;
c.network = network;
c.blockchain = blockchain;
@@ -83,6 +76,14 @@ describe('PayPro (in Wallet) model', function() {
c.networkName = walletConfig.networkName;
c.version = '0.0.1';
+ c.network = sinon.stub();
+ c.network.setHexNonce = sinon.stub();
+ c.network.setHexNonces = sinon.stub();
+ c.network.getHexNonce = sinon.stub();
+ c.network.getHexNonces = sinon.stub();
+ c.network.send = sinon.stub();
+
+
return new Wallet(c);
}
@@ -122,7 +123,14 @@ describe('PayPro (in Wallet) model', function() {
cachedW2obj = cachedW2.toObj();
cachedW2obj.opts.reconnectDelay = 100;
}
- var w = Wallet.fromObj(cachedW2obj, cachedW2.storage, cachedW2.network, cachedW2.blockchain);
+
+ Wallet._newAsync = sinon.stub().returns(new Network(walletConfig.network));
+ Wallet._newInsight = sinon.stub().returns(new Blockchain(walletConfig.blockchain));
+
+ var w = Wallet.fromObj(cachedW2obj, {
+ blockchainOpts: {},
+ networkOpts: {},
+ });
return w;
};
@@ -683,9 +691,11 @@ describe('PayPro (in Wallet) model', function() {
});
});
- it('#add tx proposal based on payment message via model', function(done) {
+ it('#add tx proposal based on payment message via model ', function(done) {
+
var w = ppw;
should.exist(w);
+
w.sendPaymentTx(w._ntxid, function(txid, merchantData) {
should.exist(txid);
should.exist(merchantData);
@@ -735,7 +745,10 @@ describe('PayPro (in Wallet) model', function() {
uri = address.split(/\s+/)[1];
}
- w.createPaymentTx({ uri: uri, memo: commentText }, function(err, ntxid, merchantData) {
+ w.createPaymentTx({
+ uri: uri,
+ memo: commentText
+ }, function(err, ntxid, merchantData) {
should.equal(err, null);
if (w.isShared()) {
should.exist(ntxid);
@@ -757,7 +770,10 @@ describe('PayPro (in Wallet) model', function() {
should.exist(w);
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
var commentText = 'Hello, server. I\'d like to make a payment.';
- w.createPaymentTx({ uri: address, memo: commentText }, function(err, ntxid, merchantData) {
+ w.createPaymentTx({
+ uri: address,
+ memo: commentText
+ }, function(err, ntxid, merchantData) {
should.equal(err, null);
if (w.isShared()) {
should.exist(ntxid);
@@ -778,7 +794,10 @@ describe('PayPro (in Wallet) model', function() {
should.exist(w);
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
var commentText = 'Hello, server. I\'d like to make a payment.';
- w.createPaymentTx({ uri: address, memo: commentText }, function(err, ntxid, merchantData) {
+ w.createPaymentTx({
+ uri: address,
+ memo: commentText
+ }, function(err, ntxid, merchantData) {
should.equal(err, null);
should.exist(ntxid);
should.exist(merchantData);
@@ -815,7 +834,10 @@ describe('PayPro (in Wallet) model', function() {
should.exist(w);
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
var commentText = 'Hello, server. I\'d like to make a payment.';
- w.createPaymentTx({ uri: address, memo: commentText }, function(err, ntxid, merchantData) {
+ w.createPaymentTx({
+ uri: address,
+ memo: commentText
+ }, function(err, ntxid, merchantData) {
should.equal(err, null);
should.exist(ntxid);
should.exist(merchantData);
@@ -843,7 +865,10 @@ describe('PayPro (in Wallet) model', function() {
should.exist(w);
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
var commentText = 'Hello, server. I\'d like to make a payment.';
- w.createPaymentTx({ uri: address, memo: commentText }, function(err, ntxid, merchantData) {
+ w.createPaymentTx({
+ uri: address,
+ memo: commentText
+ }, function(err, ntxid, merchantData) {
should.equal(err, null);
should.exist(ntxid);
should.exist(merchantData);
@@ -870,7 +895,10 @@ describe('PayPro (in Wallet) model', function() {
should.exist(w);
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
var commentText = 'Hello, server. I\'d like to make a payment.';
- w.createPaymentTx({ uri: address, memo: commentText }, function(err, ntxid, merchantData) {
+ w.createPaymentTx({
+ uri: address,
+ memo: commentText
+ }, function(err, ntxid, merchantData) {
should.equal(err, null);
should.exist(ntxid);
should.exist(merchantData);
diff --git a/test/PublicKeyRing.js b/test/PublicKeyRing.js
index 77cd47b88..26d819a85 100644
--- a/test/PublicKeyRing.js
+++ b/test/PublicKeyRing.js
@@ -167,7 +167,7 @@ describe('PublicKeyRing model', function() {
var a = as[j];
a.address.isValid().should.equal(true);
a.addressStr.should.equal(a.address.toString());
- a.isChange.should.equal([false, false, false, true, true][j]);
+ a.isChange.should.equal([false, true, true, false, false][j]);
}
});
@@ -191,58 +191,6 @@ describe('PublicKeyRing model', function() {
w.getHDParams(k.pub).getReceiveIndex().should.equal(2);
});
- it('should set backup ready', function() {
- var w = getCachedW().w;
- w.isBackupReady().should.equal(false);
- w.setBackupReady();
- w.isBackupReady().should.equal(true);
- });
-
-
- it('should check for other backups', function() {
- var w = createW().w;
- w.remainingBackups().should.equal(5);
- w.isFullyBackup().should.equal(false);
-
- w.setBackupReady();
- w.remainingBackups().should.equal(4);
- w.isFullyBackup().should.equal(false);
-
- w.copayersBackup = ["a", "b", "c", "d", "e"];
- w.remainingBackups().should.equal(0);
- w.isFullyBackup().should.equal(true);
- });
-
- it('should merge backup', function() {
- var w = getCachedW().w;
- var hasChanged;
-
- w.copayersBackup = ["a", "b"];
- hasChanged = w._mergeBackups(["b", "c"]);
- w.copayersBackup.length.should.equal(3);
- hasChanged.should.equal(true);
-
- w.copayersBackup = ["a", "b", "c"];
- hasChanged = w._mergeBackups(["b", "c"]);
- w.copayersBackup.length.should.equal(3);
- hasChanged.should.equal(false);
- });
-
- it('should merge backup tests', function() {
- var w = createW().w;
-
- var w2 = new PublicKeyRing({
- networkName: 'livenet',
- walletId: w.walletId,
- });
- w.merge(w2).should.equal(false);
- w.remainingBackups().should.equal(5);
-
- w2.setBackupReady();
- w.merge(w2).should.equal(true);
- w.remainingBackups().should.equal(4);
- });
-
it('#merge index tests', function() {
var k = createW();
var w = k.w;
diff --git a/test/Storage.js b/test/Storage.js
deleted file mode 100644
index a528044a8..000000000
--- a/test/Storage.js
+++ /dev/null
@@ -1,460 +0,0 @@
-'use strict';
-var Storage = copay.Storage;
-
-var fakeWallet = 'fake-wallet-id';
-var timeStamp = Date.now();
-
-describe('Storage model', function() {
-
- var s;
- beforeEach(function() {
- s = new Storage(requireMock('FakeLocalStorage').storageParams);
- s.setPassphrase('mysupercoolpassword');
- s.storage.clear();
- s.sessionStorage.clear();
- });
-
-
- it('should create an instance', function() {
- var s2 = new Storage(requireMock('FakeLocalStorage').storageParams);
- should.exist(s2);
- });
- it('should fail when encrypting without a password', function() {
- var s2 = new Storage(requireMock('FakeLocalStorage').storageParams);
- (function() {
- s2._write(fakeWallet + timeStamp, 1, function() {});
- }).should.throw('NOPASSPHRASE');
- });
- it('should be able to encrypt and decrypt', function(done) {
- s._write(fakeWallet + timeStamp, 'value', function() {
- s._read(fakeWallet + timeStamp, function(v) {
- v.should.equal('value');
- done();
- });
- });
- });
- it('should be able to set a value', function(done) {
- s._write(fakeWallet + timeStamp, 1, function() {
- done();
- });
- });
- var getSetData = [
- 1, 1000, -15, -1000,
- 0.1, -0.5, -0.5e-10, Math.PI,
- 'hi', 'auydoaiusyodaisudyoa', '0b5b8556a0c2ce828c9ccfa58b3dd0a1ae879b9b',
- '1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC', 'OP_DUP OP_HASH160 80ad90d4035', [1, 2, 3, 4, 5, 6], {
- x: 1,
- y: 2
- }, {
- x: 'hi',
- y: null
- }, {
- a: {},
- b: [],
- c: [1, 2, 'hi']
- },
- null
- ];
- getSetData.forEach(function(obj) {
- it('should be able to set a value and get it for ' + JSON.stringify(obj), function(done) {
- s._write(fakeWallet + timeStamp, obj, function() {
- s._read(fakeWallet + timeStamp, function(obj2) {
- JSON.stringify(obj2).should.equal(JSON.stringify(obj));
- done();
- });
- });
- });
- });
-
- describe('#export', function() {
- it('should export the encrypted wallet', function(done) {
- s._write(fakeWallet + timeStamp, 'testval', function() {
- var obj = {
- test: 'testval'
- };
- var encrypted = s.export(obj);
- encrypted.length.should.be.greaterThan(10);
- done();
- });
- });
- });
-
- describe('#_getWalletIds', function() {
- it('should get wallet ids', function(done) {
- s._write('1::hola', 'juan', function() {
- s._write('2::hola', 'juan', function() {
- s._getWalletIds(function(v) {
- v.should.deep.equal(['1', '2']);
- done();
- });
- });
- });
- });
- });
-
- describe('#getLastOpened #setLastOpened', function() {
- it('should get/set last opened', function() {
- s.setLastOpened('hey', function() {
- s.getLastOpened(function(v) {
- v.should.equal('hey');
- });
- });
- });
- });
-
- if (is_browser) {
- describe('#getSessionId', function() {
- it('should get SessionId', function(done) {
- s.getSessionId(function(sid) {
- should.exist(sid);
- s.getSessionId(function(sid2) {
- sid2.should.equal(sid);
- done();
- });
- });
- });
- });
- }
-
- describe('#getWallets_Old', function() {
- it('should retrieve wallets from storage', function(done) {
- s._write('1::hola', 'juan', function() {
- s._write('2::hola', 'juan', function() {
- s.setGlobal('nameFor::1', 'hola', function() {
-
- s.getWallets_Old(function(ws) {
- ws[0].should.deep.equal({
- id: '1',
- name: 'hola',
- });
- ws[1].should.deep.equal({
- id: '2',
- name: undefined
- });
- done();
- });
- });
- });
- });
- });
- it('should retrieve wallets from storage (with delay)', function(done) {
- s._write('1::hola', 'juan', function() {
- s._write('2::hola', 'juan', function() {
- s.setGlobal('nameFor::1', 'hola', function() {
-
- var orig = s.getGlobal.bind(s);
- s.getGlobal = function(k, cb) {
- setTimeout(function() {
- orig(k, cb);
- }, 1);
- };
-
- s.getWallets_Old(function(ws) {
- ws[0].should.deep.equal({
- id: '1',
- name: 'hola',
- });
- ws[1].should.deep.equal({
- id: '2',
- name: undefined
- });
- done();
- });
- });
- });
- });
- });
- });
-
- describe('#getWallets2', function() {
- it('should retrieve wallets from storage', function(done) {
- var w1 = {
- name: 'juan',
- opts: {
- name: 'wallet1'
- }
- };
- var w2 = {
- name: 'pepe'
- };
- s.setFromObj('1', w1, function() {
- s.setFromObj('2', w2, function() {
- s.getWallets2(function(ws) {
- ws[0].should.deep.equal({
- id: '1',
- name: 'wallet1',
- });
- ws[1].should.deep.equal({
- id: '2',
- name: undefined
- });
- done();
- });
- });
- });
- });
- });
-
-
- describe('#getWallets', function() {
- it('should retrieve wallets from storage both new and old format', function(done) {
- var w1 = {
- name: 'juan',
- opts: {
- name: 'wallet1'
- }
- };
- var w2 = {
- name: 'pepe'
- };
-
- s.setFromObj('1', w1, function() {
- s.setFromObj('2', w2, function() {
- s._write('3::name', 'matias', function() {
- s._write('1::name', 'juan', function() {
- s.setGlobal('nameFor::3', 'wallet3', function() {
- s.getWallets(function(ws) {
- ws.length.should.equal(3);
- ws[0].should.deep.equal({
- id: '1',
- name: 'wallet1',
- });
- ws[1].should.deep.equal({
- id: '2',
- name: undefined
- });
- ws[2].should.deep.equal({
- id: '3',
- name: 'wallet3',
- });
- done();
- });
- });
- });
- })
- });
- });
- });
- });
-
- describe('#deleteWallet_Old', function() {
- it('should fail to delete a unexisting wallet', function(done) {
- s._write('1::hola', 'juan', function() {
- s._write('2::hola', 'juan', function() {
- s.deleteWallet_Old('3', function(err) {
- err.toString().should.include('WNOTFOUND');
- done();
- });
- });
- });
- });
-
- it('should delete a wallet', function(done) {
- s._write('1::hola', 'juan', function() {
- s._write('2::hola', 'juan', function() {
- s.deleteWallet_Old('1', function(err) {
- should.not.exist(err);
- s.getWallets_Old(function(ws) {
- ws.length.should.equal(1);
- ws[0].should.deep.equal({
- id: '2',
- name: undefined
- });
- done();
- });
- });
- });
- });
- });
- });
-
- describe('#deleteWallet', function() {
- it('should fail to delete a unexisting wallet', function(done) {
- var w1 = {
- name: 'juan',
- opts: {
- name: 'wallet1'
- }
- };
- var w2 = {
- name: 'pepe'
- };
-
- s.setFromObj('1', w1, function() {
- s.setFromObj('2', w2, function() {
- s.deleteWallet('3', function(err) {
- err.toString().should.include('WNOTFOUND');
- done();
- });
- });
- });
- });
-
- it('should delete a wallet', function(done) {
- var w1 = {
- name: 'juan',
- opts: {
- name: 'wallet1'
- }
- };
- var w2 = {
- name: 'pepe'
- };
-
- s.setFromObj('1', w1, function() {
- s.setFromObj('2', w2, function() {
- s.deleteWallet('1', function(err) {
- should.not.exist(err);
- s.getWallets2(function(ws) {
- ws.length.should.equal(1);
- ws[0].id.should.equal('2');
- done();
- });
- });
- });
- });
- });
- });
-
- describe('#readWallet_Old', function() {
- it('should read wallet', function(done) {
- var data = {
- 'id1::a': 'x',
- 'id1::b': 'y',
- 'id2::c': 'z',
- };
- s.storage.allKeys = sinon.stub().yields(_.keys(data));
- sinon.stub(s, '_read', function(k, cb) {
- return cb(data[k]);
- });
- s.readWallet_Old('id1', function(err, w) {
- should.not.exist(err);
- w.should.exist;
- w.hasOwnProperty('a').should.be.true;
- w.hasOwnProperty('b').should.be.true;
- w.hasOwnProperty('c').should.be.false;
- w.a.should.equal('x');
- w.b.should.equal('y');
- s._read.restore();
- done();
- });
- });
- });
-
- describe('#readWallet', function() {
- it('should read wallet', function(done) {
- var data = {
- 'wallet::id1_wallet1': {
- a: 'x',
- b: 'y'
- },
- 'wallet::id2': {
- c: 'z'
- },
- };
- s.storage.allKeys = sinon.stub().yields(_.keys(data));
- sinon.stub(s, '_read', function(k, cb) {
- return cb(data[k]);
- });
- s.readWallet('id1', function(err, w) {
- should.not.exist(err);
- w.should.exist;
- w.hasOwnProperty('a').should.be.true;
- w.hasOwnProperty('b').should.be.true;
- w.hasOwnProperty('c').should.be.false;
- w.a.should.equal('x');
- w.b.should.equal('y');
- s._read.restore();
- done();
- });
- });
- });
-
- describe('#setFromObj', function() {
- it('should store from an object as single key', function(done) {
- s.setFromObj('id1', {
- 'key': 'val',
- 'opts': {
- 'name': 'nameid1'
- },
- }, function() {
- s._read('wallet::id1_nameid1', function(r) {
- r.should.exist;
- r.key.should.exist;
- r.key.should.equal('val');
- r.opts.name.should.equal('nameid1');
- done();
- });
- });
- });
- });
-
- describe('#globals', function() {
- it('should set, get and remove keys', function(done) {
- s.setGlobal('a', {
- b: 1
- }, function() {
- s.getGlobal('a', function(v) {
-
- JSON.parse(v).should.deep.equal({
- b: 1
- });
- s.removeGlobal('a', function() {
- s.getGlobal('a', function(v) {
- should.not.exist(v);
- done();
- });
- });
- });
- });
- });
- });
-
-
- describe('session storage', function() {
- it('should get a session ID', function(done) {
- s.getSessionId(function(s) {
- should.exist(s);
- s.length.should.equal(16);
- (new Buffer(s, 'hex')).length.should.equal(8);
- done();
- });
- });
- });
-
- describe('#import', function() {
- it('should not be able to decrypt with wrong password', function() {
- s.setPassphrase('xxx');
- var wo = s.import(encryptedLegacy1);
- should.not.exist(wo);
- });
-
- it('should be able to decrypt an old backup', function() {
- s.setPassphrase(legacyPassword1);
- var wo = s.import(encryptedLegacy1);
- should.exist(wo);
- wo.opts.id.should.equal('48ba2f1ffdfe9708');
- wo.opts.spendUnconfirmed.should.equal(true);
- wo.opts.requiredCopayers.should.equal(1);
- wo.opts.totalCopayers.should.equal(1);
- wo.opts.name.should.equal('pepe wallet');
- wo.opts.version.should.equal('0.4.7');
- wo.publicKeyRing.walletId.should.equal('48ba2f1ffdfe9708');
- wo.publicKeyRing.networkName.should.equal('testnet');
- wo.publicKeyRing.requiredCopayers.should.equal(1);
- wo.publicKeyRing.totalCopayers.should.equal(1);
- wo.publicKeyRing.indexes.length.should.equal(2);
- JSON.stringify(wo.publicKeyRing.indexes[0]).should.equal('{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1}');
- JSON.stringify(wo.publicKeyRing.indexes[1]).should.equal('{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}');
- wo.publicKeyRing.copayersBackup.length.should.equal(1);
- wo.publicKeyRing.copayersBackup[0].should.equal('0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f');
- wo.publicKeyRing.copayersExtPubKeys.length.should.equal(1);
- wo.publicKeyRing.copayersExtPubKeys[0].should.equal('tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6');
- wo.privateKey.extendedPrivateKeyString.should.equal('tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm');
- wo.privateKey.networkName.should.equal('testnet');
- });
- });
-});
-
-var legacyPassword1 = '1DUpLRbuVpgLkcEY8gY8iod/SmA7+OheGZJ9PtvmTlvNE0FkEWpCKW9STdzXYJqbn0wiAapE4ojHNYj2hjYYAQ==';
-var encryptedLegacy1 = 'U2FsdGVkX19yGM1uBAIzQa8Po/dvUicmxt1YyRk/S97PcZ6I6rHMp9dMagIrehg4Qd6JHn/ustmFHS7vmBYj0EBpf6rdXiQezaWnVAJS9/xYjAO36EFUbl+NmUanuwujAxgYdSP/sNssRLeInvExmZYW993EEclxkwL6YUyX66kKsxGQo2oWng0NreBJNhFmrbOEWeFje2PiWP57oUjKsurFzwpluAAarUTYSLud+nXeabC7opzOP5yqniWBMJz0Ou8gpNCWCMhG/P9F9ccVPY7juyd0Hf41FVse8nd2++axKB57+paozLdO+HRfV6zkMqC3h8gWY7LkS75j3bvqcTw9LhXmzE0Sz21n9yDnRpA4chiAvtwQvvBGgj1pFMKhNQU6Obac9ZwKYzUTgdDn3Uzg1UlDzgyOh9S89rbRTV84WB+hXwhuVluWzbNNYV3vXe5PFrocVktIrtS3xQh+k/7my4A6/gRRrzNYpKrUASJqDS/9u9WBkG35xD63J/qXjtG2M0YPwbI57BK1IK4K510b8V72lz5U2XQrIC4ldBwni1rpSavwCJV9xF6hUdOmNV8fZsVHP0NeN1PYlLkSb2QgfuoWnkcsJerwuFR7GZC/i6efrswtpO0wMEQr/J0CLbeXlHAru6xxjCBhWoJvZpMGw72zgnDLoyMNsEVglNhx/VlV9ZMYkkdaEYAxPOEIyZdQ5MS+2jEAlXf818n/xzJSVrniCn9be8EPePvkw35pivprvy09vbW4cKsWBKvgIyoT6A3OhUOCCS8E9cg0WAjjav2EymrbKmGWRHaiD+EoJqaDg6s20zhHn1YEa/YwvGGSB5+Hg8baLHD8ZASvxz4cFFAAVZrBUedRFgHzqwaMUlFXLgueivWUj7RXlIw6GuNhLoo1QkhZMacf23hrFxxQYvGBRw1hekBuDmcsGWljA28udBxBd5f9i+3gErttMLJ6IPaud590uvrxRIclu0Sz9R2EQX64YJxqDtLpMY0PjddSMu8vaDRpK9/ZSrnz/xrXsyabaafz4rE/ItFXjwFUFkvtmuauHTz6nmuKjVfxvNLNAiKb/gI7vQyUhnTbKIApe7XyJsjedNDtZqsPoJRIzdDmrZYxGStbAZ7HThqFJlSJ9NPNhH+E2jm3TwL5mwt0fFZ5h+p497lHMtIcKffESo7KNa2juSVNMDREk0NcyxGXGiVB2FWl4sLdvyhcsVq0I7tmW6OGZKRf8W49GCJXq6Ie69DJ9LB1DO67NV1jsYbsLx9uhE2yEmpWZ3jkoCV/Eas4grxt0CGN6EavzQ==';
diff --git a/test/Wallet.js b/test/Wallet.js
index 8fd2fa218..e64202d89 100644
--- a/test/Wallet.js
+++ b/test/Wallet.js
@@ -2,7 +2,6 @@
var Wallet = copay.Wallet;
var PrivateKey = copay.PrivateKey;
-var Storage = copay.Storage;
var Network = requireMock('FakeNetwork');
var Blockchain = requireMock('FakeBlockchain');
var Builder = requireMock('FakeBuilder');
@@ -10,15 +9,39 @@ var TransactionBuilder = bitcore.TransactionBuilder;
var Transaction = bitcore.Transaction;
var Address = bitcore.Address;
+
+function assertObjectEqual(a, b) {
+ Wallet.PERSISTED_PROPERTIES.forEach(function(k) {
+ if (a[k] && b[k]) {
+ _.omit(a[k], 'name').should.be.deep.equal(b[k], k + ' differs');
+ }
+ })
+}
+
+
var walletConfig = {
requiredCopayers: 3,
totalCopayers: 5,
spendUnconfirmed: true,
reconnectDelay: 100,
networkName: 'testnet',
- storage: requireMock('FakeLocalStorage').storageParams,
+ // network layer config
+ networkOpts: {
+ testnet: {
+ url: 'https://test-insight.bitpay.com:443',
+ transports: ['polling'],
+ },
+ livenet: {
+ url: 'https://insight.bitpay.com:443',
+ transports: ['polling'],
+ },
+ },
};
+
+walletConfig.blockchainOpts = walletConfig.networkOpts;
+
+
var getNewEpk = function() {
return new PrivateKey({
networkName: walletConfig.networkName,
@@ -69,13 +92,17 @@ describe('Wallet model', function() {
networkName: c.networkName,
});
- var storage = new Storage(walletConfig.storage);
- storage.setPassphrase('xxx');
- var network = new Network(walletConfig.network);
- var blockchain = new Blockchain(walletConfig.blockchain);
- c.storage = storage;
- c.network = network;
- c.blockchain = blockchain;
+ c.blockchain = new Blockchain(walletConfig.blockchain);
+
+ c.network = sinon.stub();
+ c.network.setHexNonce = sinon.stub();
+ c.network.setHexNonces = sinon.stub();
+ c.network.getHexNonce = sinon.stub();
+ c.network.getHexNonces = sinon.stub();
+ c.network.peerFromCopayer = sinon.stub().returns('xxxx');
+ c.network.send = sinon.stub();
+
+
c.addressBook = {
'2NFR2kzH9NUdp8vsXTB4wWQtTtzhpKxsyoJ': {
@@ -95,7 +122,6 @@ describe('Wallet model', function() {
c.networkName = walletConfig.networkName;
c.version = '0.0.1';
-
return new Wallet(c);
}
@@ -107,7 +133,13 @@ describe('Wallet model', function() {
cachedWobj = cachedW.toObj();
cachedWobj.opts.reconnectDelay = 100;
}
- var w = Wallet.fromObj(cachedWobj, cachedW.storage, cachedW.network, cachedW.blockchain);
+ Wallet._newAsync = sinon.stub().returns(new Network(walletConfig.network));
+ Wallet._newInsight = sinon.stub().returns(new Blockchain(walletConfig.blockchain));
+
+ var w = Wallet.fromObj(cachedWobj, {
+ blockchainOpts: {},
+ networkOpts: {},
+ });
return w;
};
@@ -173,10 +205,17 @@ describe('Wallet model', function() {
cachedW2obj = cachedW2.toObj();
cachedW2obj.opts.reconnectDelay = 100;
}
- var w = Wallet.fromObj(cachedW2obj, cachedW2.storage, cachedW2.network, cachedW2.blockchain);
+ Wallet._newAsync = sinon.stub().returns(new Network(walletConfig.network));
+ Wallet._newInsight = sinon.stub().returns(new Blockchain(walletConfig.blockchain));
+
+ var w = Wallet.fromObj(cachedW2obj, {
+ blockchainOpts: {},
+ networkOpts: {},
+ });
return w;
};
+
it('#create, fail for network', function() {
var w = cachedCreateW2();
@@ -331,12 +370,10 @@ describe('Wallet model', function() {
// non stored options
o.opts.reconnectDelay = 100;
- var s = new Storage(walletConfig.storage);
- s.setPassphrase('xxx');
- var w2 = Wallet.fromObj(o,
- s,
- new Network(walletConfig.network),
- new Blockchain(walletConfig.blockchain));
+ var w2 = Wallet.fromObj(o, {
+ blockchainOpts: {},
+ networkOpts: {},
+ });
should.exist(w2);
w2.publicKeyRing.requiredCopayers.should.equal(w.publicKeyRing.requiredCopayers);
should.exist(w2.publicKeyRing.getCopayerId);
@@ -371,20 +408,15 @@ describe('Wallet model', function() {
it('decodeSecret check', function() {
- (function() {
- Wallet.decodeSecret('4fp61K187CsYmjoRQC5iAdC5eGmbCRsAAXfwEwetSQgHvZs27eWKaLaNHRoKM');
- }).should.not.
- throw();
+ var s = Wallet.decodeSecret('4fp61K187CsYmjoRQC5iAdC5eGmbCRsAAXfwEwetSQgHvZs27eWKaLaNHRoKM');
+ should.exist(s);
- (function() {
- Wallet.decodeSecret('4fp61K187CsYmjoRQC5iAdC5eGmbCRsAAXfwEwetSQgHvZs27eWKaLaNHRoK');
- }).should.
- throw();
+ s = Wallet.decodeSecret('4fp61K187CsYmjoRQC5iAdC5eGmbCRsAAXfwEwetSQgHvZs27eWKaLaNHRoK');
+ s.should.equal(false);
- (function() {
- Wallet.decodeSecret('12345');
- }).should.
- throw();
+
+ s = Wallet.decodeSecret('123456');
+ s.should.equal(false);
});
@@ -507,10 +539,6 @@ describe('Wallet model', function() {
var w2 = createW2();
w2.publicKeyRing.isComplete().should.equal(true);
- w2.isReady().should.equal(false);
-
- w2.publicKeyRing.copayersBackup = ["a", "b", "c"];
- w2.publicKeyRing.isFullyBackup().should.equal(true);
w2.isReady().should.equal(true);
});
@@ -603,8 +631,10 @@ describe('Wallet model', function() {
var newId = '00bacacafe';
it('handle new connections', function(done) {
var w = createW();
+ w.sendWalletId = sinon.stub();
+
w.on('connect', function(id) {
- id.should.equal(newId);
+ id.should.equal('xxxx');
done();
});
w._onConnect(newId);
@@ -1106,7 +1136,7 @@ describe('Wallet model', function() {
var indexDiscovery = sinon.stub(w, 'indexDiscovery', function(a, b, c, d, cb) {
cb(null, 8);
});
- var spyStore = sinon.spy(w, 'store');
+ var spyStore = sinon.spy(w, 'emitAndKeepAlive');
w.updateIndexes(function(err) {
sinon.assert.callCount(spyStore, 1);
done();
@@ -1716,18 +1746,18 @@ describe('Wallet model', function() {
};
txp.prototype.toObj = function() {};
- var spy1 = sinon.spy(w, 'store');
+ var spy1 = sinon.spy(w, 'emitAndKeepAlive');
var spy2 = sinon.spy(w, 'emit');
w.txProposals.txps['qwerty'] = new txp();
w.txProposals.txps['qwerty'].ok.should.equal(0);
+ spy2.callCount.should.equal(0);
w._onReject('john', {
ntxid: 'qwerty'
}, 1);
w.txProposals.txps['qwerty'].ok.should.equal(1);
spy1.calledOnce.should.equal(true);
- spy2.callCount.should.equal(2);
- spy2.firstCall.args.should.deep.equal(['txProposalsUpdated']);
- spy2.secondCall.args.should.deep.equal(['txProposalEvent', {
+ spy2.callCount.should.equal(1);
+ spy2.firstCall.args.should.deep.equal(['txProposalEvent', {
type: 'rejected',
cId: 'john',
txId: 'qwerty',
@@ -1757,18 +1787,15 @@ describe('Wallet model', function() {
};
txp.prototype.toObj = function() {};
- var spy1 = sinon.spy(w, 'store');
- var spy2 = sinon.spy(w, 'emit');
+ var spy2 = sinon.spy(w, 'emitAndKeepAlive');
w.txProposals.txps['qwerty'] = new txp();
w.txProposals.txps['qwerty'].ok.should.equal(0);
w._onSeen('john', {
ntxid: 'qwerty'
}, 1);
w.txProposals.txps['qwerty'].ok.should.equal(1);
- spy1.calledOnce.should.equal(true);
- spy2.callCount.should.equal(2);
- spy2.firstCall.args.should.deep.equal(['txProposalsUpdated']);
- spy2.secondCall.args.should.deep.equal(['txProposalEvent', {
+ spy2.callCount.should.equal(1);
+ spy2.firstCall.args.should.deep.equal(['txProposalEvent', {
type: 'seen',
cId: 'john',
txId: 'qwerty',
@@ -1783,6 +1810,33 @@ describe('Wallet model', function() {
should.exist(n.networkNonce);
});
+
+ describe('#obtainNetworkName', function() {
+ it('should return the networkname', function() {
+ Wallet.obtainNetworkName({
+ networkName: 'testnet',
+ }).should.equal('testnet');
+ Wallet.obtainNetworkName({
+ opts: {
+ networkName: 'testnet'
+ }
+ }).should.equal('testnet');
+ Wallet.obtainNetworkName({
+ publicKeyRing: {
+ networkName: 'testnet'
+ }
+ }).should.equal('testnet');
+
+ Wallet.obtainNetworkName({
+ privateKey: {
+ networkName: 'testnet'
+ }
+ }).should.equal('testnet');
+ });
+ });
+
+
+
it('should emit notification when tx received', function(done) {
var w = cachedCreateW2();
w.blockchain.removeAllListeners = sinon.stub();
@@ -1806,4 +1860,399 @@ describe('Wallet model', function() {
});
});
+ describe('#fromObj / #toObj', function() {
+ var network = new Network(walletConfig.network);
+ var blockchain = new Blockchain(walletConfig.blockchain);
+
+ it('Import backup using old copayerIndex', function() {
+
+ var w = Wallet.fromObj(JSON.parse(o), {
+ blockchainOpts: {},
+ networkOpts: {},
+ });
+
+ should.exist(w);
+ w.id.should.equal("dbfe10c3fae71cea");
+ should.exist(w.publicKeyRing.getCopayerId);
+ should.exist(w.txProposals.toObj());
+ should.exist(w.privateKey.toObj());
+ assertObjectEqual(w.toObj(), JSON.parse(o));
+ });
+
+ it('#fromObj, skipping fields', function() {
+ var w = Wallet.fromObj(JSON.parse(o), {
+ networkOpts: {},
+ blockchainOpts: {},
+ skipFields: ['publicKeyRing'],
+ });
+
+ should.exist(w);
+ w.id.should.equal("dbfe10c3fae71cea");
+ should.exist(w.publicKeyRing.getCopayerId);
+ should.exist(w.txProposals.toObj());
+ should.exist(w.privateKey.toObj());
+ (function() {
+ assertObjectEqual(w.toObj(), JSON.parse(o))
+ }).should.throw();
+ });
+
+ 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"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
+ var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":0,"receiveIndex":0},{"copayerIndex":1,"changeIndex":0,"receiveIndex":0},{"copayerIndex":2,"changeIndex":0,"receiveIndex":0},{"copayerIndex":3,"changeIndex":0,"receiveIndex":0},{"copayerIndex":4,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
+
+ var w = Wallet.fromObj(JSON.parse(o), {
+ networkOpts: {},
+ blockchainOpts: {},
+ });
+
+ should.exist(w);
+ w.id.should.equal("dbfe10c3fae71cea");
+ should.exist(w.publicKeyRing.getCopayerId);
+ should.exist(w.txProposals.toObj);
+ should.exist(w.privateKey.toObj);
+
+ assertObjectEqual(w.toObj(), JSON.parse(o2));
+ });
+ });
+
+ describe('#getTransactionHistory', function() {
+ it('should return list of txs', function(done) {
+ var w = cachedCreateW2();
+ var txs = [{
+ vin: [{
+ addr: 'addr_in_1',
+ valueSat: 1000
+ }],
+ vout: [{
+ scriptPubKey: {
+ addresses: ['addr_out_1'],
+ },
+ value: '0.00000900',
+ }],
+ fees: 0.00000100
+ }, {
+ vin: [{
+ addr: 'addr_in_2',
+ valueSat: 2000
+ }],
+ vout: [{
+ scriptPubKey: {
+ addresses: ['addr_out_2'],
+ },
+ value: '0.00001900',
+ }],
+ fees: 0.00000100
+ }, {
+ vin: [{
+ addr: 'addr_in_1',
+ valueSat: 3000
+
+ }],
+ vout: [{
+ scriptPubKey: {
+ addresses: ['addr_out_2'],
+ },
+ value: '0.00002900',
+
+ }],
+ fees: 0.00000100
+ }];
+
+ w.blockchain.getTransactions = sinon.stub().yields(null, txs);
+ w.getAddressesInfo = sinon.stub().returns([{
+ addressStr: 'addr_in_1'
+ }, {
+ addressStr: 'addr_out_2'
+ }]);
+
+ w.getTransactionHistory(function(err, res) {
+ res.should.exist;
+ res.length.should.equal(3);
+ res[0].action.should.equal('sent');
+ res[0].amountSat.should.equal(900);
+ res[1].action.should.equal('received');
+ res[1].amountSat.should.equal(1900);
+ res[2].action.should.equal('moved');
+ res[2].amountSat.should.equal(2900);
+ done();
+ });
+ });
+ it('should compute sent amount correctly', function(done) {
+ var w = cachedCreateW2();
+ var txs = [{
+ vin: [{
+ addr: 'addr_in_1',
+ valueSat: 3000
+ }, {
+ addr: 'addr_in_2',
+ valueSat: 2000
+ }],
+ vout: [{
+ scriptPubKey: {
+ addresses: ['addr_out_1'],
+ },
+ value: '0.00003900',
+ }, {
+ scriptPubKey: {
+ addresses: ['change'],
+ },
+ value: '0.00001000',
+ }],
+ fees: 0.00000100
+ }];
+
+ w.blockchain.getTransactions = sinon.stub().yields(null, txs);
+ w.getAddressesInfo = sinon.stub().returns([{
+ addressStr: 'addr_in_1'
+ }, {
+ addressStr: 'addr_in_2'
+ }, {
+ addressStr: 'change',
+ isChange: true,
+ }]);
+
+ w.getTransactionHistory(function(err, res) {
+ res.should.exist;
+ res[0].action.should.equal('sent');
+ res[0].amountSat.should.equal(3900);
+ done();
+ });
+ });
+ it('should compute moved amount correctly', function(done) {
+ var w = cachedCreateW2();
+ var txs = [{
+ vin: [{
+ addr: 'addr_1',
+ valueSat: 3000
+ }, {
+ addr: 'addr_2',
+ valueSat: 2000
+ }],
+ vout: [{
+ scriptPubKey: {
+ addresses: ['addr_1'],
+ },
+ value: '0.00003900',
+ }, {
+ scriptPubKey: {
+ addresses: ['change'],
+ },
+ value: '0.00001000',
+ }],
+ fees: 0.00000100
+ }];
+
+ w.blockchain.getTransactions = sinon.stub().yields(null, txs);
+ w.getAddressesInfo = sinon.stub().returns([{
+ addressStr: 'addr_1'
+ }, {
+ addressStr: 'addr_2'
+ }, {
+ addressStr: 'change',
+ isChange: true,
+ }]);
+
+ w.getTransactionHistory(function(err, res) {
+ res.should.exist;
+ res[0].action.should.equal('moved');
+ res[0].amountSat.should.equal(3900);
+ done();
+ });
+ });
+ it('should assign label when address in address book', function(done) {
+ var w = cachedCreateW2();
+ var txs = [{
+ vin: [{
+ addr: 'addr_in_1',
+ valueSat: 3000
+ }, {
+ addr: 'addr_in_2',
+ valueSat: 2000
+ }],
+ vout: [{
+ scriptPubKey: {
+ addresses: ['addr_out_1'],
+ },
+ value: '0.00003900',
+ }, {
+ scriptPubKey: {
+ addresses: ['change'],
+ },
+ value: '0.00001000',
+ }],
+ fees: 0.00000100
+ }];
+
+ w.blockchain.getTransactions = sinon.stub().yields(null, txs);
+ w.getAddressesInfo = sinon.stub().returns([{
+ addressStr: 'addr_in_1'
+ }, {
+ addressStr: 'addr_in_2'
+ }, {
+ addressStr: 'change',
+ isChange: true,
+ }]);
+
+ w.addressBook = {
+ 'addr_out_1': {
+ label: 'Address out one'
+ },
+ };
+
+ w.getTransactionHistory(function(err, res) {
+ res.should.exist;
+ res[0].labelTo.should.equal('Address out one');
+ done();
+ });
+ });
+ it('should assign comment from tx proposal if found', function(done) {
+ var w = cachedCreateW2();
+ var txs = [{
+ txid: 'id1',
+ vin: [{
+ addr: 'addr_in_1',
+ valueSat: 3000
+ }, {
+ addr: 'addr_in_2',
+ valueSat: 2000
+ }],
+ vout: [{
+ scriptPubKey: {
+ addresses: ['addr_out_1'],
+ },
+ value: '0.00003900',
+ }, {
+ scriptPubKey: {
+ addresses: ['change'],
+ },
+ value: '0.00001000',
+ }],
+ fees: 0.00000100
+ }];
+
+ w.blockchain.getTransactions = sinon.stub().yields(null, txs);
+ w.getAddressesInfo = sinon.stub().returns([{
+ addressStr: 'addr_in_1'
+ }, {
+ addressStr: 'addr_in_2'
+ }, {
+ addressStr: 'change',
+ isChange: true,
+ }]);
+
+ w.getTxProposals = sinon.stub().returns([{
+ sentTxid: 'id0',
+ comment: 'My comment',
+ }, {
+ sentTxid: 'id1',
+ comment: 'Another comment',
+ }]);
+ w.getTransactionHistory(function(err, res) {
+ res.should.exist;
+ res[0].comment.should.equal('Another comment');
+ done();
+ });
+ });
+ });
+
+
+ describe.skip('#read', function() {
+ var network, blockchain;
+
+ beforeEach(function() {
+ var s = function() {};
+ network = new Network(walletConfig.network);
+ blockchain = new Blockchain(walletConfig.blockchain);
+ });
+
+
+ it('should fail to read an unexisting wallet', function(done) {
+
+ Wallet.read('123', {
+ networkOpts: {},
+ blockchainOpts: {},
+ }, function(err, w) {
+ err.toString().should.contain('WNOTFOUND');
+ done();
+ });
+ });
+
+ it('should not read a corrupted wallet', function(done) {
+
+ Wallet.read('123', {
+ networkOpts: {},
+ blockchainOpts: {},
+ }, function(err, w) {
+ err.toString().should.contain('WERROR');
+ done();
+ });
+ });
+
+ it('should read a wallet', function(done) {
+ Wallet.read('123', {
+ networkOpts: {},
+ blockchainOpts: {},
+ }, function(err, w) {
+ should.not.exist(err);
+ done();
+ });
+ });
+
+ it('should be able to import unencrypted legacy wallet TxProposal: v0', function(done) {
+ Wallet.read('123', {
+ networkOpts: {},
+ blockchainOpts: {},
+ }, function(err, w) {
+ should.exist(w);
+ w.id.should.equal('55d4bd062d32f90a');
+ should.exist(w.publicKeyRing.getCopayerId);
+ should.exist(w.txProposals.toObj());
+ should.exist(w.privateKey.toObj());
+ done();
+ });
+ });
+
+ it('should be able to import simple 1-of-1 encrypted legacy testnet wallet', function(done) {
+
+ Wallet.read('123', {
+ networkOpts: {},
+ blockchainOpts: {},
+ }, function(err, w) {
+ should.exist(w);
+ w.isReady().should.equal(true);
+ var wo = w.toObj();
+ wo.opts.id.should.equal('48ba2f1ffdfe9708');
+ wo.opts.spendUnconfirmed.should.equal(true);
+ wo.opts.requiredCopayers.should.equal(1);
+ wo.opts.totalCopayers.should.equal(1);
+ wo.opts.name.should.equal('pepe wallet');
+ wo.opts.version.should.equal('0.4.7');
+ wo.publicKeyRing.walletId.should.equal('48ba2f1ffdfe9708');
+ wo.publicKeyRing.networkName.should.equal('testnet');
+ wo.publicKeyRing.requiredCopayers.should.equal(1);
+ wo.publicKeyRing.totalCopayers.should.equal(1);
+ wo.publicKeyRing.indexes.length.should.equal(2);
+ JSON.stringify(wo.publicKeyRing.indexes[0]).should.equal('{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1}');
+ JSON.stringify(wo.publicKeyRing.indexes[1]).should.equal('{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}');
+ wo.publicKeyRing.copayersExtPubKeys.length.should.equal(1);
+ wo.publicKeyRing.copayersExtPubKeys[0].should.equal('tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6');
+ wo.privateKey.extendedPrivateKeyString.should.equal('tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm');
+ wo.privateKey.networkName.should.equal('testnet');
+ done();
+ });
+ });
+ });
+
+
+
+ var legacyO = '{"opts":{"id":"55d4bd062d32f90a","spendUnconfirmed":true,"requiredCopayers":2,"totalCopayers":2,"name":"xcvzxcv","version":"0.3.2"},"networkNonce":"53d25e8600000009","networkNonces":[],"publicKeyRing":{"walletId":"55d4bd062d32f90a","networkName":"testnet","requiredCopayers":2,"totalCopayers":2,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":4,"receiveIndex":2},{"copayerIndex":1,"changeIndex":5,"receiveIndex":2}],"copayersExtPubKeys":["tpubD94LTzAUiW99mpA59nyf6fAHh4xKGmnwbgCV4gU2bRpeN9CRiMSurqme22px5NmJAo6FdcdH883Zu98VbqyhesCJ86kUEjH3Zpufy5FfcaC","tpubDA2U9H6LkRHDRbRxHBp4VTbxPc7JqsvtcLxrE5QJF8z1iT6hMJ1pXSVf57GWRcxXutYvpoXRurDVGsscJauMtnJBkYAWBVExYmm91XQE2zz"],"nicknameFor":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":"asdf","02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":"qwerqw"},"publicKeysCache":{"m/0/0/0":["028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90","0332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec8"],"m/1/0/0":["0220ad514cf593d0c3905d3bb49bc5767a9410823bf9b77ea5ef2cf1d1016d77a8","02fd42cf66f1dbdc7bbb9ae09aecea72df479ffe5a0c4641301067e331d12e416d"],"m/1/0/1":["0315f7868eaf1f9b7127e3f7e0222c5e473eea003e34700f4758b6873c525d6723","02a2e8ed5e90dd39e3842fc790e06178997dbca319987f365317589e2a71a93658"],"m/0/1/0":["0244a25a0b97b26707fd855c15b046b901be85a3b70a781d0678608e633440eeca","0358cdcbc528ddfb7173b0dab283f702be82546ff031e4a832a7270080cb875959"],"m/0/1/1":["025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f","020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d"],"m/1/1/0":["02fd0e7c62b7b58d1ea7bb4cb84d53b019df99d3703a42aed73a2cfa15f3af5d08","0355a15912e76072ef50e6643376b8a9da8422ed4f8ea07b1d84d4989be5a39b2e"],"m/1/1/1":["03bc3e1f4db32efd8eb1fd44a1665938d59628429c67e1e8b7054ab5717f4e6750","03c4c817b633ac31f44f16f390af831d35f7d98744a52a0f23e9598967342255f8"],"m/1/1/2":["02826fe7e9da408480ddeb1d4414c5100b350f862ca718e27122681e1a0ca35077","02bd25af907bb3edbf6b2cd1ea90eaa92cc93ec47bea7d339af44c1d2c05708e99"],"m/0/1/2":["0337a1a70364b94745d6e26d2d28919cf528304f52765f12ef43e3d6da0a6c8dc0","039d83db9aa43e6e00e0304e6971b6079d79dc12d8d55ce2e6fc24a52ba8d41329"],"m/0/0/1":["0359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b8138","037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d93"],"m/1/1/3":["02600e5c41670773a213a4cb58c8f2fa3e83840784bc7f0b56925e1075e06632c2","036d01867af5f61371151ef7d9026fa0400a623f6924e404ee0b856625268972f9"],"m/0/1/3":["03e5a9b039b187ca8e065627df402e4a5b196b94198542da7036879de08be63d2d","0304f3e0b70f696d80e5785dc7747d6dcb55ba24c31f2d80bf184b4e582e6b47fc"],"m/1/1/4":["03741afa5bd50d6ba5801064c810fae84f6a4557d6a88ddc8591d0d4eb68a8fc41","0214dd6ce6073b05999fb887098ca6f7e1d0b4fdc0760557786907df353df90d1c"],"m/2147483647/1/0":["033e072a53ea835763a03c66e35c35384736210a1bb7d7ee6d9a3e109e82426b30","02e37b5570c053da8a8ee587be86fc629775c4db890aba2745ccc4e4dcc8c31041"],"m/2147483647/1/1":["0228a6de42ef421c263d1efd9f28d9a7d15a261995028a24eff6b9f1c3fc46e6bf","0226cff885cb0d607cc9cf69a7608316eb3fb2ec344c0c9956246ba776116fc396"],"m/2147483647/1/2":["034fe2a8f0b98445eb5810fe36572ad2f64ed9bf64dc9de624f99c0142cb07c682","02f2c5c758e32293f5c193fd69afadbba83abafb397db01e6f2b447690e900475a"],"m/2147483647/1/3":["02b25ef9434446c51f10678f787e4913de582e34d164bd3b06af7732c5476df1a8","025d51a1efd59bcff22ee2e0af61b21a7ba5f639e20dfdf25690e926005177dd0c"],"m/2147483647/1/4":["03e5734e1d29b2f684d0446b7a2ffbd0ba8952570a502d0d14b1efd8f24b61be53","0258fc28a324848d8d0154e8614815e35c668d274a8f01957bb99aab8dc8f386c0"],"m/2147483647/1/5":["021f9e775246765e1cfba0ae453b4eae6cd4ae5a57a09c319edbe89d4dbbf23be3","02857f66571a1c3eb9e72d22ae88e734c03d448bced4dcfd345c2059468124c741"],"m/2147483647/1/6":["02c072f329391a25255dc6452e5f5220966869dbf736ba8a8c3ae9d273a84bc3fd","030920a8b8e88c4db2871a7df0878a86cf0695f6d96bb50c701c3454f3df25176a"],"m/2147483647/1/7":["036bf329fc19bce10cf1999fae5bfa80290ff7b44776b49c7b0dc9eec6cffcfa21","03955a549875b4f7b9be28b9ff4bcd51ad2bc224430b1634baef890585885d5e1b"],"m/2147483647/1/8":["024879c9c9a261b3141ecfa1c79c4efc25278c844ecd1dcfcb95d9c19581fbdd25","03fb4a5fdb91239df3ccf7f61a5b99e7e72483101e21c9d1ee0d85544e9354c6c7"],"m/2147483647/1/9":["035928a107ec01f78cd586914d5a49710fd42e352b1312e3ad0eeb2c9666fdf8e7","03a54c03093797854829c75357f092356352a109042bbb83bdac20cb4e5eca27ea"],"m/2147483647/1/10":["021e7a3a7efe888c5e820b5cf0f03317b2b4bf438d8563449aeb7a77cade97f136","03ec0960b3d1df52ca3cc2c82b7d97063400da4dd051bba2f9bab6cb44aee01efa"],"m/2147483647/1/11":["035d70c26b7f429861f555f7c0d99947411b23b7f95303fb8d5de5b82a95aa30fc","038b922f7024f5446d6b48e5253643543b35c006d90fd37688105c6cefcd8adb8a"],"m/2147483647/1/12":["02158d6503891c6c65a606221dbf5c68d0832288975914007968419939588ecb24","0248264cb1763a3f4de9b34787b4bc5443ec92ef915927494bb9f1c1c0b498c7ca"],"m/2147483647/1/13":["0349965eea38a25ae0c061faeac4c4e57e648bc4c0f059d07b3b8b7962cbc0dde5","0352243d9269565ce2a1ffdd0b8e43a442c6dd1c9edda86eaaf2cba5a4a95c40f1"],"m/2147483647/1/14":["030fa6e3d0c5cedc0581955395c77cbe134c912a47971023b9695332df3f7bb200","03f2cf09e33326fb59bf3f13e6298d2d5d29c9eae3b872e5a851e8d8d77259c883"],"m/2147483647/1/15":["02bf0d45e41339f552df6f8baf4392142921fd38b0f2a4388a905ff6cbacbc278a","03fabe46bb6706a1b8edfd28c046a8891b4530bbe5305080b72b0d08ebdf7b8c0a"],"m/2147483647/1/16":["03a4e3146ed34d6a8af4e4379e6edcff32cb0373ba232b3d746af3052f674133ac","030311b73c6f5c46ddffc0cfce6e5ed0b671d94267d8e52cd8837f2a479916eb91"],"m/2147483647/1/17":["03233df93c762d2f06c7f5f388e4e0a8dbdb13302acba0d2d6995c487d8aec9f2f","024badfdcb7e772ac7fc1c46d3943b07500edbbece105cdeff3eb9e9fcc9f54782"],"m/2147483647/1/18":["0364035475a098e00eb010c500cad3c90af3e81a4bd613144bc9433a150f14718b","028223dc8142154e7477ce000b3dc13e1d15a901553d9b18864c8645b582b38fe6"],"m/2147483647/1/19":["03971b74b4ac4bdaadf636baa4caa82fe5355471ed6ea05a9cbe5fc6c9e4b9db76","0202ebffacd01f83849e5bc5c0e2c317bc5fb2fbcb2d6d4482a5235f9f1308b61a"],"m/0/1/4":["03005ee9ff028c98fd132e531023f2f2b61ff0d26022f979dd98088d2ba167b031","0345ea82e8dfe38277f0c3aee18d2dd93edb63e8663ac83328a7934d2ca57006f6"],"m/0/1/5":["0391bc4990b71d8a3f156ae7107929ed6372b0b4ba8a868253f71ba7189d1efa02","0312a74cf2e7c0dd41897d04fabfd8cc3187b84a28305cfc79315b24e6fe23a6b4"],"m/0/1/6":["021a38c492607ff9684a4fec445e47b5b7100d3ef9e9dc0d0b37c0a646d28d4f77","03ae0b46ab36f97447ebaa53f2b5c8f090f15395378785f2fd285eeba17fbf3f65"],"m/0/1/7":["0308cdec88c1ffe16edc98853d9c08dbd4ba2541ba566668ca17bda19d7eb3481f","02dd622267c2e68287287b8b61724f76fbe84096a56aa5054af92f8fe25380e2d1"],"m/0/1/8":["039647da9ad725836bcb28a3e0497659a28d7749d1416c421a0a01c62d237ee962","022e22aa61eafda0dd8820427f1a06314d352a15ea8645e7ab9b80920017084d82"],"m/0/1/9":["03a4ade946076c6962b70c70ac7fad3a87efb59a1d0a4e32bda13a6d47fe9df961","029a07235aba04ab69526e117d836d5b3fae5cfc8c5e72b10c6d1afd261ccc19f3"],"m/0/1/10":["03c78e9b6493b22790db1acea20df9444e0f9c424fc5756e7a32c290ae01783953","0254c130ee467a96570c9f5ebea89de04f0b1db1686b164f2694339bef8f25dd88"],"m/0/1/11":["03a762c43318ef8d4840fab04c8db73797dc648825fac60f2730b4c76678df1cf3","0212c684a4de8e750ad2dfe2b136370ab9803eca178ed9a27b3990c29b067de35c"],"m/0/1/12":["02702d221f9b15c5cf75ac2f497a6c63e60213087c3d2d3be46768e3ebd238e26e","03ed58580744deb357258e44548212038670769d8d51e385d4fb8414311fd01b52"],"m/0/1/13":["0320e0597b54c62768352f433389cee4725d6094d7bcb5c72265edcc0933829aff","02c5706f11b9a85f3176c572842b7c9812c2195058d24d945bc026b00312740e76"],"m/0/1/14":["02fe43077676b844226d3aaa62e8a86d237710d92f882366944acbde0c8992fcaf","039a6a8662abb8910741cf331320549665e9feb28ca94d1ab6a43c84fa330b94ee"],"m/0/1/15":["0369f99f72847af93d50ab8ee75b6e7e912d26e27be96f6d6b7215cf7daeff7ba5","02521700cc07c953ba5aa586fb0e4795a34dffc68c5fb43e038be3866e40f4daed"],"m/0/1/16":["02f67d1d89bd8fe2f91c5b973cbdacfb4ba440e7656bce284cf73d549625607347","035da9cfac5a803dcb2b283b02a2515a4a1bcbf3d19e0d180aee8fc30193bc0555"],"m/0/1/17":["02c024ec199d240e8d6c66276b94b91071f7cdf2bef540c29d6d18d25de7b1cf7c","02190865f9dafae3f7f05c093463be5632946422ddda0a6fef6904390792516067"],"m/0/1/18":["035ed504d7704ad984a333b8eb0fceb8be043da9284de31ed84d9e68d90c75507d","033303c415b50421732402df00f4baa219f334647a7eb5014b9f8079864d6ab558"],"m/0/1/19":["02ce49fe86b0eee73663b1ee867b16b97c876af26f12764c528a2e6d0eb55ad3d7","03ab969bc81796b88e44c340d854df955fc60ea17ea92db5d3115595d6dec890d8"],"m/0/1/20":["03e2fa915378cbdffa0d919b0fb50c7256ca731b9d571b3365e486893a1d43079c","038d058b895cf084dccfcc9367e4796a5cf4ddceed6c35f6885d75c80119613350"],"m/0/1/21":["02fcb1bf644446b5b42205272af72f0aeab9e92ca29aafa91c5fb69142764017aa","035c5fe5c8811603279a5b72b6c30735d702817db1eab937c622269e28192ffa90"],"m/0/1/22":["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5","022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"],"m/0/1/23":["03f40b82fe8cacff08879f13c45f443a3dc3ea98e1d75d5f32a19f5e5a8f7a905b","028415ee458e4dcfd440ce969726f3b58ae74fb6cf3995ced099579211e7419844"],"m/1/1/5":["032748a6282e21f571b8c8dd49e775deb83c90fcf88dc4ba81d878536973709c3f","020837cd68f14ce571b335eecd1b6fa0af43e1576dd9721aaca2a8ab639ac6b7cd"],"m/1/1/6":["0337032efb013dc92bb8dccfbdda9f5c28f0039a9c60953d41003d095e9f9778af","03ceed2da6b9603297061dc8eb930112ba726b2ccf5eec67f4866a05ca4049a22b"],"m/1/1/7":["0383c96ac2af7d203f69133b2fab6b68366b5075ad6957fa06759df3b20fbfec70","0311385f79834cedaf2230a48c0f9dc8e794da1869fc595db2518d62debb85579a"],"m/1/1/8":["03efc649680280f4e4df96da923bc88330275004125ebe5483c2f3e05ca52e19a4","02803c02d197d780388259afbd001ae41fa3eb3e2bac9627aff540521c184c3b23"],"m/1/1/9":["03af2fe6aa027a76b42c1c4050a040bfd026ad2daec1bb96a5fe2d026a7df919de","02ce14163047c640228796fb1f72bbe3afb05819ad141598a4f021058a6f79dd3b"],"m/1/1/10":["033770378bd762cf0408e44e4e604bef77e336170428c506949b1a4f1f2963e574","02c58ed43946f699dbd3e36d3e9aab2714cadeb19ecd3a56e4328c50336b4a76cb"],"m/1/1/11":["02898a1545fa19bdca92adc498698d27b86529cd4c08946d9d29604734b86f31af","02b402767a045ede072600924401c0d720000b2ed59fa444bfdbef4a5f1cead745"],"m/1/1/12":["039b8659430be49913e2cd869aa8c99ccf49a13df35837370b792033dadb891483","03264e63df292257cc76babb15d15bef620d1c2f8c3bbc78d6ea02d127e5ee7386"],"m/1/1/13":["02381a559791b8e86bf546e2c718ae63cf24eed0518a58e4d4a4b310adf2cd38fa","02d7f8283a4418d912508901b4a3db0d2103206dfdd74b3c75648671e20ecfd445"],"m/1/1/14":["020376e8c550b7d9faa0b2da947a2a36fab22c6e8190b6f99460b6022017bb97d7","03fbc5299190e6628de28c92aaa12e3a131b21eb7266462c46fbedeb86fa878055"],"m/1/1/15":["027209fd3b0cf7368180a5dbb16b928c997d33fccb78505d48440c7d23eadf5460","03450bfb22858726cd7e228e6733f69457546978a95188565c53e0d1c0d6070ea8"],"m/1/1/16":["03cb355ba04f64293793855121bab5831f84a3a3edf7cd31fccaa6d67c407a4912","028bc897a39c1224610b765a80f4cd8ab79cb37776f58fec9c10ac6f649d1f3c72"],"m/1/1/17":["03f4cb0564d7e2c6b85673503b7954db22779f29a8f3374904573984e318a96bf1","037c11b6ee906d84aa7eed359d758d986d912b6f8e5cbb1acf0982a77b3ef812c4"],"m/1/1/18":["02d2e5798f33f6889472857744316f2d253f25f88379610063f40cfe5798d9858f","0253cefdfe9ca987cbf1c950b6246d5b7a194d8dfad47c3a78dbbc5c1d01511d97"],"m/1/1/19":["0336c325f5aed366ffc10d553f2bfd4d69e66cbe1688d77af14efc8827aea2e318","0378b1b9a6074f9f2ab4fa9ad1e14649c621b0c8124a1b148914d3c10e6ab390c6"],"m/1/1/20":["03ea55740a734689ce778a8c00df8ebf4274c8f66de7d05646fe5c927773ff7f2e","02275b558d49aef955b6dee51a3c0a53f4b076b97bb3f26abcc82540168ec87cac"],"m/1/1/21":["03c77869c9984664eac9c238f4b6d806c9f48ca8a736c48450f398834db2aa915c","02d984f548c7f60c09dad3287cfc48807bc8157123989636c713be61be6a2e9ced"],"m/1/1/22":["03ed7c6a3c854c1f9459891691cc32671402f9e47126919878251e568dbdf353f8","02a113dab22cd9e46967b3fd76b9b9ec1d227d88817a9300e42d332cca2a0877fd"],"m/1/1/23":["02ee186432dcf69fda50a6fdbd94651817d8a271c273a5b70cab3ec4ae77a3753b","02291370aad9de0dac676355ced64e268b0c431a51f42f12d13f5144940fce4285"],"m/1/1/24":["02bf71435e84e66547c8c583d5ba226a5ac4d935e0a9f9603ecd8925c3e847e91a","03578d8657d285a89d9d597632db662cfef9baccfb55c76b1e87948a94fc9de30d"],"m/2147483647/0/0":["02a8425bbe23426219065969f695a6c3e242b24e57226bffdd542be8fd6be968c9","03057a42fdb6569fb1615b173ccb702453db2eac5be4291b82d4511461eafbed87"],"m/2147483647/0/1":["0250c3d3e86e332010c5233c2ec3bc728026002f0037cb3382d6318409b0e70796","02cfac1e7c4c88191201080f8316af52d9faa6ba624a6e160279e9fac4d1cf79a9"],"m/2147483647/0/2":["02a8c266a5b92eb50c8be91f95e4d1ad968b2f57d527377fd642d63fb84474f61a","028cc954ab31bd179ff80b8a05f95430ae534e61b3ff35f5284fa2fbe1832ceccb"],"m/2147483647/0/3":["02f719e1a7ab00ea98611453fb03d44c1da04655bed74af392534d70099039b4c2","03bfa548bfd4718c50bfce173f780eadcfb679d9c0206c91a2fa1879a9cf7558b2"],"m/2147483647/0/4":["0362c0695d397ca26bf47f0e641bb3cfb06ff29ccac2e1d56ded3afcf88b1e688d","02f9d87b05bdb3b9e82f506b43f813041c0e403274adc23d11e5e1651e34b606c2"],"m/2147483647/0/5":["033731323032d4ee08e858fc71f93970444333e183a1d5052e1d08cfb511e262c8","023e12556cef67ade35b7758916b5e1a3ebe074ccd35c5d8eff6b01321f63eb495"],"m/2147483647/0/6":["025d11b90081972bc1c258c9d6f476dfc2f95b69f0e9935322bf9c21deb580ff64","02b065f56a378907354f0738a0ed74f10660c6b5dd68c9f992093b75ce3d7d8b72"],"m/2147483647/0/7":["0210e721e8a35db9d8c855a0d346f60c09208f3be80b39e03af2c29db777332c71","0277f352969fadb1f1835f9a0fa99c6a3c7b6c281be5b2794c88a708eb177ea33d"],"m/2147483647/0/8":["02998d8d41e4215cd2a961a415a3ed0b1f984f1627719a7b102a75864943c4d87b","03d8ed7fc8f68a77f68d3afd007b7aa4c89944195143630ce183f0fa5438f2b559"],"m/2147483647/0/9":["0324fa91737588e4f85937303ce65c3b91b5f2ae506a72d92b83e3f5f9aeeb3c6f","02a011be72c4a400319212228106af278823a97acfe0a67e1ecd866d446b315114"],"m/2147483647/0/10":["025886ba287922a904881c7315e6fcc410a7976741771a5937d3a1a01b529f21fd","0243bb91ceed9d29d0c2ca66a8ab77e82110bbcc023beb4106f787964f44a0b972"],"m/2147483647/0/11":["0369d21684894cc2d4b2f5e581ede3cac9e8db4161a08e7737c1be129bb673d3d5","03c9ef27e3cd3dadc078fdfd9936a7ad9bf7954747085cf8f8a2a5bb3431f68a9f"],"m/2147483647/0/12":["03a73b8fd859bf6acebffdfffa2597199091daedd2c011ac67fc3494d8a1a8ceb6","025a213f7771c8be03f43f2e7f469ad4ef2cf6907ea284b227a786d1f55dfa7144"],"m/2147483647/0/13":["03a09f7ca257e1ab263cd5e6b0addc3ff868b93df132321d98775ca3505efb576f","03454c715739164bc55f347a651439cdf3ec146b35d2927beb60e8290b3916e082"],"m/2147483647/0/14":["03a64b1f7bd94a6b1a6e84ea444e0ba04e9deb86460934ccc37c0615a134a8257b","02794f09210b1811a455f3e1c7bcd35c76dff2523190fef9615eb27e2376acac1c"],"m/2147483647/0/15":["0392dca2fd9a3bc2b2a7d90a848719069fbc5f22bff7327bb8186c032514085263","032ee8a33ea76d70c7ae839448ca6c5b1af89146f2922e23ba1822df42dbc7e66a"],"m/2147483647/0/16":["031a22a1a3c1abad7c4d782ef6ba3cc00f2e8fe549eb33e0732200aff6d3174831","03bdce9781289e0c31cf727f4c93fe46f7930dd8fd68f818ce241f1ede268e8e0e"],"m/2147483647/0/17":["03b12d27e9aea2c2ad598e54e40860a705ac2ca2427aa511b501b38ec368ea5c7d","03e60d35d84d4536cad895215256b312bb4879a8d417251c279995e58f25da3d54"],"m/2147483647/0/18":["0380266cc9a9673676ad6a1b2e7148766df9c25b4dce299e5edc4f65b72aa58e64","0329e2a8a48c06c0c45dfdd2ab33e6455551557d8ebaf8c12fdf7470f8c45f1d28"],"m/2147483647/0/19":["036fe62af85560d7eea7c7af55e60b32a97dca80134d0aedffb19eb2705b9d6e01","02381c2c30b9f81e2a53c69028fbe11803acad0420b267719b7a80870be0baaeb7"],"m/0/0/2":["027bf94b8fc4e9b42683af25fda125ccab8760040717d100270dd4afd032692daf","026382c6c9357250d96dc21e43c053857a64efeac1887fdcbc107fbe3ecfc6115a"],"m/0/0/3":["03fd203acbd9af3cbbfb709458f8952078234a36094f12d00372e4b2b14cfdf419","03f2e5db59aea5dc89f53ac2a9f4ef66d41265c45afc5d763e0ca61ab70c7c61ec"],"m/0/0/4":["02a1d7cf4fcdbbf4de4002b844c3bff1639073f1cd6e5c4a4e02596b45d3f518c2","03b5fba813294e6ae096ea158833453caa5a945609b0a554696091b9b152bb0f7d"],"m/0/0/5":["0261d37e3b56ef4e106c59753037f516a4b1c45e056b2a3e00f8b77f15aaa7f8a5","0256a55e66e0de1603f0d600c0eb5f5486cf3512a776a36f3ab0d1941fc0dc9b09"],"m/0/0/6":["031db2826af215fe6cbe3f6e121b0497840fc49be133cff0a4d4eab679d6b99d70","021dd722c3f35dd04fcdb57f09b76c723d521fb36751de03ffd08096ddf1dc1f86"],"m/0/0/7":["0354ea75bdd9eb5beae7262e4a5eeb58bd10103ee0185e85b749ea39f6615d0f62","03f2c8f3b6478c0501a8578d5caf5ac2974f8213fc5e699d62dd2af58fbe8781d4"],"m/0/0/8":["0282e67df3bcd1e1662469b4c3151fb50ee1e46b75d787d91184c16b9803131f82","02921a7054af1e425f4137a5eb6b34d1f2b9d81c2625230194bc30657bb4277e11"],"m/0/0/9":["033e7e387933983ceab37c8388bd8ebc5119760f493ffe6f083bef0e5dfe22891d","02d660d60cc55d80912e0745cb142a8596a4604fbf72f9aadec0599aa2ed62461a"],"m/0/0/10":["022ce5b2750ae34512199856eab9e912dc25281cd8b88e7688a46c3b9a389701cb","02f14aa1608fce3b6088148709eb5fe72b61699c931fa8d95a45fab1106859d1b0"],"m/0/0/11":["0288dbef3302c1bc5556028adb33e2f9e03c119dbad4f706befb8ce86cea459f2b","03f13ced465e2e0a3aaa8895f3185d5711e0bebdaf507610b7a669ac8fc82da8fe"],"m/0/0/12":["031ab4677885340d2f927ccc9747f4346b79e4eb6c750695095a8a2524610fa94a","038c881910fbd8b50d193db4e0c84f5b7840820397f92cf0718a8e06d027125503"],"m/0/0/13":["031b568452cba22eb7a88c6085489e53e35abd16068882e71a140e47e12dee9c61","020d09885ee362101d12d34ce0918d41593634db1b9413e5415c6755753b9330e8"],"m/0/0/14":["024177bc9aa03cfc72eda2dfddffd7fe9d0c2f007fc3ba1a48280feae2b9fb117a","03394ad321668440c08da76eb35475ba3a8c0e8cbe0ed81468673a8c72d38fe457"],"m/0/0/15":["02037b1cc696ffbe9eba3684edd53653386ef6cd7728401c40120037593a4c2ae2","020ab8d6900ec9c11ca5d96dfc0ce7cf0ee71653a7c45118e89abb4b113147e53a"],"m/0/0/16":["023bcbb8d4726a546087cdb83740adf0ace879b7195a572c652fa8ce4dbe195a04","0392721b230d5163d28b27fc7e059b875711f12b3da448eabe7229bde57530e637"],"m/0/0/17":["02498ee74e849d3e9261dd1863038caf83d6a3bc2eeebecf17055d4bab44dee77f","03d4dc104b2e0981693e8097437de9b05334a85e2c8edb02783897859bdbc93e32"],"m/0/0/18":["0218a9f524fe54abf8c3afd21314296cfd93eaa9227acbd457e6c9a742dc233cf4","03760f3d0c5db969bda698ff9352e3b7c332216c34825f4c6e857e39c9aee7cd35"],"m/0/0/19":["033dd51f7737f0e9db79f5c38e4298bf3396346904ef3933d290a22e5b77048d9e","0221b2eedccb9a37515263071550069b3b349a166f0f131d0028e8600d9a2251b9"],"m/0/0/20":["02cb6c39161f3244d7769f7ab96346cae2cf21cb6f4538f5e7382d363dc2f836c7","034f7bda4d1e9ed6a3774608a4d6cd8582ab59fe3187f8a7a7cf914d89426ebe28"],"m/0/0/21":["035490549d65f1360f10340037250b171470ff4c86966318a2b1eead6d8b969aea","03f6a04f6fcd07a4f32c82d53710ed30e0f54d43d41c67c661d158b3d0830c3ea2"],"m/1/0/2":["02972eae7e4302e319c266578e14a07839c1e788296a92906e6d66d938211dad5f","039ed6b488f1571ad6527acd6b6c5b8453eacf6665dc5cb7852e33d1c8ea73f9fe"],"m/1/0/3":["02bec4728888c2c045108353994bae5731ec7a7b41459023b0023e10b8d616bd30","03ce1efe16214c9eac595382e46a68143dd11a335b3f7c971ddd719ac544a5fc4b"],"m/1/0/4":["030e2df1d341568225d8dfbe5d07e98dae9f90e0f43e19dcc68c998a6ed7bcc1f0","0380f4c07dc84faf42d51779f104aa6e3b5c3ce2d7684b3cb76d49faeefc2b69d6"],"m/1/0/5":["029a54ddaa25f433b493f4b72df8c1d41be2c4d2963b8b61ee63cc86d16c12d066","021567c95e0317442e7367aa4e3378dd46c5bcef5860f789272fea83b917de0669"],"m/1/0/6":["03590320d80b61cc0874b579f467c9b5ccc50d9ef875bcf6bdd12e2d0c211e8973","03ee4677b6ee89a9d355851f2230506c6897ff219062c0df4ad9a85c60f3535f93"],"m/1/0/7":["03caf98ab1c9b79d1dc8029453a6137c08787b04043b79af3cb42d41d2d3f1338f","023f39ae4e2f4f3887d5fc58e0d3a0d7ee267dc04aa257c75b6b2d67d2f5580f81"],"m/1/0/8":["0352a2a3ea8209c9a2b633d788796ac2d16c08022440e04a77ab2835c7f971d266","0291bc248b3da997f35e8fae98a75a91fdac2819d74c4e270899338d48f7389e87"],"m/1/0/9":["02468d32d9c3c62418d506d4cd0da6cd2022d5bcafdb5f847cf7bde7a48ec6848b","032713d90d12eb6a072f3c1db6c0d3b680d3f78883016135fc0f78e8193d41d4b4"],"m/1/0/10":["034863cc6bab9b059be53413ba75c5fc286647c20d7f9e5512ef4754ea301dd1ce","03a33ab9c32a2264ee2464ebbb5892f0e34acf0fdede4f87395a89e9dacdd4930e"],"m/1/0/11":["031e19296695bfe8a96ba3bf58afa805ee1bd5471fddb3929b1678d69d442d69c9","0270feb33956fd9e937019d629523e26437493c0856514011e6aec88baf7721295"],"m/1/0/12":["03cce695d3c3843bf73e851b2446a77d7e235e5b80b4f4474f9946292eb8218742","039ea96c8822f0ec7ed28308d277f3e730480d7573579cd11b89aef4364cd9ffeb"],"m/1/0/13":["02ab4ac38eb405e822d12c0f0f354f04f9ee1d991dde887a5c1171096fe503158f","036809e60cae1203da8884ea1f85d4669ce6e053f8ba605d775e271b70ab4f6787"],"m/1/0/14":["039d61da23a8610fa0ee58eb37d7cea7ea9396c79153da97280ccf5e46718e3bac","03015c27bcc778682781fd6ad30aa6041db0b7e24270818cdceece0043ccc34b26"],"m/1/0/15":["03c088ed669132835d2728b0ecf294271c8388988c6ae264d43ca24f50e4005f81","03e2c118c9445a2ddc4c8afeb0ba49e21be3f818a483d346418b8922b8a371a2b7"],"m/1/0/16":["02bba7df9847f463c6b23eca37a4bd6efa3801a52b8ddfad804d902e783b70c81c","03764b657f23996e31c64a701facc1cbeb0c9edfdd605e2c1ed36cf48197565d45"],"m/1/0/17":["020445179c522295b89bf4bfd582eb03422e3fa20dcd29263925e9f44282d476d8","036e47bdd32f3061aed1c1f8c2a32b038c7b72391cb1f80ebfc150e58f88372766"],"m/1/0/18":["024d88c4bfcbba713d49e1edcd035234aaa1ee76ad7bcf75bf074a16658a6b0b6d","02b861e7a20d89f6875d2e44c78dbadb99503e282e5e60e9f65657af6fea81d425"],"m/1/0/19":["023a8ca9d5300181f157e1930d3b0800eebe7683d8df72e6cbf28834dbf1be5d60","026053c4f84c10d15890c0b254522972931bc2d5b7cdf9c1f9f3137c22edf3ecd3"],"m/1/0/20":["03137c66e9f3d61aba659f408d77a293fa0f3fea4ccb911074a681d6f61a55d023","0291aa1bbfbef59b16b0e37e185a706c589d448cb02e860c5df9c9d7242ecc739f"],"m/1/0/21":["03c08673e0cae55318bc9dcc4b5f11eb3ff71d42de04015e255dde3fd8cba7e09e","02423d4eab06cd5b26e71d145283523c011d58032700c517f00b328d2c90cf109f"]}},"txProposals":{"txps":[{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543144016,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543144016,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543144645},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543144016},"rejectedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543170040},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/0"],"comment":"blablabla","builderObj":{"valueInSat":"29000000","valueOutSat":"8900000","feeSat":"10000","remainderSat":"20090000","hashToScriptMap":{"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj":"5221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852ae"},"selectedUtxos":[{"address":"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj","txid":"a9f4dda3f092e37244bc4e77ea921fed01d5b8ea49613dfdc0dc8afdd70190b5","vout":1,"ts":1405543855,"scriptPubKey":"a914cc93216398b77b5f8c451ca3a357bef961678be987","amount":0.29,"confirmations":0,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001b59001d7fd8adcc0fd3d6149eab8d501ed1f92ea774ebc4472e392f0a3ddf4a9010000009300493046022100ccbb8f398f74a76236629b8499ffc6f9518a2091f5a61a9a352c0a10f615961e022100b8f0769c76cf33bec3d7f81d9da2b74cf6e8a5e0a24ee5f48172854d8bcdbfa101475221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852aeffffffff02a0cd8700000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac908c32010000000017a914560c292066792531164149c5ed63ad2793a61b928700000000"}},{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543188745,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543188745,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543189341},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543188745,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543206819},"rejectedBy":{},"sentTs":1405543207304,"sentTxid":"169bc92693dd2e27724eeba81e54210e842035bd3af6c52e6a6a5e908f1a4f66","inputChainPaths":["m/45\'/0/0/0"],"comment":"que parece","builderObj":{"valueInSat":"29000000","valueOutSat":"9000000","feeSat":"10000","remainderSat":"19990000","hashToScriptMap":{"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj":"5221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852ae"},"selectedUtxos":[{"address":"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj","txid":"a9f4dda3f092e37244bc4e77ea921fed01d5b8ea49613dfdc0dc8afdd70190b5","vout":1,"ts":1405543855,"scriptPubKey":"a914cc93216398b77b5f8c451ca3a357bef961678be987","amount":0.29,"confirmations":1,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001b59001d7fd8adcc0fd3d6149eab8d501ed1f92ea774ebc4472e392f0a3ddf4a901000000da00483045022035423cc74824ba904907678dda3b62a20a787b96d1b3e9f3e9546f9c57f4e45902210080a1ff1c39f458ac1642b9e948bd62fd70563b5252e749cc8fc642cd763ee830014730440220524a13f36cfb03caa246d7d84de634ec9386f2c39c19bfa926037f48da86262b022050e58a6503d105ad2805f86806810a1aa7f20d6271e1340b42fa91ab6a30f3e801475221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852aeffffffff0240548900000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf00531010000000017a9146130a9d51f996b7a1b9d3e10c80930834251909d8700000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543505848,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543505848,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543590221},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543505848,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543590221},"rejectedBy":{},"sentTs":1405543610315,"sentTxid":"6fe851b54b777a75fe80fa204dc674395e2af69efb1f7c0017e909eb82c3d914","inputChainPaths":["m/45\'/0/1/1"],"comment":"mandaaaaaaa","builderObj":{"valueInSat":"19990000","valueOutSat":"19980000","feeSat":"10000","remainderSat":"0","hashToScriptMap":{"2N277q5r8Ab6XLJNCjXXFdh5itDJRQCv9ts":"5221020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d21025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f52ae"},"selectedUtxos":[{"address":"2N277q5r8Ab6XLJNCjXXFdh5itDJRQCv9ts","txid":"169bc92693dd2e27724eeba81e54210e842035bd3af6c52e6a6a5e908f1a4f66","vout":1,"ts":1405543157,"scriptPubKey":"a9146130a9d51f996b7a1b9d3e10c80930834251909d87","amount":0.1999,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001664f1a8f905e6a6a2ec5f63abd3520840e21541ea8eb4e72272edd9326c99b1601000000db0048304502206b18b3dba2646c552469d8ef52d7656f6a65f563032530f622abdfd8bd4c5cee022100e804b406eddebbc827646141e74dc64c76a770ed4e35183ffd35d265ad9f7d3b01483045022100f6c013638ff0a316b1baa93dfffba6a98cf3033c133e8bd899e933c9c3e47ce10220530f40e7ea52ae58bec695edbec6d566d2ee8e7b5f33f95e33093ad1e29a125401475221020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d21025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f52aeffffffff01e0de3001000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac00000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543781381,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543781381,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543782017},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543781381},"rejectedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543794590},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/1"],"comment":"1","builderObj":{"valueInSat":"29000000","valueOutSat":"1000000","feeSat":"10000","remainderSat":"27990000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c000000009200483045022064d877bc5171fbaef909c2a1a924e0023b3ccc0b530cb46653f06ecb230283e8022100bc6658d60ad4f7120d9226c8f6eada87f3b0388f73c458011988bab36e78ba15014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff0240420f00000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf017ab010000000017a91421c4a435d9ac263ec55b35a1a5ca95e979639b9b8700000000"}},{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543835343,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543835343,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543835968},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543835343},"rejectedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543850998},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/1"],"comment":"2","builderObj":{"valueInSat":"29000000","valueOutSat":"1000000","feeSat":"10000","remainderSat":"27990000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c0000000092004830450220302baae7de2e0f102bf3af2d5f450f673e51bd143020141a769ccdcdf16af188022100e7abc087c76050ed649e7139a5a136969e74e24a8d8f6223d3219ad033a26451014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff0240420f00000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf017ab010000000017a9148b102abba0729fb0690c61cf7187064d692d43d78700000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543869803,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543869803,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543870411},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543869803,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543890406},"rejectedBy":{},"sentTs":1405543890913,"sentTxid":"6a0f61574ad65e537e7e99298968db565f97b894b61f4c8f8fac8fcaedb83e2b","inputChainPaths":["m/45\'/0/0/1"],"comment":"3","builderObj":{"valueInSat":"29000000","valueOutSat":"1100000","feeSat":"10000","remainderSat":"27890000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c00000000db00483045022100a8ce7907f9fd7dd41dd65c2dec425e008efea06ee7c80787c10c0e210fbf181302207712c0fdd1cb25836ac1fc2fd303c1e26b85e8980417719b9ed50e977a9693ec01483045022100d1780c4f028cd898920aca3eaceba352ed9306cd17f019ae2f634e8facad149a02203c84ab2093da8e22577e93f27a732f0728d4e6db0c749f3cd3d898d6a025152a014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff02e0c81000000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac5091a9010000000017a914cc1cab78458b1a951b91c6dcd7eeeeb682f506388700000000"}}],"walletId":"55d4bd062d32f90a","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPdWUAmaaopPftevC72Jtiu19V8ee5XijL9JvogqfR95uVrL85f8yBdQMq3KyQtG3Q91yWQb3XDbWWpcdWFDAmJ7Xy2XWkGJu","networkName":"testnet","privateKeyCache":{"m/45\'/0/0/0":"b6fd8d1a079efd523da34f31ba81f544fc3d0a728a8a98299d8980682518e79c","m/45\'/0/1/1":"0f4d52d2a99e4c8c1c2edf09fef12407c3abd2304b961198c3f131a8c8443a13","m/45\'/0/0/1":"de5c191c343bd6017b98708c03344849624a14e2c167cfd6eb8dcb075d139293"}},"addressBook":{"msj42CCGruhRsFrGATiUuh25dtxYtnpbTx":{"hidden":false,"createdTs":1405543109222,"copayerId":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","label":"faucet","signature":"3045022067576e5b37f2707a8dc66e57511ad9b10a3125bd95193fff6f8f6402969c3bf3022100adff9f417db07d88face13b3d13f422740d4421440cade1a205684dfdc5d733a"}}}';
+
+
+
+ var legacy1 = '{"opts":{"id":"48ba2f1ffdfe9708","spendUnconfirmed":true,"requiredCopayers":1,"totalCopayers":1,"name":"pepe wallet","version":"0.4.7"},"networkNonce":"5405f06b00000001","networkNonces":[],"publicKeyRing":{"walletId":"48ba2f1ffdfe9708","networkName":"testnet","requiredCopayers":1,"totalCopayers":1,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1},{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}],"copayersExtPubKeys":["tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"48ba2f1ffdfe9708","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm","networkName":"testnet"},"addressBook":{}}';
+
+
+ // DATA
+ var o = '{"opts":{"id":"dbfe10c3fae71cea", "spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{},"settings":{"unitName":"BTC","unitToSatoshi":100000000,"unitDecimals":8,"alternativeName":"Argentine Peso","alternativeIsoCode":"ARS"}}';
+
});
diff --git a/test/WalletFactory.js b/test/WalletFactory.js
deleted file mode 100644
index 0bd5a71c5..000000000
--- a/test/WalletFactory.js
+++ /dev/null
@@ -1,603 +0,0 @@
-'use strict';
-
-
-var FakeNetwork = requireMock('FakeNetwork');
-var FakeBlockchain = requireMock('FakeBlockchain');
-var FakeStorage = function FakeStorage() {};
-var WalletFactory = copay.WalletFactory;
-var Passphrase = copay.Passphrase;
-var mockLocalStorage = requireMock('FakeLocalStorage');
-var mockSessionStorage = requireMock('FakeLocalStorage');
-
-
-var PERSISTED_PROPERTIES = copay.Wallet.PERSISTED_PROPERTIES;
-
-function assertObjectEqual(a, b) {
- PERSISTED_PROPERTIES.forEach(function(k) {
- if (a[k] && b[k]) {
- _.omit(a[k], 'name').should.be.deep.equal(b[k], k + ' differs');
- }
- })
-}
-
-
-describe('WalletFactory model', function() {
-
- var wf;
-
- beforeEach(function() {
- wf = new WalletFactory(config, '0.0.1');
-
- wf.storage.setPassphrase = sinon.spy();
- wf.storage.getSessionId = sinon.spy();
- wf.storage.setFromObj = sinon.spy();
- wf.storage.setLastOpened = sinon.stub().yields(null);
-
-
- var w = sinon.stub();
- w.store = sinon.stub().yields(null);
-
- wf._getWallet = sinon.stub().returns(w);
- });
-
-
- afterEach(function() {
- wf = undefined;
- });
-
-
-
- var config = {
- Network: FakeNetwork,
- Blockchain: FakeBlockchain,
- Storage: FakeStorage,
- wallet: {
- requiredCopayers: 3,
- totalCopayers: 5,
- spendUnconfirmed: 1,
- reconnectDelay: 100,
-
- },
- blockchain: {
- host: 'test.insight.is',
- port: 80,
- schema: 'https'
- },
- networkName: 'testnet',
- passphrase: {
- iterations: 100,
- storageSalt: 'mjuBtGybi/4=',
- },
-
- // network layer config
- network: {
- testnet: {
- url: 'https://test-insight.bitpay.com:443'
- },
- livenet: {
- url: 'https://insight.bitpay.com:443'
- },
- },
-
- };
-
- describe('#constructor', function() {
- it('should create the factory', function() {
- var wf = new WalletFactory(config, '0.0.1');
- should.exist(wf);
- wf.walletDefaults.should.deep.equal(config.wallet);
- wf.version.should.equal('0.0.1');
- });
- });
-
- // TODO this is a WALLET TEST! not Wallet Factory. Move it.
- describe('#fromObj / #toObj', function() {
- it('round trip', function() {
- var wf = new WalletFactory(config, '0.0.5');
- var original = JSON.parse(o);
- var o2 = wf.fromObj(original).toObj();
- assertObjectEqual(o2, original);
- });
-
- it('round trip, using old copayerIndex', function() {
- 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());
- assertObjectEqual(w.toObj(), JSON.parse(o));
- });
-
- it('#fromObj, skipping fields', function() {
- var wf = new WalletFactory(config, '0.0.5');
- var w = wf.fromObj(JSON.parse(o), ['publicKeyRing']);
-
- should.exist(w);
- w.id.should.equal("dbfe10c3fae71cea");
- should.exist(w.publicKeyRing.getCopayerId);
- should.exist(w.txProposals.toObj());
- should.exist(w.privateKey.toObj());
- (function() {
- assertObjectEqual(w.toObj(), JSON.parse(o))
- }).should.throw();
- });
-
- 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"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
- var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":0,"receiveIndex":0},{"copayerIndex":1,"changeIndex":0,"receiveIndex":0},{"copayerIndex":2,"changeIndex":0,"receiveIndex":0},{"copayerIndex":3,"changeIndex":0,"receiveIndex":0},{"copayerIndex":4,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
-
- 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);
-
- assertObjectEqual(w.toObj(), JSON.parse(o2));
- });
- });
-
- describe('#fromEncryptedObj', function() {
- it('should create wallet from encrypted object', function() {
- wf.storage.setPassphrase = sinon.spy();
- wf.storage.import = sinon.stub().withArgs('base64').returns('walletObj');
- wf.fromObj = sinon.stub().withArgs('walletObj').returns('ok');
-
- var w = wf.fromEncryptedObj("encrypted object", "123");
-
- w.should.equal('ok');
- wf.storage.setPassphrase.calledOnce.should.be.true;
- wf.storage.setPassphrase.getCall(0).args[0].should.equal('123');
- wf.storage.import.calledOnce.should.be.true;
- wf.fromObj.calledWith('walletObj').should.be.true;
- });
- });
-
- describe('#import', function() {
- it('should import and update indexes', function() {
- var wallet = {
- id: "fake wallet",
- updateIndexes: function(cb) {
- cb();
- }
- };
- wf.fromEncryptedObj = sinon.stub().returns(wallet);
-
- var w = wf.import("encrypted", "password");
-
- should.exist(w);
- wallet.should.equal(w);
- });
- it('should import with a wrong password', function() {
- wf.fromEncryptedObj = sinon.stub().returns(null);
- var w = wf.import("encrypted", "passwordasdfasdf");
- should.not.exist(w);
- });
- });
-
- describe('#getWallets', function() {
- it('should return empty array if no wallets', function(done) {
- wf.storage.getWallets = sinon.stub().yields([]);
- wf.storage.getLastOpened = sinon.stub().yields(null);
-
- wf.getWallets(function(err, ws) {
- should.not.exist(err);
- ws.should.deep.equal([]);
- done();
- });
- });
-
- it('should be able to get current wallets', function(done) {
- wf.storage.getWallets = sinon.stub().yields([{
- name: 'w1',
- id: 'id1',
- }, {
- name: 'w',
- id: 'id2',
- }]);
- wf.storage.getLastOpened = sinon.stub().yields(null);
-
- wf.getWallets(function(err, ws) {
- should.not.exist(err);
- ws.should.deep.equal([{
- name: 'w1',
- id: 'id1',
- show: 'w1 '
- }, {
- name: 'w',
- id: 'id2',
- show: 'w '
- }]);
- done();
- });
- });
- it('should include last used info', function(done) {
- wf.storage.getWallets = sinon.stub().yields([{
- name: 'w1',
- id: 'id1',
- }, {
- name: 'w',
- id: 'id2',
- }]);
- wf.storage.getLastOpened = sinon.stub().yields('id2');
-
- wf.getWallets(function(err, ws) {
- should.not.exist(err);
- ws.should.deep.equal([{
- name: 'w1',
- id: 'id1',
- show: 'w1 '
- }, {
- name: 'w',
- id: 'id2',
- lastOpened: true,
- show: 'w '
- }]);
- done();
- });
- });
- });
-
- describe('#delete', function() {
- it('should call deleteWallet', function(done) {
- wf.storage.deleteWallet = sinon.stub().yields(null);
- wf.delete('xxx', function() {
- wf.storage.deleteWallet.getCall(0).args[0].should.equal('xxx');
- done();
- });
- });
-
- it('should call lastOpened', function(done) {
- wf.storage.deleteWallet = sinon.stub().yields(null);
- wf.storage.setLastOpened = sinon.stub().yields(null);
- wf.delete('xxx', function() {
- wf.storage.setLastOpened.calledOnce.should.equal(true);
- should.not.exist(wf.storage.setLastOpened.getCall(0).args[0]);
- done();
- });
- });
- });
-
-
- describe('#read', function() {
- it('should fail to read unexisting wallet', function(done) {
- wf.storage.readWallet = sinon.stub().yields(null, {});
-
- wf.read('id', [], function(err, w) {
- should.not.exist(w);
- should.exist(err);
- should.exist(err.message);
- var m = err.message.toString();
- m.should.to.have.string('Wallet not found');
- done();
- });
- });
- it('should fail to read broken wallet', function(done) {
- wf.storage.readWallet = sinon.stub().yields(null, {
- 'opts': 1
- });
- wf.read('id', [], function(err, w) {
- should.not.exist(w);
- should.exist(err);
- should.exist(err.message);
- var m = err.message.toString();
- m.should.to.have.string('Could not read');
- done();
- });
- });
- it('should read existing wallet', function(done) {
- var wf = new WalletFactory(config, '0.0.1');
- wf.storage.readWallet = sinon.stub().yields(null, {
- 'opts': 1
- });
- wf.fromObj = sinon.stub().returns('ok');
- wf.read('id', [], function(err, w) {
- should.not.exist(err);
- should.exist(w);
- done();
- });
- });
- });
-
-
- describe('#open', function() {
- var opts = {
- 'requiredcopayers': 2,
- 'totalcopayers': 3
- };
-
- it('should call setPassphrase', function(done) {
- var wf = new WalletFactory(config, '0.0.1');
- wf.storage.setPassphrase = sinon.spy();
-
- var s1 = sinon.stub();
- s1.store = sinon.stub().yields(null);
- wf.read = sinon.stub().yields(null, s1);
- wf.migrateWallet = sinon.stub().yields(null);
- wf.storage.setLastOpened = sinon.stub().yields(null);
-
- wf.open('dummy', 'xxx', function(err, w) {
- wf.storage.setPassphrase.calledOnce.should.equal(true);
- wf.storage.setPassphrase.getCall(0).args[0].should.equal('xxx');
- done();
- });
- });
-
- it('should call return wallet', function(done) {
- var wf = new WalletFactory(config, '0.0.1');
- wf.storage.setPassphrase = sinon.spy();
-
- var s1 = sinon.stub();
- s1.store = sinon.stub().yields(null);
- wf.read = sinon.stub().yields(null, s1);
- wf.migrateWallet = sinon.stub().yields(null);
- wf.storage.setLastOpened = sinon.stub().yields(null);
-
- wf.open('dummy', 'xxx', function(err, w) {
- w.should.equal(s1);
- s1.store.calledOnce.should.equal(true);
- done();
- });
- });
-
-
- it('should call #store', function(done) {
- var wf = new WalletFactory(config, '0.0.1');
- wf.storage.setPassphrase = sinon.spy();
-
- var s1 = sinon.stub();
- s1.store = sinon.stub().yields(null);
- wf.read = sinon.stub().yields(null, s1);
- wf.migrateWallet = sinon.stub().yields(null);
- wf.storage.setLastOpened = sinon.stub().yields(null);
-
- wf.open('dummy', 'xxx', function(err, w) {
- s1.store.calledOnce.should.equal(true);
- done();
- });
- });
-
- it('should call #setLastOpened', function(done) {
- var wf = new WalletFactory(config, '0.0.1');
- wf.storage.setPassphrase = sinon.spy();
-
- var s1 = sinon.stub();
- s1.store = sinon.stub().yields(null);
- wf.read = sinon.stub().yields(null, s1);
- wf.migrateWallet = sinon.stub().yields(null);
- wf.storage.setLastOpened = sinon.stub().yields(null);
-
- wf.open('dummy', 'xxx', function(err, w) {
- wf.storage.setLastOpened.calledOnce.should.equal(true);
- wf.storage.setLastOpened.getCall(0).args[0].should.equal('dummy');
- done();
- });
- });
- it('should call #migrateWallet', function(done) {
- var wf = new WalletFactory(config, '0.0.1');
- wf.storage.setPassphrase = sinon.spy();
-
- var s1 = sinon.stub();
- s1.store = sinon.stub().yields(null);
- wf.read = sinon.stub().yields(null, s1);
- wf.migrateWallet = sinon.stub().yields(null);
- wf.storage.deleteWallet_Old = sinon.stub().yields(null);
- wf.storage.removeGlobal = sinon.stub().yields(null);
- wf.storage.setLastOpened = sinon.stub().yields(null);
-
- wf.open('dummy', 'xxx', function(err, w) {
- wf.migrateWallet.calledOnce.should.equal(true);
- wf.migrateWallet.getCall(0).args[0].should.equal('dummy');
- done();
- });
- });
- });
-
- describe('#create', function() {
- it('should create wallet', function(done) {
- wf.create(null, function(err, w) {
- should.exist(w);
- should.not.exist(err);
- done();
- });
- });
-
- it('should be able to create wallets with given pk', function(done) {
- var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m';
- wf.create({
- privateKeyHex: priv,
- }, function(err, w) {
- wf._getWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.equal(priv);
- should.not.exist(err);
- done();
- });
- });
-
- it('should be able to create wallets with random pk', function(done) {
- wf.create(null, function(err, w1) {
- wf.create(null, function(err, w2) {
- wf._getWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.not.equal(
- wf._getWallet.getCall(1).args[0].privateKey.toObj().extendedPrivateKeyString
- );
- done();
- });
- });
- });
- });
-
- describe('#joinCreateSession', function() {
- var opts = {
- secret: '8WtTuiFTkhP5ao7AF2QErSwV39Cbur6pdMebKzQXFqL59RscXM',
- nickname: 'test',
- passphrase: 'pass'
- };
-
- it('should yield bad network error', function(done) {
- var net = wf.networks['testnet'];
- net.greet = sinon.stub();
- net.on = sinon.stub();
- net.on.withArgs('data').yields('senderId', {
- type: 'walletId',
- networkName: 'aWeirdNetworkName',
- opts: {},
- });
- opts.privHex = undefined;
- wf.joinCreateSession(opts, function(err, w) {
- err.should.equal('badNetwork');
- done();
- });
- });
-
-
- it('should yield to join error', function(done) {
- opts.privHex = undefined;
- var net = wf.networks['testnet'];
- net.greet = sinon.stub();
- net.on = sinon.stub();
- net.on.withArgs('serverError').yields(null);
- net.on.withArgs('data').yields('senderId', {
- type: 'walletId',
- networkName: wf.networkName,
- });
- wf.joinCreateSession(opts, function(err, w) {
- err.should.equal('joinError');
- done();
- });
- });
-
-
- it('should call network.start / create', function(done) {
- opts.privHex = undefined;
- var net = wf.networks['testnet'];
- net.cleanUp = sinon.spy();
- net.greet = sinon.spy();
- net.start = sinon.stub().yields(null);
-
- net.on = sinon.stub();
- net.on.withArgs('connected').yields(null);
- net.on.withArgs('data').yields('senderId', {
- type: 'walletId',
- networkName: 'testnet',
- opts: {},
- });
-
- var w = sinon.stub();
- w.sendWalletReady = sinon.spy();
- wf.create = sinon.stub().yields(null, w);
- wf.joinCreateSession(opts, function(err, w) {
- net.start.calledOnce.should.equal(true);
- wf.create.calledOnce.should.equal(true);
- wf.create.calledOnce.should.equal(true);
-
- w.sendWalletReady.calledOnce.should.equal(true);
- w.sendWalletReady.getCall(0).args[0].should.equal('03ddbc4711534bc62ccf576ab05f2a0afd11f9e2f4016781f3f5a88de9543a229a');
- done();
- });
- });
-
- it('should return walletFull', function(done) {
- opts.privHex = undefined;
- var net = wf.networks['testnet'];
- net.cleanUp = sinon.spy();
- net.greet = sinon.spy();
- net.start = sinon.stub().yields(null);
-
- net.on = sinon.stub();
- net.on.withArgs('connected').yields(null);
- net.on.withArgs('data').yields('senderId', {
- type: 'walletId',
- networkName: 'testnet',
- opts: {},
- });
- wf.create = sinon.stub().yields(null, null);
- wf.joinCreateSession(opts, function(err, w) {
- err.should.equal('walletFull');
- done();
- });
- });
-
- it('should accept a priv key a input', function() {
- var wf = new WalletFactory(config, '0.0.1');
- opts.privHex = 'tprv8ZgxMBicQKsPf7MCvCjnhnr4uiR2Z2gyNC27vgd9KUu98F9mM1tbaRrWMyddVju36GxLbeyntuSadBAttriwGGMWUkRgVmUUCg5nFioGZsd';
- var net = wf.networks['testnet'];
- net.cleanUp = sinon.spy();
- net.start = sinon.spy();
- wf.joinCreateSession(opts, function(err, w) {
- net.start.getCall(0).args[0].privkey.should.equal('ddc2fa8c583a73c4b2a24630ec7c283df4e7c230a02c4e48bc36ec61687afd7d');
- });
- });
- it('should call network.start with private key', function() {
- opts.privHex = undefined;
- var wf = new WalletFactory(config, '0.0.1');
- var net = wf.networks['testnet'];
- net.cleanUp = sinon.spy();
- net.start = sinon.spy();
- wf.joinCreateSession(opts, function(err, w) {
- net.start.getCall(0).args[0].privkey.length.should.equal(64); //privkey is hex of private key buffer
- });
- });
- });
-
-
- describe('Backwards compatibility tests', function() {
- it('should be able to import unencrypted legacy wallet TxProposal: v0', function() {
- var wf = new WalletFactory(config, '0.0.5');
- var w = wf.fromObj(JSON.parse(legacyO));
-
- should.exist(w);
- w.id.should.equal('55d4bd062d32f90a');
- should.exist(w.publicKeyRing.getCopayerId);
- should.exist(w.txProposals.toObj());
- should.exist(w.privateKey.toObj());
- });
-
- it('should be able to import simple 1-of-1 encrypted legacy testnet wallet', function() {
-
- wf.storage.import = sinon.stub();
- wf.storage.setPassphrase = sinon.spy();
- wf.storage.import.withArgs('dummy').returns(JSON.parse(legacy1));
-
- var w = wf.import('dummy', 'xxx');
- should.exist(w);
- wf.storage.setPassphrase.calledOnce.should.equal(true);
- wf.storage.setPassphrase.getCall(0).args[0].should.equal('xxx');
-
- w.isReady().should.equal(true);
- var wo = w.toObj();
- wo.opts.id.should.equal('48ba2f1ffdfe9708');
- wo.opts.spendUnconfirmed.should.equal(true);
- wo.opts.requiredCopayers.should.equal(1);
- wo.opts.totalCopayers.should.equal(1);
- wo.opts.name.should.equal('pepe wallet');
- wo.opts.version.should.equal('0.4.7');
- wo.publicKeyRing.walletId.should.equal('48ba2f1ffdfe9708');
- wo.publicKeyRing.networkName.should.equal('testnet');
- wo.publicKeyRing.requiredCopayers.should.equal(1);
- wo.publicKeyRing.totalCopayers.should.equal(1);
- wo.publicKeyRing.indexes.length.should.equal(2);
- JSON.stringify(wo.publicKeyRing.indexes[0]).should.equal('{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1}');
- JSON.stringify(wo.publicKeyRing.indexes[1]).should.equal('{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}');
- wo.publicKeyRing.copayersBackup.length.should.equal(1);
- wo.publicKeyRing.copayersBackup[0].should.equal('0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f');
- wo.publicKeyRing.copayersExtPubKeys.length.should.equal(1);
- wo.publicKeyRing.copayersExtPubKeys[0].should.equal('tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6');
- wo.privateKey.extendedPrivateKeyString.should.equal('tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm');
- wo.privateKey.networkName.should.equal('testnet');
-
- });
- });
-});
-
-
-var o = '{"opts":{"id":"dbfe10c3fae71cea", "spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{},"settings":{"unitName":"BTC","unitToSatoshi":100000000,"unitDecimals":8,"alternativeName":"Argentine Peso","alternativeIsoCode":"ARS"}}';
-
-var legacyO = '{"opts":{"id":"55d4bd062d32f90a","spendUnconfirmed":true,"requiredCopayers":2,"totalCopayers":2,"name":"xcvzxcv","version":"0.3.2"},"networkNonce":"53d25e8600000009","networkNonces":[],"publicKeyRing":{"walletId":"55d4bd062d32f90a","networkName":"testnet","requiredCopayers":2,"totalCopayers":2,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":4,"receiveIndex":2},{"copayerIndex":1,"changeIndex":5,"receiveIndex":2}],"copayersBackup":["02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5"],"copayersExtPubKeys":["tpubD94LTzAUiW99mpA59nyf6fAHh4xKGmnwbgCV4gU2bRpeN9CRiMSurqme22px5NmJAo6FdcdH883Zu98VbqyhesCJ86kUEjH3Zpufy5FfcaC","tpubDA2U9H6LkRHDRbRxHBp4VTbxPc7JqsvtcLxrE5QJF8z1iT6hMJ1pXSVf57GWRcxXutYvpoXRurDVGsscJauMtnJBkYAWBVExYmm91XQE2zz"],"nicknameFor":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":"asdf","02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":"qwerqw"},"publicKeysCache":{"m/0/0/0":["028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90","0332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec8"],"m/1/0/0":["0220ad514cf593d0c3905d3bb49bc5767a9410823bf9b77ea5ef2cf1d1016d77a8","02fd42cf66f1dbdc7bbb9ae09aecea72df479ffe5a0c4641301067e331d12e416d"],"m/1/0/1":["0315f7868eaf1f9b7127e3f7e0222c5e473eea003e34700f4758b6873c525d6723","02a2e8ed5e90dd39e3842fc790e06178997dbca319987f365317589e2a71a93658"],"m/0/1/0":["0244a25a0b97b26707fd855c15b046b901be85a3b70a781d0678608e633440eeca","0358cdcbc528ddfb7173b0dab283f702be82546ff031e4a832a7270080cb875959"],"m/0/1/1":["025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f","020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d"],"m/1/1/0":["02fd0e7c62b7b58d1ea7bb4cb84d53b019df99d3703a42aed73a2cfa15f3af5d08","0355a15912e76072ef50e6643376b8a9da8422ed4f8ea07b1d84d4989be5a39b2e"],"m/1/1/1":["03bc3e1f4db32efd8eb1fd44a1665938d59628429c67e1e8b7054ab5717f4e6750","03c4c817b633ac31f44f16f390af831d35f7d98744a52a0f23e9598967342255f8"],"m/1/1/2":["02826fe7e9da408480ddeb1d4414c5100b350f862ca718e27122681e1a0ca35077","02bd25af907bb3edbf6b2cd1ea90eaa92cc93ec47bea7d339af44c1d2c05708e99"],"m/0/1/2":["0337a1a70364b94745d6e26d2d28919cf528304f52765f12ef43e3d6da0a6c8dc0","039d83db9aa43e6e00e0304e6971b6079d79dc12d8d55ce2e6fc24a52ba8d41329"],"m/0/0/1":["0359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b8138","037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d93"],"m/1/1/3":["02600e5c41670773a213a4cb58c8f2fa3e83840784bc7f0b56925e1075e06632c2","036d01867af5f61371151ef7d9026fa0400a623f6924e404ee0b856625268972f9"],"m/0/1/3":["03e5a9b039b187ca8e065627df402e4a5b196b94198542da7036879de08be63d2d","0304f3e0b70f696d80e5785dc7747d6dcb55ba24c31f2d80bf184b4e582e6b47fc"],"m/1/1/4":["03741afa5bd50d6ba5801064c810fae84f6a4557d6a88ddc8591d0d4eb68a8fc41","0214dd6ce6073b05999fb887098ca6f7e1d0b4fdc0760557786907df353df90d1c"],"m/2147483647/1/0":["033e072a53ea835763a03c66e35c35384736210a1bb7d7ee6d9a3e109e82426b30","02e37b5570c053da8a8ee587be86fc629775c4db890aba2745ccc4e4dcc8c31041"],"m/2147483647/1/1":["0228a6de42ef421c263d1efd9f28d9a7d15a261995028a24eff6b9f1c3fc46e6bf","0226cff885cb0d607cc9cf69a7608316eb3fb2ec344c0c9956246ba776116fc396"],"m/2147483647/1/2":["034fe2a8f0b98445eb5810fe36572ad2f64ed9bf64dc9de624f99c0142cb07c682","02f2c5c758e32293f5c193fd69afadbba83abafb397db01e6f2b447690e900475a"],"m/2147483647/1/3":["02b25ef9434446c51f10678f787e4913de582e34d164bd3b06af7732c5476df1a8","025d51a1efd59bcff22ee2e0af61b21a7ba5f639e20dfdf25690e926005177dd0c"],"m/2147483647/1/4":["03e5734e1d29b2f684d0446b7a2ffbd0ba8952570a502d0d14b1efd8f24b61be53","0258fc28a324848d8d0154e8614815e35c668d274a8f01957bb99aab8dc8f386c0"],"m/2147483647/1/5":["021f9e775246765e1cfba0ae453b4eae6cd4ae5a57a09c319edbe89d4dbbf23be3","02857f66571a1c3eb9e72d22ae88e734c03d448bced4dcfd345c2059468124c741"],"m/2147483647/1/6":["02c072f329391a25255dc6452e5f5220966869dbf736ba8a8c3ae9d273a84bc3fd","030920a8b8e88c4db2871a7df0878a86cf0695f6d96bb50c701c3454f3df25176a"],"m/2147483647/1/7":["036bf329fc19bce10cf1999fae5bfa80290ff7b44776b49c7b0dc9eec6cffcfa21","03955a549875b4f7b9be28b9ff4bcd51ad2bc224430b1634baef890585885d5e1b"],"m/2147483647/1/8":["024879c9c9a261b3141ecfa1c79c4efc25278c844ecd1dcfcb95d9c19581fbdd25","03fb4a5fdb91239df3ccf7f61a5b99e7e72483101e21c9d1ee0d85544e9354c6c7"],"m/2147483647/1/9":["035928a107ec01f78cd586914d5a49710fd42e352b1312e3ad0eeb2c9666fdf8e7","03a54c03093797854829c75357f092356352a109042bbb83bdac20cb4e5eca27ea"],"m/2147483647/1/10":["021e7a3a7efe888c5e820b5cf0f03317b2b4bf438d8563449aeb7a77cade97f136","03ec0960b3d1df52ca3cc2c82b7d97063400da4dd051bba2f9bab6cb44aee01efa"],"m/2147483647/1/11":["035d70c26b7f429861f555f7c0d99947411b23b7f95303fb8d5de5b82a95aa30fc","038b922f7024f5446d6b48e5253643543b35c006d90fd37688105c6cefcd8adb8a"],"m/2147483647/1/12":["02158d6503891c6c65a606221dbf5c68d0832288975914007968419939588ecb24","0248264cb1763a3f4de9b34787b4bc5443ec92ef915927494bb9f1c1c0b498c7ca"],"m/2147483647/1/13":["0349965eea38a25ae0c061faeac4c4e57e648bc4c0f059d07b3b8b7962cbc0dde5","0352243d9269565ce2a1ffdd0b8e43a442c6dd1c9edda86eaaf2cba5a4a95c40f1"],"m/2147483647/1/14":["030fa6e3d0c5cedc0581955395c77cbe134c912a47971023b9695332df3f7bb200","03f2cf09e33326fb59bf3f13e6298d2d5d29c9eae3b872e5a851e8d8d77259c883"],"m/2147483647/1/15":["02bf0d45e41339f552df6f8baf4392142921fd38b0f2a4388a905ff6cbacbc278a","03fabe46bb6706a1b8edfd28c046a8891b4530bbe5305080b72b0d08ebdf7b8c0a"],"m/2147483647/1/16":["03a4e3146ed34d6a8af4e4379e6edcff32cb0373ba232b3d746af3052f674133ac","030311b73c6f5c46ddffc0cfce6e5ed0b671d94267d8e52cd8837f2a479916eb91"],"m/2147483647/1/17":["03233df93c762d2f06c7f5f388e4e0a8dbdb13302acba0d2d6995c487d8aec9f2f","024badfdcb7e772ac7fc1c46d3943b07500edbbece105cdeff3eb9e9fcc9f54782"],"m/2147483647/1/18":["0364035475a098e00eb010c500cad3c90af3e81a4bd613144bc9433a150f14718b","028223dc8142154e7477ce000b3dc13e1d15a901553d9b18864c8645b582b38fe6"],"m/2147483647/1/19":["03971b74b4ac4bdaadf636baa4caa82fe5355471ed6ea05a9cbe5fc6c9e4b9db76","0202ebffacd01f83849e5bc5c0e2c317bc5fb2fbcb2d6d4482a5235f9f1308b61a"],"m/0/1/4":["03005ee9ff028c98fd132e531023f2f2b61ff0d26022f979dd98088d2ba167b031","0345ea82e8dfe38277f0c3aee18d2dd93edb63e8663ac83328a7934d2ca57006f6"],"m/0/1/5":["0391bc4990b71d8a3f156ae7107929ed6372b0b4ba8a868253f71ba7189d1efa02","0312a74cf2e7c0dd41897d04fabfd8cc3187b84a28305cfc79315b24e6fe23a6b4"],"m/0/1/6":["021a38c492607ff9684a4fec445e47b5b7100d3ef9e9dc0d0b37c0a646d28d4f77","03ae0b46ab36f97447ebaa53f2b5c8f090f15395378785f2fd285eeba17fbf3f65"],"m/0/1/7":["0308cdec88c1ffe16edc98853d9c08dbd4ba2541ba566668ca17bda19d7eb3481f","02dd622267c2e68287287b8b61724f76fbe84096a56aa5054af92f8fe25380e2d1"],"m/0/1/8":["039647da9ad725836bcb28a3e0497659a28d7749d1416c421a0a01c62d237ee962","022e22aa61eafda0dd8820427f1a06314d352a15ea8645e7ab9b80920017084d82"],"m/0/1/9":["03a4ade946076c6962b70c70ac7fad3a87efb59a1d0a4e32bda13a6d47fe9df961","029a07235aba04ab69526e117d836d5b3fae5cfc8c5e72b10c6d1afd261ccc19f3"],"m/0/1/10":["03c78e9b6493b22790db1acea20df9444e0f9c424fc5756e7a32c290ae01783953","0254c130ee467a96570c9f5ebea89de04f0b1db1686b164f2694339bef8f25dd88"],"m/0/1/11":["03a762c43318ef8d4840fab04c8db73797dc648825fac60f2730b4c76678df1cf3","0212c684a4de8e750ad2dfe2b136370ab9803eca178ed9a27b3990c29b067de35c"],"m/0/1/12":["02702d221f9b15c5cf75ac2f497a6c63e60213087c3d2d3be46768e3ebd238e26e","03ed58580744deb357258e44548212038670769d8d51e385d4fb8414311fd01b52"],"m/0/1/13":["0320e0597b54c62768352f433389cee4725d6094d7bcb5c72265edcc0933829aff","02c5706f11b9a85f3176c572842b7c9812c2195058d24d945bc026b00312740e76"],"m/0/1/14":["02fe43077676b844226d3aaa62e8a86d237710d92f882366944acbde0c8992fcaf","039a6a8662abb8910741cf331320549665e9feb28ca94d1ab6a43c84fa330b94ee"],"m/0/1/15":["0369f99f72847af93d50ab8ee75b6e7e912d26e27be96f6d6b7215cf7daeff7ba5","02521700cc07c953ba5aa586fb0e4795a34dffc68c5fb43e038be3866e40f4daed"],"m/0/1/16":["02f67d1d89bd8fe2f91c5b973cbdacfb4ba440e7656bce284cf73d549625607347","035da9cfac5a803dcb2b283b02a2515a4a1bcbf3d19e0d180aee8fc30193bc0555"],"m/0/1/17":["02c024ec199d240e8d6c66276b94b91071f7cdf2bef540c29d6d18d25de7b1cf7c","02190865f9dafae3f7f05c093463be5632946422ddda0a6fef6904390792516067"],"m/0/1/18":["035ed504d7704ad984a333b8eb0fceb8be043da9284de31ed84d9e68d90c75507d","033303c415b50421732402df00f4baa219f334647a7eb5014b9f8079864d6ab558"],"m/0/1/19":["02ce49fe86b0eee73663b1ee867b16b97c876af26f12764c528a2e6d0eb55ad3d7","03ab969bc81796b88e44c340d854df955fc60ea17ea92db5d3115595d6dec890d8"],"m/0/1/20":["03e2fa915378cbdffa0d919b0fb50c7256ca731b9d571b3365e486893a1d43079c","038d058b895cf084dccfcc9367e4796a5cf4ddceed6c35f6885d75c80119613350"],"m/0/1/21":["02fcb1bf644446b5b42205272af72f0aeab9e92ca29aafa91c5fb69142764017aa","035c5fe5c8811603279a5b72b6c30735d702817db1eab937c622269e28192ffa90"],"m/0/1/22":["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5","022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"],"m/0/1/23":["03f40b82fe8cacff08879f13c45f443a3dc3ea98e1d75d5f32a19f5e5a8f7a905b","028415ee458e4dcfd440ce969726f3b58ae74fb6cf3995ced099579211e7419844"],"m/1/1/5":["032748a6282e21f571b8c8dd49e775deb83c90fcf88dc4ba81d878536973709c3f","020837cd68f14ce571b335eecd1b6fa0af43e1576dd9721aaca2a8ab639ac6b7cd"],"m/1/1/6":["0337032efb013dc92bb8dccfbdda9f5c28f0039a9c60953d41003d095e9f9778af","03ceed2da6b9603297061dc8eb930112ba726b2ccf5eec67f4866a05ca4049a22b"],"m/1/1/7":["0383c96ac2af7d203f69133b2fab6b68366b5075ad6957fa06759df3b20fbfec70","0311385f79834cedaf2230a48c0f9dc8e794da1869fc595db2518d62debb85579a"],"m/1/1/8":["03efc649680280f4e4df96da923bc88330275004125ebe5483c2f3e05ca52e19a4","02803c02d197d780388259afbd001ae41fa3eb3e2bac9627aff540521c184c3b23"],"m/1/1/9":["03af2fe6aa027a76b42c1c4050a040bfd026ad2daec1bb96a5fe2d026a7df919de","02ce14163047c640228796fb1f72bbe3afb05819ad141598a4f021058a6f79dd3b"],"m/1/1/10":["033770378bd762cf0408e44e4e604bef77e336170428c506949b1a4f1f2963e574","02c58ed43946f699dbd3e36d3e9aab2714cadeb19ecd3a56e4328c50336b4a76cb"],"m/1/1/11":["02898a1545fa19bdca92adc498698d27b86529cd4c08946d9d29604734b86f31af","02b402767a045ede072600924401c0d720000b2ed59fa444bfdbef4a5f1cead745"],"m/1/1/12":["039b8659430be49913e2cd869aa8c99ccf49a13df35837370b792033dadb891483","03264e63df292257cc76babb15d15bef620d1c2f8c3bbc78d6ea02d127e5ee7386"],"m/1/1/13":["02381a559791b8e86bf546e2c718ae63cf24eed0518a58e4d4a4b310adf2cd38fa","02d7f8283a4418d912508901b4a3db0d2103206dfdd74b3c75648671e20ecfd445"],"m/1/1/14":["020376e8c550b7d9faa0b2da947a2a36fab22c6e8190b6f99460b6022017bb97d7","03fbc5299190e6628de28c92aaa12e3a131b21eb7266462c46fbedeb86fa878055"],"m/1/1/15":["027209fd3b0cf7368180a5dbb16b928c997d33fccb78505d48440c7d23eadf5460","03450bfb22858726cd7e228e6733f69457546978a95188565c53e0d1c0d6070ea8"],"m/1/1/16":["03cb355ba04f64293793855121bab5831f84a3a3edf7cd31fccaa6d67c407a4912","028bc897a39c1224610b765a80f4cd8ab79cb37776f58fec9c10ac6f649d1f3c72"],"m/1/1/17":["03f4cb0564d7e2c6b85673503b7954db22779f29a8f3374904573984e318a96bf1","037c11b6ee906d84aa7eed359d758d986d912b6f8e5cbb1acf0982a77b3ef812c4"],"m/1/1/18":["02d2e5798f33f6889472857744316f2d253f25f88379610063f40cfe5798d9858f","0253cefdfe9ca987cbf1c950b6246d5b7a194d8dfad47c3a78dbbc5c1d01511d97"],"m/1/1/19":["0336c325f5aed366ffc10d553f2bfd4d69e66cbe1688d77af14efc8827aea2e318","0378b1b9a6074f9f2ab4fa9ad1e14649c621b0c8124a1b148914d3c10e6ab390c6"],"m/1/1/20":["03ea55740a734689ce778a8c00df8ebf4274c8f66de7d05646fe5c927773ff7f2e","02275b558d49aef955b6dee51a3c0a53f4b076b97bb3f26abcc82540168ec87cac"],"m/1/1/21":["03c77869c9984664eac9c238f4b6d806c9f48ca8a736c48450f398834db2aa915c","02d984f548c7f60c09dad3287cfc48807bc8157123989636c713be61be6a2e9ced"],"m/1/1/22":["03ed7c6a3c854c1f9459891691cc32671402f9e47126919878251e568dbdf353f8","02a113dab22cd9e46967b3fd76b9b9ec1d227d88817a9300e42d332cca2a0877fd"],"m/1/1/23":["02ee186432dcf69fda50a6fdbd94651817d8a271c273a5b70cab3ec4ae77a3753b","02291370aad9de0dac676355ced64e268b0c431a51f42f12d13f5144940fce4285"],"m/1/1/24":["02bf71435e84e66547c8c583d5ba226a5ac4d935e0a9f9603ecd8925c3e847e91a","03578d8657d285a89d9d597632db662cfef9baccfb55c76b1e87948a94fc9de30d"],"m/2147483647/0/0":["02a8425bbe23426219065969f695a6c3e242b24e57226bffdd542be8fd6be968c9","03057a42fdb6569fb1615b173ccb702453db2eac5be4291b82d4511461eafbed87"],"m/2147483647/0/1":["0250c3d3e86e332010c5233c2ec3bc728026002f0037cb3382d6318409b0e70796","02cfac1e7c4c88191201080f8316af52d9faa6ba624a6e160279e9fac4d1cf79a9"],"m/2147483647/0/2":["02a8c266a5b92eb50c8be91f95e4d1ad968b2f57d527377fd642d63fb84474f61a","028cc954ab31bd179ff80b8a05f95430ae534e61b3ff35f5284fa2fbe1832ceccb"],"m/2147483647/0/3":["02f719e1a7ab00ea98611453fb03d44c1da04655bed74af392534d70099039b4c2","03bfa548bfd4718c50bfce173f780eadcfb679d9c0206c91a2fa1879a9cf7558b2"],"m/2147483647/0/4":["0362c0695d397ca26bf47f0e641bb3cfb06ff29ccac2e1d56ded3afcf88b1e688d","02f9d87b05bdb3b9e82f506b43f813041c0e403274adc23d11e5e1651e34b606c2"],"m/2147483647/0/5":["033731323032d4ee08e858fc71f93970444333e183a1d5052e1d08cfb511e262c8","023e12556cef67ade35b7758916b5e1a3ebe074ccd35c5d8eff6b01321f63eb495"],"m/2147483647/0/6":["025d11b90081972bc1c258c9d6f476dfc2f95b69f0e9935322bf9c21deb580ff64","02b065f56a378907354f0738a0ed74f10660c6b5dd68c9f992093b75ce3d7d8b72"],"m/2147483647/0/7":["0210e721e8a35db9d8c855a0d346f60c09208f3be80b39e03af2c29db777332c71","0277f352969fadb1f1835f9a0fa99c6a3c7b6c281be5b2794c88a708eb177ea33d"],"m/2147483647/0/8":["02998d8d41e4215cd2a961a415a3ed0b1f984f1627719a7b102a75864943c4d87b","03d8ed7fc8f68a77f68d3afd007b7aa4c89944195143630ce183f0fa5438f2b559"],"m/2147483647/0/9":["0324fa91737588e4f85937303ce65c3b91b5f2ae506a72d92b83e3f5f9aeeb3c6f","02a011be72c4a400319212228106af278823a97acfe0a67e1ecd866d446b315114"],"m/2147483647/0/10":["025886ba287922a904881c7315e6fcc410a7976741771a5937d3a1a01b529f21fd","0243bb91ceed9d29d0c2ca66a8ab77e82110bbcc023beb4106f787964f44a0b972"],"m/2147483647/0/11":["0369d21684894cc2d4b2f5e581ede3cac9e8db4161a08e7737c1be129bb673d3d5","03c9ef27e3cd3dadc078fdfd9936a7ad9bf7954747085cf8f8a2a5bb3431f68a9f"],"m/2147483647/0/12":["03a73b8fd859bf6acebffdfffa2597199091daedd2c011ac67fc3494d8a1a8ceb6","025a213f7771c8be03f43f2e7f469ad4ef2cf6907ea284b227a786d1f55dfa7144"],"m/2147483647/0/13":["03a09f7ca257e1ab263cd5e6b0addc3ff868b93df132321d98775ca3505efb576f","03454c715739164bc55f347a651439cdf3ec146b35d2927beb60e8290b3916e082"],"m/2147483647/0/14":["03a64b1f7bd94a6b1a6e84ea444e0ba04e9deb86460934ccc37c0615a134a8257b","02794f09210b1811a455f3e1c7bcd35c76dff2523190fef9615eb27e2376acac1c"],"m/2147483647/0/15":["0392dca2fd9a3bc2b2a7d90a848719069fbc5f22bff7327bb8186c032514085263","032ee8a33ea76d70c7ae839448ca6c5b1af89146f2922e23ba1822df42dbc7e66a"],"m/2147483647/0/16":["031a22a1a3c1abad7c4d782ef6ba3cc00f2e8fe549eb33e0732200aff6d3174831","03bdce9781289e0c31cf727f4c93fe46f7930dd8fd68f818ce241f1ede268e8e0e"],"m/2147483647/0/17":["03b12d27e9aea2c2ad598e54e40860a705ac2ca2427aa511b501b38ec368ea5c7d","03e60d35d84d4536cad895215256b312bb4879a8d417251c279995e58f25da3d54"],"m/2147483647/0/18":["0380266cc9a9673676ad6a1b2e7148766df9c25b4dce299e5edc4f65b72aa58e64","0329e2a8a48c06c0c45dfdd2ab33e6455551557d8ebaf8c12fdf7470f8c45f1d28"],"m/2147483647/0/19":["036fe62af85560d7eea7c7af55e60b32a97dca80134d0aedffb19eb2705b9d6e01","02381c2c30b9f81e2a53c69028fbe11803acad0420b267719b7a80870be0baaeb7"],"m/0/0/2":["027bf94b8fc4e9b42683af25fda125ccab8760040717d100270dd4afd032692daf","026382c6c9357250d96dc21e43c053857a64efeac1887fdcbc107fbe3ecfc6115a"],"m/0/0/3":["03fd203acbd9af3cbbfb709458f8952078234a36094f12d00372e4b2b14cfdf419","03f2e5db59aea5dc89f53ac2a9f4ef66d41265c45afc5d763e0ca61ab70c7c61ec"],"m/0/0/4":["02a1d7cf4fcdbbf4de4002b844c3bff1639073f1cd6e5c4a4e02596b45d3f518c2","03b5fba813294e6ae096ea158833453caa5a945609b0a554696091b9b152bb0f7d"],"m/0/0/5":["0261d37e3b56ef4e106c59753037f516a4b1c45e056b2a3e00f8b77f15aaa7f8a5","0256a55e66e0de1603f0d600c0eb5f5486cf3512a776a36f3ab0d1941fc0dc9b09"],"m/0/0/6":["031db2826af215fe6cbe3f6e121b0497840fc49be133cff0a4d4eab679d6b99d70","021dd722c3f35dd04fcdb57f09b76c723d521fb36751de03ffd08096ddf1dc1f86"],"m/0/0/7":["0354ea75bdd9eb5beae7262e4a5eeb58bd10103ee0185e85b749ea39f6615d0f62","03f2c8f3b6478c0501a8578d5caf5ac2974f8213fc5e699d62dd2af58fbe8781d4"],"m/0/0/8":["0282e67df3bcd1e1662469b4c3151fb50ee1e46b75d787d91184c16b9803131f82","02921a7054af1e425f4137a5eb6b34d1f2b9d81c2625230194bc30657bb4277e11"],"m/0/0/9":["033e7e387933983ceab37c8388bd8ebc5119760f493ffe6f083bef0e5dfe22891d","02d660d60cc55d80912e0745cb142a8596a4604fbf72f9aadec0599aa2ed62461a"],"m/0/0/10":["022ce5b2750ae34512199856eab9e912dc25281cd8b88e7688a46c3b9a389701cb","02f14aa1608fce3b6088148709eb5fe72b61699c931fa8d95a45fab1106859d1b0"],"m/0/0/11":["0288dbef3302c1bc5556028adb33e2f9e03c119dbad4f706befb8ce86cea459f2b","03f13ced465e2e0a3aaa8895f3185d5711e0bebdaf507610b7a669ac8fc82da8fe"],"m/0/0/12":["031ab4677885340d2f927ccc9747f4346b79e4eb6c750695095a8a2524610fa94a","038c881910fbd8b50d193db4e0c84f5b7840820397f92cf0718a8e06d027125503"],"m/0/0/13":["031b568452cba22eb7a88c6085489e53e35abd16068882e71a140e47e12dee9c61","020d09885ee362101d12d34ce0918d41593634db1b9413e5415c6755753b9330e8"],"m/0/0/14":["024177bc9aa03cfc72eda2dfddffd7fe9d0c2f007fc3ba1a48280feae2b9fb117a","03394ad321668440c08da76eb35475ba3a8c0e8cbe0ed81468673a8c72d38fe457"],"m/0/0/15":["02037b1cc696ffbe9eba3684edd53653386ef6cd7728401c40120037593a4c2ae2","020ab8d6900ec9c11ca5d96dfc0ce7cf0ee71653a7c45118e89abb4b113147e53a"],"m/0/0/16":["023bcbb8d4726a546087cdb83740adf0ace879b7195a572c652fa8ce4dbe195a04","0392721b230d5163d28b27fc7e059b875711f12b3da448eabe7229bde57530e637"],"m/0/0/17":["02498ee74e849d3e9261dd1863038caf83d6a3bc2eeebecf17055d4bab44dee77f","03d4dc104b2e0981693e8097437de9b05334a85e2c8edb02783897859bdbc93e32"],"m/0/0/18":["0218a9f524fe54abf8c3afd21314296cfd93eaa9227acbd457e6c9a742dc233cf4","03760f3d0c5db969bda698ff9352e3b7c332216c34825f4c6e857e39c9aee7cd35"],"m/0/0/19":["033dd51f7737f0e9db79f5c38e4298bf3396346904ef3933d290a22e5b77048d9e","0221b2eedccb9a37515263071550069b3b349a166f0f131d0028e8600d9a2251b9"],"m/0/0/20":["02cb6c39161f3244d7769f7ab96346cae2cf21cb6f4538f5e7382d363dc2f836c7","034f7bda4d1e9ed6a3774608a4d6cd8582ab59fe3187f8a7a7cf914d89426ebe28"],"m/0/0/21":["035490549d65f1360f10340037250b171470ff4c86966318a2b1eead6d8b969aea","03f6a04f6fcd07a4f32c82d53710ed30e0f54d43d41c67c661d158b3d0830c3ea2"],"m/1/0/2":["02972eae7e4302e319c266578e14a07839c1e788296a92906e6d66d938211dad5f","039ed6b488f1571ad6527acd6b6c5b8453eacf6665dc5cb7852e33d1c8ea73f9fe"],"m/1/0/3":["02bec4728888c2c045108353994bae5731ec7a7b41459023b0023e10b8d616bd30","03ce1efe16214c9eac595382e46a68143dd11a335b3f7c971ddd719ac544a5fc4b"],"m/1/0/4":["030e2df1d341568225d8dfbe5d07e98dae9f90e0f43e19dcc68c998a6ed7bcc1f0","0380f4c07dc84faf42d51779f104aa6e3b5c3ce2d7684b3cb76d49faeefc2b69d6"],"m/1/0/5":["029a54ddaa25f433b493f4b72df8c1d41be2c4d2963b8b61ee63cc86d16c12d066","021567c95e0317442e7367aa4e3378dd46c5bcef5860f789272fea83b917de0669"],"m/1/0/6":["03590320d80b61cc0874b579f467c9b5ccc50d9ef875bcf6bdd12e2d0c211e8973","03ee4677b6ee89a9d355851f2230506c6897ff219062c0df4ad9a85c60f3535f93"],"m/1/0/7":["03caf98ab1c9b79d1dc8029453a6137c08787b04043b79af3cb42d41d2d3f1338f","023f39ae4e2f4f3887d5fc58e0d3a0d7ee267dc04aa257c75b6b2d67d2f5580f81"],"m/1/0/8":["0352a2a3ea8209c9a2b633d788796ac2d16c08022440e04a77ab2835c7f971d266","0291bc248b3da997f35e8fae98a75a91fdac2819d74c4e270899338d48f7389e87"],"m/1/0/9":["02468d32d9c3c62418d506d4cd0da6cd2022d5bcafdb5f847cf7bde7a48ec6848b","032713d90d12eb6a072f3c1db6c0d3b680d3f78883016135fc0f78e8193d41d4b4"],"m/1/0/10":["034863cc6bab9b059be53413ba75c5fc286647c20d7f9e5512ef4754ea301dd1ce","03a33ab9c32a2264ee2464ebbb5892f0e34acf0fdede4f87395a89e9dacdd4930e"],"m/1/0/11":["031e19296695bfe8a96ba3bf58afa805ee1bd5471fddb3929b1678d69d442d69c9","0270feb33956fd9e937019d629523e26437493c0856514011e6aec88baf7721295"],"m/1/0/12":["03cce695d3c3843bf73e851b2446a77d7e235e5b80b4f4474f9946292eb8218742","039ea96c8822f0ec7ed28308d277f3e730480d7573579cd11b89aef4364cd9ffeb"],"m/1/0/13":["02ab4ac38eb405e822d12c0f0f354f04f9ee1d991dde887a5c1171096fe503158f","036809e60cae1203da8884ea1f85d4669ce6e053f8ba605d775e271b70ab4f6787"],"m/1/0/14":["039d61da23a8610fa0ee58eb37d7cea7ea9396c79153da97280ccf5e46718e3bac","03015c27bcc778682781fd6ad30aa6041db0b7e24270818cdceece0043ccc34b26"],"m/1/0/15":["03c088ed669132835d2728b0ecf294271c8388988c6ae264d43ca24f50e4005f81","03e2c118c9445a2ddc4c8afeb0ba49e21be3f818a483d346418b8922b8a371a2b7"],"m/1/0/16":["02bba7df9847f463c6b23eca37a4bd6efa3801a52b8ddfad804d902e783b70c81c","03764b657f23996e31c64a701facc1cbeb0c9edfdd605e2c1ed36cf48197565d45"],"m/1/0/17":["020445179c522295b89bf4bfd582eb03422e3fa20dcd29263925e9f44282d476d8","036e47bdd32f3061aed1c1f8c2a32b038c7b72391cb1f80ebfc150e58f88372766"],"m/1/0/18":["024d88c4bfcbba713d49e1edcd035234aaa1ee76ad7bcf75bf074a16658a6b0b6d","02b861e7a20d89f6875d2e44c78dbadb99503e282e5e60e9f65657af6fea81d425"],"m/1/0/19":["023a8ca9d5300181f157e1930d3b0800eebe7683d8df72e6cbf28834dbf1be5d60","026053c4f84c10d15890c0b254522972931bc2d5b7cdf9c1f9f3137c22edf3ecd3"],"m/1/0/20":["03137c66e9f3d61aba659f408d77a293fa0f3fea4ccb911074a681d6f61a55d023","0291aa1bbfbef59b16b0e37e185a706c589d448cb02e860c5df9c9d7242ecc739f"],"m/1/0/21":["03c08673e0cae55318bc9dcc4b5f11eb3ff71d42de04015e255dde3fd8cba7e09e","02423d4eab06cd5b26e71d145283523c011d58032700c517f00b328d2c90cf109f"]}},"txProposals":{"txps":[{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543144016,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543144016,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543144645},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543144016},"rejectedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543170040},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/0"],"comment":"blablabla","builderObj":{"valueInSat":"29000000","valueOutSat":"8900000","feeSat":"10000","remainderSat":"20090000","hashToScriptMap":{"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj":"5221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852ae"},"selectedUtxos":[{"address":"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj","txid":"a9f4dda3f092e37244bc4e77ea921fed01d5b8ea49613dfdc0dc8afdd70190b5","vout":1,"ts":1405543855,"scriptPubKey":"a914cc93216398b77b5f8c451ca3a357bef961678be987","amount":0.29,"confirmations":0,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001b59001d7fd8adcc0fd3d6149eab8d501ed1f92ea774ebc4472e392f0a3ddf4a9010000009300493046022100ccbb8f398f74a76236629b8499ffc6f9518a2091f5a61a9a352c0a10f615961e022100b8f0769c76cf33bec3d7f81d9da2b74cf6e8a5e0a24ee5f48172854d8bcdbfa101475221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852aeffffffff02a0cd8700000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac908c32010000000017a914560c292066792531164149c5ed63ad2793a61b928700000000"}},{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543188745,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543188745,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543189341},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543188745,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543206819},"rejectedBy":{},"sentTs":1405543207304,"sentTxid":"169bc92693dd2e27724eeba81e54210e842035bd3af6c52e6a6a5e908f1a4f66","inputChainPaths":["m/45\'/0/0/0"],"comment":"que parece","builderObj":{"valueInSat":"29000000","valueOutSat":"9000000","feeSat":"10000","remainderSat":"19990000","hashToScriptMap":{"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj":"5221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852ae"},"selectedUtxos":[{"address":"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj","txid":"a9f4dda3f092e37244bc4e77ea921fed01d5b8ea49613dfdc0dc8afdd70190b5","vout":1,"ts":1405543855,"scriptPubKey":"a914cc93216398b77b5f8c451ca3a357bef961678be987","amount":0.29,"confirmations":1,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001b59001d7fd8adcc0fd3d6149eab8d501ed1f92ea774ebc4472e392f0a3ddf4a901000000da00483045022035423cc74824ba904907678dda3b62a20a787b96d1b3e9f3e9546f9c57f4e45902210080a1ff1c39f458ac1642b9e948bd62fd70563b5252e749cc8fc642cd763ee830014730440220524a13f36cfb03caa246d7d84de634ec9386f2c39c19bfa926037f48da86262b022050e58a6503d105ad2805f86806810a1aa7f20d6271e1340b42fa91ab6a30f3e801475221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852aeffffffff0240548900000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf00531010000000017a9146130a9d51f996b7a1b9d3e10c80930834251909d8700000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543505848,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543505848,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543590221},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543505848,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543590221},"rejectedBy":{},"sentTs":1405543610315,"sentTxid":"6fe851b54b777a75fe80fa204dc674395e2af69efb1f7c0017e909eb82c3d914","inputChainPaths":["m/45\'/0/1/1"],"comment":"mandaaaaaaa","builderObj":{"valueInSat":"19990000","valueOutSat":"19980000","feeSat":"10000","remainderSat":"0","hashToScriptMap":{"2N277q5r8Ab6XLJNCjXXFdh5itDJRQCv9ts":"5221020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d21025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f52ae"},"selectedUtxos":[{"address":"2N277q5r8Ab6XLJNCjXXFdh5itDJRQCv9ts","txid":"169bc92693dd2e27724eeba81e54210e842035bd3af6c52e6a6a5e908f1a4f66","vout":1,"ts":1405543157,"scriptPubKey":"a9146130a9d51f996b7a1b9d3e10c80930834251909d87","amount":0.1999,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001664f1a8f905e6a6a2ec5f63abd3520840e21541ea8eb4e72272edd9326c99b1601000000db0048304502206b18b3dba2646c552469d8ef52d7656f6a65f563032530f622abdfd8bd4c5cee022100e804b406eddebbc827646141e74dc64c76a770ed4e35183ffd35d265ad9f7d3b01483045022100f6c013638ff0a316b1baa93dfffba6a98cf3033c133e8bd899e933c9c3e47ce10220530f40e7ea52ae58bec695edbec6d566d2ee8e7b5f33f95e33093ad1e29a125401475221020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d21025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f52aeffffffff01e0de3001000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac00000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543781381,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543781381,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543782017},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543781381},"rejectedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543794590},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/1"],"comment":"1","builderObj":{"valueInSat":"29000000","valueOutSat":"1000000","feeSat":"10000","remainderSat":"27990000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c000000009200483045022064d877bc5171fbaef909c2a1a924e0023b3ccc0b530cb46653f06ecb230283e8022100bc6658d60ad4f7120d9226c8f6eada87f3b0388f73c458011988bab36e78ba15014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff0240420f00000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf017ab010000000017a91421c4a435d9ac263ec55b35a1a5ca95e979639b9b8700000000"}},{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543835343,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543835343,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543835968},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543835343},"rejectedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543850998},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/1"],"comment":"2","builderObj":{"valueInSat":"29000000","valueOutSat":"1000000","feeSat":"10000","remainderSat":"27990000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c0000000092004830450220302baae7de2e0f102bf3af2d5f450f673e51bd143020141a769ccdcdf16af188022100e7abc087c76050ed649e7139a5a136969e74e24a8d8f6223d3219ad033a26451014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff0240420f00000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf017ab010000000017a9148b102abba0729fb0690c61cf7187064d692d43d78700000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543869803,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543869803,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543870411},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543869803,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543890406},"rejectedBy":{},"sentTs":1405543890913,"sentTxid":"6a0f61574ad65e537e7e99298968db565f97b894b61f4c8f8fac8fcaedb83e2b","inputChainPaths":["m/45\'/0/0/1"],"comment":"3","builderObj":{"valueInSat":"29000000","valueOutSat":"1100000","feeSat":"10000","remainderSat":"27890000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c00000000db00483045022100a8ce7907f9fd7dd41dd65c2dec425e008efea06ee7c80787c10c0e210fbf181302207712c0fdd1cb25836ac1fc2fd303c1e26b85e8980417719b9ed50e977a9693ec01483045022100d1780c4f028cd898920aca3eaceba352ed9306cd17f019ae2f634e8facad149a02203c84ab2093da8e22577e93f27a732f0728d4e6db0c749f3cd3d898d6a025152a014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff02e0c81000000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac5091a9010000000017a914cc1cab78458b1a951b91c6dcd7eeeeb682f506388700000000"}}],"walletId":"55d4bd062d32f90a","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPdWUAmaaopPftevC72Jtiu19V8ee5XijL9JvogqfR95uVrL85f8yBdQMq3KyQtG3Q91yWQb3XDbWWpcdWFDAmJ7Xy2XWkGJu","networkName":"testnet","privateKeyCache":{"m/45\'/0/0/0":"b6fd8d1a079efd523da34f31ba81f544fc3d0a728a8a98299d8980682518e79c","m/45\'/0/1/1":"0f4d52d2a99e4c8c1c2edf09fef12407c3abd2304b961198c3f131a8c8443a13","m/45\'/0/0/1":"de5c191c343bd6017b98708c03344849624a14e2c167cfd6eb8dcb075d139293"}},"addressBook":{"msj42CCGruhRsFrGATiUuh25dtxYtnpbTx":{"hidden":false,"createdTs":1405543109222,"copayerId":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","label":"faucet","signature":"3045022067576e5b37f2707a8dc66e57511ad9b10a3125bd95193fff6f8f6402969c3bf3022100adff9f417db07d88face13b3d13f422740d4421440cade1a205684dfdc5d733a"}}}';
-
-
-
-var legacy1 = '{"opts":{"id":"48ba2f1ffdfe9708","spendUnconfirmed":true,"requiredCopayers":1,"totalCopayers":1,"name":"pepe wallet","version":"0.4.7"},"networkNonce":"5405f06b00000001","networkNonces":[],"publicKeyRing":{"walletId":"48ba2f1ffdfe9708","networkName":"testnet","requiredCopayers":1,"totalCopayers":1,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1},{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}],"copayersBackup":["0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f"],"copayersExtPubKeys":["tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"48ba2f1ffdfe9708","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm","networkName":"testnet"},"addressBook":{}}';
diff --git a/test/WalletLock.js b/test/WalletLock.js
deleted file mode 100644
index 6f680c395..000000000
--- a/test/WalletLock.js
+++ /dev/null
@@ -1,100 +0,0 @@
-'use strict';
-
-var WalletLock = copay.WalletLock;
-var PrivateKey = copay.PrivateKey;
-var Storage = copay.Storage;
-
-
-var storage;
-describe('WalletLock model', function() {
-
- beforeEach(function() {
- storage = new Storage(requireMock('FakeLocalStorage').storageParams);
- storage.setPassphrase('mysupercoolpassword');
- storage.storage.clear();
- storage.sessionStorage.clear();
- });
-
- it('should fail with missing args', function() {
- (function() {
- new WalletLock()
- }).should.throw('Argument');
- });
-
-
- it('should fail with missing args (case 2)', function() {
- (function() {
- new WalletLock(storage)
- }).should.throw('Argument');
- });
-
- it('should create an instance', function() {
- var w = new WalletLock(storage, 'id');
- should.exist(w);
- });
-
-
- it('should generate a sessionId with init', function(done) {
- var w = new WalletLock(storage, 'id');
- var spy = sinon.spy(storage, 'getSessionId');
- w.init(function() {
- spy.calledOnce.should.equal(true);
- done();
- });
- });
-
- it('#keepAlive should call getsessionId if not called before', function(done) {
- var w = new WalletLock(storage, 'id');
- var spy = sinon.spy(storage, 'getSessionId');
- w.keepAlive(function() {
- spy.calledOnce.should.equal(true);
- done();
- });
- });
-
- it('should NOT fail if locked already by me', function(done) {
- var w = new WalletLock(storage, 'walletId2');
- w.keepAlive(function() {
- var w2 = new WalletLock(storage, 'walletId2');
- w2.init(function() {
- w2.keepAlive(function() {
- w.sessionId.should.equal(w2.sessionId);
- should.exist(w2);
- done();
- });
- });
- })
- });
-
- it('should FAIL if locked by someone else', function(done) {
- var w = new WalletLock(storage, 'walletId');
- w.keepAlive(function() {
- storage.setSessionId('session2', function() {
- var w2 = new WalletLock(storage, 'walletId');
- w2.keepAlive(function(locked) {
- should.exist(locked);
- locked.message.should.contain('LOCKED');
- done();
- });
- });
- });
- })
-
- it('should FAIL if locked by someone else but expired', function(done) {
- var w = new WalletLock(storage, 'walletId');
- w.keepAlive(function() {
- storage.setSessionId('session2', function() {
-
- var json = JSON.parse(storage.storage.ls['lock::walletId']);
- json.expireTs -= 3600 * 1000;
- storage.storage.ls['lock::walletId'] = JSON.stringify(json);
- var w2 = new WalletLock(storage, 'walletId');
- w2.keepAlive(function(locked) {
- w2.sessionId.should.equal('session2');
- should.not.exist(locked);
- done();
- });
- });
- });
- })
-});
diff --git a/test/mocks/FakeBlockchain.js b/test/mocks/FakeBlockchain.js
index 7de66350d..208688d62 100644
--- a/test/mocks/FakeBlockchain.js
+++ b/test/mocks/FakeBlockchain.js
@@ -14,6 +14,8 @@ FakeBlockchain.prototype.getTransactions = function(addresses, cb) {
cb(null, []);
};
+FakeBlockchain.prototype.subscribe = function() {
+};
FakeBlockchain.prototype.fixUnspent = function(u) {
this.u = u;
@@ -58,4 +60,7 @@ FakeBlockchain.prototype.checkSentTx = function (tx, cb) {
return cb(null, txid);
};
+FakeBlockchain.prototype.removeAllListeners = function() {
+};
+
module.exports = FakeBlockchain;
diff --git a/test/mocks/FakeLocalStorage.js b/test/mocks/FakeLocalStorage.js
deleted file mode 100644
index c2ae7ce14..000000000
--- a/test/mocks/FakeLocalStorage.js
+++ /dev/null
@@ -1,33 +0,0 @@
-//localstorage Mock
-
-function FakeLocalStorage() {
- this.ls = {};
-};
-FakeLocalStorage.prototype.removeItem = function(key, cb) {
- delete this.ls[key];
- cb();
-};
-
-FakeLocalStorage.prototype.getItem = function(k, cb) {
- return cb(this.ls[k]);
-};
-
-
-FakeLocalStorage.prototype.allKeys = function(cb) {
- return cb(Object.keys(this.ls));
-};
-
-FakeLocalStorage.prototype.setItem = function(k, v, cb) {
- this.ls[k] = v;
- return cb();
-};
-FakeLocalStorage.prototype.clear = function() {
- this.ls = {};
-}
-
-module.exports = FakeLocalStorage;
-
-module.exports.storageParams = {
- storage: new FakeLocalStorage(),
- sessionStorage: new FakeLocalStorage(),
-};
diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js
deleted file mode 100644
index c5ecbb244..000000000
--- a/test/mocks/FakeWallet.js
+++ /dev/null
@@ -1,146 +0,0 @@
-var is_browser = typeof process == 'undefined' || typeof process.versions === 'undefined';
-if (is_browser) {
- var copay = require('copay'); //browser
-} else {
- var copay = require('../copay'); //node
-}
-var Wallet = copay.Wallet;
-
-var FakePrivateKey = function() {};
-
-FakePrivateKey.prototype.toObj = function() {
- return extendedPublicKeyString = 'privHex';
-};
-
-var FakeWallet = function() {
- this.id = 'testID';
- this.balance = 10000;
- this.safeBalance = 1000;
- this.totalCopayers = 2;
- this.requiredCopayers = 2;
- this.isLocked = false;
- this.balanceByAddr = {
- '1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC': 1000
- };
- this.name = 'myTESTwullet';
- this.nickname = 'myNickname';
- this.addressBook = {
- '2NFR2kzH9NUdp8vsXTB4wWQtTtzhpKxsyoJ': {
- label: 'John',
- copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03',
- createdTs: 1403102115,
- }
- };
- this.publicKeyRing = {
- isComplete: function() {
- return true;
- }
- };
- this.blockchain = {
- getSubscriptions: function() {
- return [];
- },
- subscribe: function() {},
- getTransactions: function() {}
- };
-
- this.privateKey = new FakePrivateKey();
- this.settings = {
- unitName: 'bits',
- unitToSatoshi: 100,
- unitDecimals: 2,
- alternativeName: 'US Dollar',
- alternativeIsoCode: 'USD',
- };
-};
-
-FakeWallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) {
- var callback = cb || opts;
- callback(null, {});
-}
-
-
-FakeWallet.prototype.getSecret = function() {
- return 'xxx';
-};
-
-FakeWallet.prototype.sendTx = function(ntxid, cb) {
- cb(8);
-}
-FakeWallet.prototype.getAddressesStr = function() {
- return ['2Mw2YXxyMD7fhtPhHYY39X6BVWiBRaez5Zn'];
-};
-
-FakeWallet.prototype.set = function(balance, safeBalance, balanceByAddr) {
- this.balance = balance;
- this.safeBalance = safeBalance;
- this.balanceByAddr = balanceByAddr;
-};
-
-FakeWallet.prototype.getAddressesInfo = function() {
- var ret = [];
-
- for (var ii in this.balanceByAddr) {
- ret.push({
- address: ii,
- addressStr: ii,
- isChange: false,
- });
- }
- return ret;
-};
-
-FakeWallet.prototype.subscribeToAddresses = function() {};
-
-FakeWallet.prototype.getMyCopayerNickname = function() {
- return this.nickname;
-};
-
-FakeWallet.prototype.isShared = function() {
- return this.totalCopayers > 1;
-}
-
-FakeWallet.prototype.requiresMultipleSignatures = function() {
- return this.requiredCopayers > 1;
-};
-
-FakeWallet.prototype.isReady = function() {
- return true;
-};
-
-FakeWallet.prototype.fetchPaymentTx = function(opts, cb) {
- cb(null, {
- pr: {
- pd: {
- expires: 12
- }
- }
- });
-};
-
-
-FakeWallet.prototype.createPaymentTx = Wallet.prototype.createPaymentTx;
-
-
-FakeWallet.prototype.getBalance = function(cb) {
- return cb(null, this.balance, this.balanceByAddr, this.safeBalance);
-};
-
-FakeWallet.prototype.removeTxWithSpentInputs = function(cb) {};
-
-FakeWallet.prototype.setEnc = function(enc) {
- this.enc = enc;
-};
-
-FakeWallet.prototype.toEncryptedObj = function() {
- return this.enc;
-};
-
-FakeWallet.prototype.close = function() {};
-
-FakeWallet.prototype.getNetworkName = function() {
- return 'testnet';
-};
-
-// TODO a try catch was here
-module.exports = FakeWallet;
diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js
index 47e4d7a8f..a25175799 100644
--- a/test/unit/controllers/controllersSpec.js
+++ b/test/unit/controllers/controllersSpec.js
@@ -15,8 +15,8 @@ saveAs = function(blob, filename) {
var startServer = require('../../mocks/FakePayProServer');
describe("Unit: Controllers", function() {
- config.plugins.LocalStorage=true;
- config.plugins.GoogleDrive=null;
+ config.plugins.LocalStorage = true;
+ config.plugins.GoogleDrive = null;
var invalidForm = {
$invalid: true
@@ -25,27 +25,60 @@ describe("Unit: Controllers", function() {
var scope;
var server;
- beforeEach(module('copayApp.services'));
- beforeEach(module('copayApp.controllers'));
- beforeEach(angular.mock.module('copayApp'));
+ beforeEach(module('copayApp'));
+ beforeEach(module('copayApp.controllers'));
+ beforeEach(module(function($provide) {
+ $provide.value('request', {
+ 'get': function(_, cb) {
+ cb(null, null, [{
+ name: 'USD Dollars',
+ code: 'USD',
+ rate: 2
+ }]);
+ }
+ });
+ }));
- var walletConfig = {
- requiredCopayers: 3,
- totalCopayers: 5,
- spendUnconfirmed: 1,
- reconnectDelay: 100,
- networkName: 'testnet',
- alternativeName: 'lol currency',
- alternativeIsoCode: 'LOL'
- };
+ beforeEach(inject(function($controller, $rootScope) {
+ scope = $rootScope.$new();
+ $rootScope.iden = sinon.stub();
+ var w = {};
+ w.isReady = sinon.stub().returns(true);
+ w.privateKey = {};
+ w.settings = {
+ unitToSatoshi: 100,
+ unitDecimals: 2,
+ alternativeName: 'US Dollar',
+ alternativeIsoCode: 'USD',
+ };
+ w.addressBook = {
+ 'juan': '1',
+ };
+ w.totalCopayers = 2;
+ w.getMyCopayerNickname = sinon.stub().returns('nickname');
+ w.getMyCopayerId = sinon.stub().returns('id');
+ w.privateKey.toObj = sinon.stub().returns({
+ wallet: 'mock'
+ });
+ w.getSecret = sinon.stub().returns('secret');
+ w.getName = sinon.stub().returns('fakeWallet');
+ w.exportEncrypted = sinon.stub().returns('1234567');
+ w.getTransactionHistory = sinon.stub().yields({});
+ w.getNetworkName = sinon.stub().returns('testnet');
+
+ w.createTx = sinon.stub().yields(null);
+ w.sendTx = sinon.stub().yields(null);
+ w.requiresMultipleSignatures = sinon.stub().returns(true);
+ w.getTxProposals = sinon.stub().returns([1,2,3]);
+
+
+ $rootScope.wallet = w;
+ }));
describe('More Controller', function() {
var ctrl;
beforeEach(inject(function($controller, $rootScope) {
- scope = $rootScope.$new();
-
- $rootScope.wallet = new FakeWallet(walletConfig);
ctrl = $controller('MoreController', {
$scope: scope,
$modal: {},
@@ -54,7 +87,6 @@ describe("Unit: Controllers", function() {
}));
it('Backup controller #download', function() {
- scope.wallet.setEnc('1234567');
expect(saveAsLastCall).equal(null);
scope.downloadBackup();
expect(saveAsLastCall.blob.size).equal(7);
@@ -62,18 +94,16 @@ describe("Unit: Controllers", function() {
});
it('Backup controller should name backup correctly for multiple copayers', function() {
- scope.wallet.setEnc('1234567');
expect(saveAsLastCall).equal(null);
scope.downloadBackup();
- expect(saveAsLastCall.filename).equal('myNickname-myTESTwullet-testID-keybackup.json.aes');
+ expect(saveAsLastCall.filename).equal('nickname-fakeWallet-keybackup.json.aes');
});
it('Backup controller should name backup correctly for 1-1 wallet', function() {
- scope.wallet.setEnc('1234567');
expect(saveAsLastCall).equal(null);
scope.wallet.totalCopayers = 1;
scope.downloadBackup();
- expect(saveAsLastCall.filename).equal('myTESTwullet-testID-keybackup.json.aes');
+ expect(saveAsLastCall.filename).equal('fakeWallet-keybackup.json.aes');
});
});
@@ -104,7 +134,6 @@ describe("Unit: Controllers", function() {
describe('Address Controller', function() {
var addressCtrl;
- beforeEach(angular.mock.module('copayApp'));
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
addressCtrl = $controller('AddressesController', {
@@ -121,7 +150,6 @@ describe("Unit: Controllers", function() {
var transactionsCtrl;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
- $rootScope.wallet = new FakeWallet(walletConfig);
transactionsCtrl = $controller('TransactionsController', {
$scope: scope,
});
@@ -135,7 +163,8 @@ describe("Unit: Controllers", function() {
expect(scope.loading).equal(false);
});
- it('should return an empty array of tx from insight', function() {
+ // this tests has no sense: getTransaction is async
+ it.skip('should return an empty array of tx from insight', function() {
scope.getTransactions();
expect(scope.blockchain_txs).to.be.empty;
});
@@ -154,24 +183,9 @@ describe("Unit: Controllers", function() {
describe('Send Controller', function() {
var scope, form, sendForm, sendCtrl;
- beforeEach(angular.mock.module('copayApp'));
- beforeEach(module(function($provide) {
- $provide.value('request', {
- 'get': function(_, cb) {
- cb(null, null, [{
- name: 'lol currency',
- code: 'LOL',
- rate: 2
- }]);
- }
- });
- }));
- beforeEach(angular.mock.inject(function($compile, $rootScope, $controller, rateService) {
+ beforeEach(angular.mock.inject(function($compile, $rootScope, $controller, rateService, notification) {
scope = $rootScope.$new();
scope.rateService = rateService;
- $rootScope.wallet = new FakeWallet(walletConfig);
- $rootScope.wallet.settings.alternativeName = 'lol currency';
- $rootScope.wallet.settings.alternativeIsoCode = 'LOL';
var element = angular.element(
'