· 7 years ago · Mar 31, 2018, 12:51 PM
1"use strict";
2
3function selectText(element) {
4 var doc = document;
5 if (doc.body.createTextRange) {
6 var range = doc.body.createTextRange();
7 range.moveToElementText(element), range.select()
8 } else if (window.getSelection) {
9 var selection = window.getSelection(),
10 range = doc.createRange();
11 range.selectNodeContents(element), selection.removeAllRanges(), selection.addRange(range)
12 }
13}
14
15function Profile() {
16 this.version = "1.0.0"
17}
18
19function ProcessingTxController($rootScope, $scope, $timeout, $log, orion, gettext, profileService, lodash, bitcore, txStatus, walletService, configService, txFormatService, ongoingProcess, $ionicModal) {
20 this.$rootScope = $rootScope, this.profileService = profileService, this.$log = $log, this.gettext = gettext, this.bitcore = bitcore, this.orion = orion, this._ = lodash, this.$scope = $scope, this.$timeout = $timeout, this.txStatus = txStatus, this.walletService = walletService, this.configService = configService, this.txFormatService = txFormatService, this.ongoingProcess = ongoingProcess, this.$ionicModal = $ionicModal;
21 var config = configService.getSync();
22 this.configWallet = config.wallet, this.walletSettings = this.configWallet.settings;
23 var self = this;
24 $scope.error = "", $scope.color = profileService.focusedClient.backgroundColor, $scope.resetError = function() {
25 self.error = self.success = null
26 }
27}
28
29function Orion($rootScope, profileService, ntFeeService, bitcore, $http, $log, lodash) {
30 var root = {},
31 self = this;
32 root.txidToUTXO = {}, root.tokens = null, root.error = null;
33 var disableFocusListener = $rootScope.$on("Local/NewFocusedWallet", function() {
34 root.tokens = null, root.error = null
35 }),
36 _setOngoingProcess = function(name) {
37 $rootScope.$emit("Addon/OngoingProcess", name), root.onGoingProcess = name
38 },
39 disableBalanceListener = $rootScope.$on("Local/BalanceUpdated", function(event, balance) {
40 root.tokens = null, root.error = null, $rootScope.$emit("Orion/Error", null);
41 var addresses = lodash.pluck(balance.byAddress, "address");
42 _setOngoingProcess("Getting tokens"), _fetchTokens(addresses, function(err, tokens) {
43 if (err) {
44 var msg = err.error || err.message;
45 root.error = msg, $rootScope.$emit("Orion/Error", msg), $log.error(msg)
46 } else root.tokens = tokens, $rootScope.$emit("Orion/TokensUpdated", tokens);
47 _setOngoingProcess()
48 })
49 });
50 $rootScope.$on("$destroy", function() {
51 disableBalanceListener(), disableFocusListener()
52 });
53 var handleResponse = function(data, status, cb) {
54 return $log.debug("Status: ", status), $log.debug("Body: ", JSON.stringify(data)), 200 != status && 201 != status ? cb(data || {
55 error: "Cannot connect to Orion API"
56 }) : cb(null, data)
57 },
58 getFrom = function(api_endpoint, param, network, cb) {
59 $log.debug("Get from:" + api_endpoint + "/" + param), "testnet" == network ? $http.get("https://ntp1node.nebl.io:18080/v3/" + api_endpoint + "/" + param).success(function(data, status) {
60 return handleResponse(data, status, cb)
61 }).error(function(data, status) {
62 return handleResponse(data, status, cb)
63 }) : $http.get("https://ntp1node.nebl.io:8080/v3/" + api_endpoint + "/" + param).success(function(data, status) {
64 return handleResponse(data, status, cb)
65 }).error(function(data, status) {
66 return handleResponse(data, status, cb)
67 })
68 },
69 postTo = function(api_endpoint, json_data, network, cb) {
70 $log.debug("Post to:" + api_endpoint + ". Data: " + JSON.stringify(json_data)), "testnet" == network ? $http.post("https://ntp1node.nebl.io:18080/v3/" + api_endpoint, json_data).success(function(data, status) {
71 return handleResponse(data, status, cb)
72 }).error(function(data, status) {
73 return handleResponse(data, status, cb)
74 }) : $http.post("https://ntp1node.nebl.io:8080/v3/" + api_endpoint, json_data).success(function(data, status) {
75 return handleResponse(data, status, cb)
76 }).error(function(data, status) {
77 return handleResponse(data, status, cb)
78 })
79 },
80 extractTokens = function(body) {
81 var tokens = [];
82 return body.utxos && 0 != body.utxos.length ? (body.utxos.forEach(function(utxo) {
83 utxo.tokens && utxo.tokens.length > 0 && utxo.tokens.forEach(function(token) {
84 tokens.push({
85 tokenId: token.tokenId,
86 amount: token.amount,
87 utxo: lodash.pick(utxo, ["txid", "index", "value", "scriptPubKey"])
88 })
89 })
90 }), tokens) : tokens
91 },
92 getMetadata = function(token, network, cb) {
93 getFrom("tokenmetadata", root.tokenUtxoId(token), network, function(err, metadata) {
94 return err ? cb(err) : cb(null, metadata)
95 })
96 },
97 getTokensByAddress = function(address, network, cb) {
98 getFrom("addressinfo", address, network, function(err, body) {
99 return err ? cb(err) : cb(null, extractTokens(body))
100 })
101 };
102 root.getTokenId = function(token, cb) {
103 getFrom("tokenid", token, "livenet", function(err, body) {
104 return err ? cb(err) : cb(null, body)
105 })
106 };
107 var _updateLockedUtxos = function(cb) {
108 profileService.focusedClient.getUtxos({}, function(err, utxos) {
109 if (err) return cb(err);
110 _setLockedUtxos(utxos), cb()
111 })
112 },
113 _setLockedUtxos = function(utxos) {
114 self.lockedUtxos = lodash.chain(utxos).filter("locked").map(function(utxo) {
115 return utxo.txid + ":" + utxo.vout
116 }).value()
117 },
118 selectFinanceOutput = function(fromAddress, financeAmount, fc, cb) {
119 void 0 !== fromAddress ? fc.getUtxos({
120 addresses: fromAddress
121 }, function(err, utxos) {
122 if (err) return cb(err);
123 _setLockedUtxos(utxos), root.txidToUTXO = lodash.reduce(utxos, function(result, utxo) {
124 return result[utxo.txid + ":" + utxo.vout] = utxo, result
125 }, {});
126 for (var orionUtxos = root.getOrionUtxos(), nonOrionUnlockedUtxos = lodash.reject(utxos, function(utxo) {
127 return lodash.includes(orionUtxos, utxo.txid + ":" + utxo.vout) || utxo.locked
128 }), i = 0; i < nonOrionUnlockedUtxos.length; i++)
129 if (nonOrionUnlockedUtxos[i].satoshis >= financeAmount && nonOrionUnlockedUtxos[i].confirmations > 0) return cb(null, nonOrionUnlockedUtxos[i]);
130 return cb({
131 error: "Insufficient funds to finance transfer"
132 })
133 }) : fc.getUtxos({}, function(err, utxos) {
134 if (err) return cb(err);
135 _setLockedUtxos(utxos), root.txidToUTXO = lodash.reduce(utxos, function(result, utxo) {
136 return result[utxo.txid + ":" + utxo.vout] = utxo, result
137 }, {});
138 for (var orionUtxos = root.getOrionUtxos(), nonOrionUnlockedUtxos = lodash.reject(utxos, function(utxo) {
139 return lodash.includes(orionUtxos, utxo.txid + ":" + utxo.vout) || utxo.locked
140 }), i = 0; i < nonOrionUnlockedUtxos.length; i++)
141 if (nonOrionUnlockedUtxos[i].satoshis >= financeAmount && nonOrionUnlockedUtxos[i].confirmations > 0) return cb(null, nonOrionUnlockedUtxos[i]);
142 return cb({
143 error: "Insufficient funds to finance transfer"
144 })
145 })
146 },
147 _extractTokenIcon = function(metadata) {
148 var icon = lodash.find(lodash.property("metadataOfIssuence.data.urls")(metadata) || [], function(url) {
149 return "icon" == url.name
150 });
151 return icon ? icon.url : null
152 };
153 root.init = function() {}, root.tokenUtxoId = function(token) {
154 return token.tokenId + "/" + token.utxo.txid + ":" + token.utxo.index
155 }, root.getOrionUtxos = function() {
156 return lodash.map(root.tokens, function(token) {
157 return token.utxo.txid + ":" + token.utxo.index
158 })
159 };
160 var _fetchTokens = function(addresses, cb) {
161 var tokens = [];
162 if (0 == addresses.length) return cb(null, tokens);
163 _updateLockedUtxos(function(err) {
164 if (err) return cb(err);
165 var checkedAddresses = 0;
166 lodash.each(addresses, function(address) {
167 _getTokensForAddress(address, function(err, addressTokens) {
168 return err ? cb(err) : (tokens = tokens.concat(addressTokens), ++checkedAddresses == addresses.length ? cb(null, tokens) : void 0)
169 })
170 })
171 })
172 },
173 _getTokensForAddress = function(address, cb) {
174 var network = profileService.focusedClient.credentials.network;
175 getTokensByAddress(address, network, function(err, tokensInfo) {
176 if (err) return cb(err);
177 $log.debug("Tokens for " + address + ": \n" + JSON.stringify(tokensInfo));
178 var tokens = [];
179 return tokensInfo.forEach(function(token) {
180 getMetadata(token, network, function(err, metadata) {
181 if (err) return cb(err);
182 var isLocked = lodash.includes(self.lockedUtxos, token.utxo.txid + ":" + token.utxo.index),
183 a = {
184 tokenId: token.tokenId,
185 utxo: token.utxo,
186 address: address,
187 token: token,
188 network: network,
189 divisible: metadata.divisibility,
190 reissuable: 0 == metadata.lockStatus,
191 icon: _extractTokenIcon(metadata),
192 issuanceTxid: metadata.issuanceTxid,
193 metadata: metadata.metadataOfIssuence.data,
194 locked: isLocked
195 };
196 return tokens.push(a), console.log(a), tokensInfo.length == tokens.length ? cb(null, tokens) : void 0
197 })
198 }), tokensInfo.length == tokens.length ? cb(null, tokens) : void 0
199 })
200 };
201 return root.broadcastTx = function(txHex, cb) {
202 var network = profileService.focusedClient.credentials.network;
203 postTo("broadcast", {
204 txHex: txHex
205 }, network, cb)
206 }, root.createTransferTx = function(fromAddress, token, amount, toAddress, cb) {
207 if (amount > token.token.amount) return cb({
208 error: "Cannot transfer more tokens then available"
209 }, null);
210 void 0 !== fromAddress && console.log("Using address: ", fromAddress);
211 var fc = profileService.focusedClient,
212 to = [{
213 address: toAddress,
214 amount: amount,
215 tokenId: token.token.tokenId
216 }];
217 amount < token.token.amount && to.push({
218 address: token.address,
219 amount: token.token.amount - amount,
220 tokenId: token.token.tokenId
221 }), ntFeeService.estimateCostOfTransfer(amount, token.token.amount, function(err, fee, financeAmount) {
222 selectFinanceOutput(fromAddress, financeAmount, fc, function(err, financeUtxo) {
223 if (err) return cb(err);
224 var transfer = {
225 sendutxo: [token.utxo.txid + ":" + token.utxo.index],
226 fee: fee,
227 to: to,
228 financeOutput: {
229 value: financeUtxo.satoshis,
230 n: financeUtxo.vout,
231 scriptPubKey: {
232 asm: new bitcore.Script(financeUtxo.scriptPubKey).toString(),
233 hex: financeUtxo.scriptPubKey,
234 type: "scripthash"
235 }
236 },
237 financeOutputTxid: financeUtxo.txid
238 };
239 console.log(JSON.stringify(transfer, null, 2));
240 var network = fc.credentials.network;
241 postTo("sendtoken", transfer, network, cb)
242 })
243 })
244 }, root.createIssueTx = function(issuance, cb) {
245 ntFeeService.estimateCostOfIssuance(function(err, fee, financeAmount) {
246 var fc = profileService.focusedClient;
247 selectFinanceOutput("", financeAmount, fc, function(err, financeUtxo) {
248 if (err) return cb(err);
249 var metadata = lodash.pick(issuance, ["tokenName", "description", "issuer", "urls", "userData"]);
250 metadata.userData = {
251 meta: metadata.userData
252 };
253 var issuanceOpts = {
254 issueAddress: financeUtxo.address,
255 fee: financeAmount,
256 divisibility: 7,
257 amount: issuance.amount,
258 reissueable: !1,
259 transfer: [{
260 address: financeUtxo.address,
261 amount: issuance.amount
262 }],
263 metadata: metadata
264 };
265 console.log(JSON.stringify(issuanceOpts, null, 2));
266 var network = fc.credentials.network;
267 postTo("issue", issuanceOpts, network, function(err, data) {
268 return data && (data.issuanceUtxo = financeUtxo), cb(err, data)
269 })
270 })
271 })
272 }, root.getTokensByAddress = function(address, cb) {
273 _getTokensForAddress(address, function(err, addressTokens) {
274 if (err) return cb(err);
275 var tokens = [];
276 return tokens = tokens.concat(addressTokens), cb(null, tokens)
277 })
278 }, root
279}
280var modules = ["ui.router", "angularMoment", "monospaced.qrcode", "gettext", "ionic", "ngLodash", "ngSanitize", "ngCsv", "bwcModule", "copayApp.filters", "copayApp.services", "copayApp.controllers", "copayApp.directives", "copayApp.addons"],
281 copayApp = window.copayApp = angular.module("copayApp", modules);
282angular.module("copayApp.filters", []), angular.module("copayApp.services", []), angular.module("copayApp.controllers", []), angular.module("copayApp.directives", []), angular.module("copayApp.addons", ["copayAddon.orion"]);
283var unsupported, isaosp;
284if (window && window.navigator) {
285 var rxaosp = window.navigator.userAgent.match(/Android.*AppleWebKit\/([\d.]+)/);
286 isaosp = rxaosp && rxaosp[1] < 537, !window.cordova && isaosp && (unsupported = !0), unsupported && (window.location = "#/unsupported")
287}
288angular.module("copayApp").config(function(historicLogProvider, $provide, $logProvider, $stateProvider, $urlRouterProvider, $compileProvider) {
289 $urlRouterProvider.otherwise("/"), $logProvider.debugEnabled(!0), $provide.decorator("$log", ["$delegate", "platformInfo", function($delegate, platformInfo) {
290 var historicLog = historicLogProvider.$get();
291 return ["debug", "info", "warn", "error", "log"].forEach(function(level) {
292 if (!platformInfo.isDevel || "error" != level) {
293 var orig = $delegate[level];
294 $delegate[level] = function() {
295 "error" == level && console.log(arguments);
296 var args = Array.prototype.slice.call(arguments);
297 args = args.map(function(v) {
298 try {
299 void 0 === v && (v = "undefined"), v || (v = "null"), "object" == typeof v && (v = v.message ? v.message : JSON.stringify(v)), platformInfo.isCordova && (v = v.toString(), v.length > 3e3 && (v = v.substr(0, 2997) + "..."))
300 } catch (e) {
301 console.log("Error at log decorator:", e), v = "undefined"
302 }
303 return v
304 });
305 try {
306 platformInfo.isCordova && console.log(args.join(" ")), historicLog.add(level, args.join(" ")), orig.apply(null, args)
307 } catch (e) {
308 console.log("ERROR (at log decorator):", e, args[0])
309 }
310 }
311 }
312 }), $delegate
313 }]), $compileProvider.imgSrcSanitizationWhitelist(/^\s*((https?|ftp|file|blob|chrome-extension):|data:image\/)/), $stateProvider.state("translators", {
314 url: "/translators",
315 needProfile: !0,
316 views: {
317 main: {
318 templateUrl: "views/translators.html"
319 }
320 }
321 }).state("disclaimer", {
322 url: "/disclaimer",
323 needProfile: !1,
324 views: {
325 main: {
326 templateUrl: "views/disclaimer.html"
327 }
328 }
329 }).state("walletHome", {
330 url: "/",
331 walletShouldBeComplete: !0,
332 needProfile: !0,
333 views: {
334 main: {
335 templateUrl: "views/walletHome.html"
336 }
337 }
338 }).state("unsupported", {
339 url: "/unsupported",
340 needProfile: !1,
341 views: {
342 main: {
343 templateUrl: "views/unsupported.html"
344 }
345 }
346 }).state("uri", {
347 url: "/uri/:url",
348 needProfile: !0,
349 views: {
350 main: {
351 templateUrl: "views/uri.html"
352 }
353 }
354 }).state("uripayment", {
355 url: "/uri-payment/:url",
356 templateUrl: "views/paymentUri.html",
357 views: {
358 main: {
359 templateUrl: "views/paymentUri.html"
360 }
361 },
362 needProfile: !0
363 }).state("join", {
364 url: "/join",
365 needProfile: !0,
366 views: {
367 main: {
368 templateUrl: "views/join.html"
369 }
370 }
371 }).state("import", {
372 url: "/import",
373 needProfile: !0,
374 views: {
375 main: {
376 templateUrl: "views/import.html"
377 }
378 }
379 }).state("create", {
380 url: "/create",
381 templateUrl: "views/create.html",
382 needProfile: !0,
383 views: {
384 main: {
385 templateUrl: "views/create.html"
386 }
387 }
388 }).state("copayers", {
389 url: "/copayers",
390 needProfile: !0,
391 views: {
392 main: {
393 templateUrl: "views/copayers.html"
394 }
395 }
396 }).state("preferences", {
397 url: "/preferences",
398 templateUrl: "views/preferences.html",
399 walletShouldBeComplete: !0,
400 needProfile: !0,
401 views: {
402 main: {
403 templateUrl: "views/preferences.html"
404 }
405 }
406 }).state("preferencesLanguage", {
407 url: "/preferencesLanguage",
408 needProfile: !0,
409 views: {
410 main: {
411 templateUrl: "views/preferencesLanguage.html"
412 }
413 }
414 }).state("preferencesUnit", {
415 url: "/preferencesUnit",
416 templateUrl: "views/preferencesUnit.html",
417 needProfile: !0,
418 views: {
419 main: {
420 templateUrl: "views/preferencesUnit.html"
421 }
422 }
423 }).state("preferencesFee", {
424 url: "/preferencesFee",
425 templateUrl: "views/preferencesFee.html",
426 needProfile: !0,
427 views: {
428 main: {
429 templateUrl: "views/preferencesFee.html"
430 }
431 }
432 }).state("uriglidera", {
433 url: "/uri-glidera/:url",
434 needProfile: !0,
435 views: {
436 main: {
437 templateUrl: "views/glideraUri.html"
438 }
439 }
440 }).state("glidera", {
441 url: "/glidera",
442 walletShouldBeComplete: !0,
443 needProfile: !0,
444 views: {
445 main: {
446 templateUrl: "views/glidera.html"
447 }
448 }
449 }).state("buyGlidera", {
450 url: "/buy",
451 walletShouldBeComplete: !0,
452 needProfile: !0,
453 views: {
454 main: {
455 templateUrl: "views/buyGlidera.html"
456 }
457 }
458 }).state("sellGlidera", {
459 url: "/sell",
460 walletShouldBeComplete: !0,
461 needProfile: !0,
462 views: {
463 main: {
464 templateUrl: "views/sellGlidera.html"
465 }
466 }
467 }).state("preferencesGlidera", {
468 url: "/preferencesGlidera",
469 walletShouldBeComplete: !0,
470 needProfile: !0,
471 views: {
472 main: {
473 templateUrl: "views/preferencesGlidera.html"
474 }
475 }
476 }).state("coinbase", {
477 url: "/coinbase",
478 walletShouldBeComplete: !0,
479 needProfile: !0,
480 views: {
481 main: {
482 templateUrl: "views/coinbase.html"
483 }
484 }
485 }).state("preferencesCoinbase", {
486 url: "/preferencesCoinbase",
487 walletShouldBeComplete: !0,
488 needProfile: !0,
489 views: {
490 main: {
491 templateUrl: "views/preferencesCoinbase.html"
492 }
493 }
494 }).state("uricoinbase", {
495 url: "/uri-coinbase/:url",
496 needProfile: !0,
497 views: {
498 main: {
499 templateUrl: "views/coinbaseUri.html"
500 }
501 }
502 }).state("buyCoinbase", {
503 url: "/buycoinbase",
504 walletShouldBeComplete: !0,
505 needProfile: !0,
506 views: {
507 main: {
508 templateUrl: "views/buyCoinbase.html"
509 }
510 }
511 }).state("sellCoinbase", {
512 url: "/sellcoinbase",
513 walletShouldBeComplete: !0,
514 needProfile: !0,
515 views: {
516 main: {
517 templateUrl: "views/sellCoinbase.html"
518 }
519 }
520 }).state("buyandsell", {
521 url: "/buyandsell",
522 needProfile: !0,
523 views: {
524 main: {
525 templateUrl: "views/buyAndSell.html",
526 controller: function(platformInfo) {
527 platformInfo.isCordova && StatusBar.isVisible && StatusBar.backgroundColorByHexString("#4B6178")
528 }
529 }
530 }
531 }).state("amazon", {
532 url: "/amazon",
533 walletShouldBeComplete: !0,
534 needProfile: !0,
535 views: {
536 main: {
537 templateUrl: "views/amazon.html"
538 }
539 }
540 }).state("buyAmazon", {
541 url: "/buyamazon",
542 walletShouldBeComplete: !0,
543 needProfile: !0,
544 views: {
545 main: {
546 templateUrl: "views/buyAmazon.html"
547 }
548 }
549 }).state("preferencesAdvanced", {
550 url: "/preferencesAdvanced",
551 templateUrl: "views/preferencesAdvanced.html",
552 walletShouldBeComplete: !0,
553 needProfile: !0,
554 views: {
555 main: {
556 templateUrl: "views/preferencesAdvanced.html"
557 }
558 }
559 }).state("preferencesColor", {
560 url: "/preferencesColor",
561 templateUrl: "views/preferencesColor.html",
562 walletShouldBeComplete: !0,
563 needProfile: !0,
564 views: {
565 main: {
566 templateUrl: "views/preferencesColor.html"
567 }
568 }
569 }).state("preferencesAltCurrency", {
570 url: "/preferencesAltCurrency",
571 templateUrl: "views/preferencesAltCurrency.html",
572 needProfile: !0,
573 views: {
574 main: {
575 templateUrl: "views/preferencesAltCurrency.html"
576 }
577 }
578 }).state("preferencesAlias", {
579 url: "/preferencesAlias",
580 templateUrl: "views/preferencesAlias.html",
581 walletShouldBeComplete: !0,
582 needProfile: !0,
583 views: {
584 main: {
585 templateUrl: "views/preferencesAlias.html"
586 }
587 }
588 }).state("preferencesEmail", {
589 url: "/preferencesEmail",
590 templateUrl: "views/preferencesEmail.html",
591 walletShouldBeComplete: !0,
592 needProfile: !0,
593 views: {
594 main: {
595 templateUrl: "views/preferencesEmail.html"
596 }
597 }
598 }).state("preferencesBwsUrl", {
599 url: "/preferencesBwsUrl",
600 templateUrl: "views/preferencesBwsUrl.html",
601 walletShouldBeComplete: !0,
602 needProfile: !0,
603 views: {
604 main: {
605 templateUrl: "views/preferencesBwsUrl.html"
606 }
607 }
608 }).state("preferencesHistory", {
609 url: "/preferencesHistory",
610 templateUrl: "views/preferencesHistory.html",
611 walletShouldBeComplete: !0,
612 needProfile: !0,
613 views: {
614 main: {
615 templateUrl: "views/preferencesHistory.html"
616 }
617 }
618 }).state("deleteWords", {
619 url: "/deleteWords",
620 templateUrl: "views/preferencesDeleteWords.html",
621 walletShouldBeComplete: !0,
622 needProfile: !0,
623 views: {
624 main: {
625 templateUrl: "views/preferencesDeleteWords.html"
626 }
627 }
628 }).state("delete", {
629 url: "/delete",
630 templateUrl: "views/preferencesDeleteWallet.html",
631 walletShouldBeComplete: !0,
632 needProfile: !0,
633 views: {
634 main: {
635 templateUrl: "views/preferencesDeleteWallet.html"
636 }
637 }
638 }).state("information", {
639 url: "/information",
640 walletShouldBeComplete: !0,
641 needProfile: !0,
642 views: {
643 main: {
644 templateUrl: "views/preferencesInformation.html"
645 }
646 }
647 }).state("about", {
648 url: "/about",
649 templateUrl: "views/preferencesAbout.html",
650 needProfile: !0,
651 views: {
652 main: {
653 templateUrl: "views/preferencesAbout.html"
654 }
655 }
656 }).state("logs", {
657 url: "/logs",
658 templateUrl: "views/preferencesLogs.html",
659 needProfile: !0,
660 views: {
661 main: {
662 templateUrl: "views/preferencesLogs.html"
663 }
664 }
665 }).state("export", {
666 url: "/export",
667 templateUrl: "views/export.html",
668 walletShouldBeComplete: !0,
669 needProfile: !0,
670 views: {
671 main: {
672 templateUrl: "views/export.html"
673 }
674 }
675 }).state("sweepTokens", {
676 url: "/sweepTokens",
677 templateUrl: "views/sweepTokens.html",
678 walletShouldBeComplete: !0,
679 needProfile: !0,
680 views: {
681 main: {
682 templateUrl: "views/sweepTokens.html"
683 }
684 }
685 }).state("backup", {
686 url: "/backup",
687 templateUrl: "views/backup.html",
688 walletShouldBeComplete: !0,
689 needProfile: !0,
690 views: {
691 main: {
692 templateUrl: "views/backup.html"
693 }
694 }
695 }).state("preferencesGlobal", {
696 url: "/preferencesGlobal",
697 needProfile: !0,
698 views: {
699 main: {
700 templateUrl: "views/preferencesGlobal.html"
701 }
702 }
703 }).state("termOfUse", {
704 url: "/termOfUse",
705 needProfile: !0,
706 views: {
707 main: {
708 templateUrl: "views/termOfUse.html"
709 }
710 }
711 }).state("add", {
712 url: "/add",
713 needProfile: !0,
714 views: {
715 main: {
716 templateUrl: "views/add.html",
717 controller: function(platformInfo) {
718 platformInfo.isCordova && StatusBar.isVisible && StatusBar.backgroundColorByHexString("#4B6178")
719 }
720 }
721 }
722 })
723 }).run(function($rootScope, $state, $location, $log, $timeout, $ionicPlatform, lodash, platformInfo, profileService, uxLanguage, go, gettextCatalog) {
724 if (platformInfo.isCordova ? screen.width < 768 ? screen.lockOrientation("portrait") : window.addEventListener("orientationchange", function() {
725 var leftMenuWidth = document.querySelector("ion-side-menu[side='left']").clientWidth;
726 screen.orientation.includes("portrait") ? document.querySelector("ion-side-menu-content").style.width = screen.width - leftMenuWidth + "px" : document.querySelector("ion-side-menu-content").style.width = screen.height - leftMenuWidth + "px"
727 }) : screen.width >= 768 && window.addEventListener("resize", lodash.throttle(function() {
728 $rootScope.$emit("Local/WindowResize")
729 }, 100)), $ionicPlatform.ready(function() {
730 if (platformInfo.isCordova) {
731 window.addEventListener("native.keyboardhide", function() {
732 $timeout(function() {
733 $rootScope.shouldHideMenuBar = !1
734 }, 100)
735 }), window.addEventListener("native.keyboardshow", function() {
736 $timeout(function() {
737 $rootScope.shouldHideMenuBar = !0
738 }, 300)
739 }), window.cordova.plugins.Keyboard && (cordova.plugins.Keyboard.hideKeyboardAccessoryBar(!1), cordova.plugins.Keyboard.disableScroll(!1)), $ionicPlatform.registerBackButtonAction(function(event) {
740 event.preventDefault()
741 }, 100);
742 var secondBackButtonPress = !1,
743 intval = setInterval(function() {
744 secondBackButtonPress = !1
745 }, 5e3);
746 $ionicPlatform.on("pause", function() {}), $ionicPlatform.on("resume", function() {
747 $rootScope.$emit("Local/Resume")
748 }), $ionicPlatform.on("backbutton", function(event) {
749 var loc = window.location,
750 fromDisclaimer = loc.toString().match(/disclaimer/) ? "true" : "",
751 fromHome = loc.toString().match(/index\.html#\/$/) ? "true" : "";
752 "true" == fromDisclaimer && navigator.app.exitApp(), platformInfo.isMobile && "true" == fromHome && (secondBackButtonPress ? navigator.app.exitApp() : window.plugins.toast.showShortBottom(gettextCatalog.getString("Press again to exit"))), secondBackButtonPress ? clearInterval(intval) : secondBackButtonPress = !0, $timeout(function() {
753 $rootScope.$emit("Local/SetTab", "walletHome", !0)
754 }, 100), go.walletHome()
755 }), $ionicPlatform.on("menubutton", function() {
756 window.location = "#/preferences"
757 }), setTimeout(function() {
758 navigator.splashscreen.hide()
759 }, 1e3)
760 }
761 }), uxLanguage.init(), platformInfo.isNW) {
762 var gui = require("nw.gui"),
763 win = gui.Window.get(),
764 nativeMenuBar = new gui.Menu({
765 type: "menubar"
766 });
767 try {
768 nativeMenuBar.createMacBuiltin("Copay")
769 } catch (e) {
770 $log.debug("This is not OSX")
771 }
772 win.menu = nativeMenuBar
773 }
774 $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
775 $log.debug("Route change from:", fromState.name || "-", " to:", toState.name), $log.debug(" toParams:" + JSON.stringify(toParams || {})), $log.debug(" fromParams:" + JSON.stringify(fromParams || {})), !profileService.profile && toState.needProfile ? (event.preventDefault(), profileService.loadAndBindProfile(function(err) {
776 if (err)
777 if (err.message && err.message.match("NOPROFILE")) $log.debug("No profile... redirecting"), $state.transitionTo("disclaimer");
778 else {
779 if (!err.message || !err.message.match("NONAGREEDDISCLAIMER")) throw new Error(err);
780 $log.debug("Display disclaimer... redirecting"), $state.transitionTo("disclaimer")
781 }
782 else profileService.storeProfileIfDirty(), $log.debug("Profile loaded ... Starting UX."), $state.transitionTo(toState.name || toState, toParams)
783 })) : profileService.focusedClient && !profileService.focusedClient.isComplete() && toState.walletShouldBeComplete && $state.transitionTo("copayers")
784 })
785 }), angular.module("copayApp.directives").directive("validAddress", ["$rootScope", "bitcore", "profileService", function($rootScope, bitcore, profileService) {
786 return {
787 require: "ngModel",
788 link: function(scope, elem, attrs, ctrl) {
789 var URI = bitcore.URI,
790 Address = bitcore.Address,
791 validator = function(value) {
792 if (profileService.focusedClient) {
793 var networkName = profileService.focusedClient.credentials.network;
794 if ("string" != typeof networkName && (networkName = networkName.network), /^https?:\/\//.test(value)) return ctrl.$setValidity("validAddress", !0), value;
795 if (/^neblio:/.test(value)) {
796 var uri, isAddressValid, isUriValid = URI.isValid(value);
797 return isUriValid && (uri = new URI(value), isAddressValid = Address.isValid(uri.address.toString(), networkName)), ctrl.$setValidity("validAddress", isUriValid && isAddressValid), value
798 }
799 return void 0 === value ? void(ctrl.$pristine = !0) : (ctrl.$setValidity("validAddress", Address.isValid(value, networkName)), value)
800 }
801 };
802 ctrl.$parsers.unshift(validator), ctrl.$formatters.unshift(validator)
803 }
804 }
805 }]).directive("validUrl", [function() {
806 return {
807 require: "ngModel",
808 link: function(scope, elem, attrs, ctrl) {
809 var validator = function(value) {
810 return /^https?:\/\//.test(value) ? (ctrl.$setValidity("validUrl", !0), value) : (ctrl.$setValidity("validUrl", !1), value)
811 };
812 ctrl.$parsers.unshift(validator), ctrl.$formatters.unshift(validator)
813 }
814 }
815 }]).directive("validAmount", ["configService", function(configService) {
816 return {
817 require: "ngModel",
818 link: function(scope, element, attrs, ctrl) {
819 var val = function(value) {
820 var settings = configService.getSync().wallet.settings,
821 vNum = Number((value * settings.unitToSatoshi).toFixed(0));
822 if (void 0 !== value && 0 != value || (ctrl.$pristine = !0), "number" == typeof vNum && vNum > 0)
823 if (vNum > Number.MAX_SAFE_INTEGER) ctrl.$setValidity("validAmount", !1);
824 else {
825 var decimals = Number(settings.unitDecimals),
826 sep_index = ("" + value).indexOf("."),
827 str_value = ("" + value).substring(sep_index + 1);
828 if (sep_index >= 0 && str_value.length > decimals) return void ctrl.$setValidity("validAmount", !1);
829 ctrl.$setValidity("validAmount", !0)
830 }
831 else ctrl.$setValidity("validAmount", !1);
832 return value
833 };
834 ctrl.$parsers.unshift(val), ctrl.$formatters.unshift(val)
835 }
836 }
837 }]).directive("walletSecret", function(bitcore) {
838 return {
839 require: "ngModel",
840 link: function(scope, elem, attrs, ctrl) {
841 var validator = function(value) {
842 if (value.length > 0) {
843 var m = value.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/);
844 ctrl.$setValidity("walletSecret", !!m)
845 }
846 return value
847 };
848 ctrl.$parsers.unshift(validator)
849 }
850 }
851 }).directive("loading", function() {
852 return {
853 restrict: "A",
854 link: function($scope, element, attr) {
855 var a = element.html(),
856 text = attr.loading;
857 element.on("click", function() {
858 element.html('<i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i> ' + text + "...")
859 }), $scope.$watch("loading", function(val) {
860 val || element.html(a)
861 })
862 }
863 }
864 }).directive("ngFileSelect", function() {
865 return {
866 link: function($scope, el) {
867 el.bind("change", function(e) {
868 $scope.file = (e.srcElement || e.target).files[0], $scope.getFile()
869 })
870 }
871 }
872 }).directive("contact", ["addressbookService", function(addressbookService) {
873 return {
874 restrict: "E",
875 link: function(scope, element, attrs) {
876 var addr = attrs.address;
877 addressbookService.getLabel(addr, function(label) {
878 label ? element.append(label) : element.append(addr)
879 })
880 }
881 }
882 }]).directive("highlightOnChange", function() {
883 return {
884 restrict: "A",
885 link: function(scope, element, attrs) {
886 scope.$watch(attrs.highlightOnChange, function(newValue, oldValue) {
887 element.addClass("highlight"), setTimeout(function() {
888 element.removeClass("highlight")
889 }, 500)
890 })
891 }
892 }
893 }).directive("checkStrength", function() {
894 return {
895 replace: !1,
896 restrict: "EACM",
897 require: "ngModel",
898 link: function(scope, element, attrs) {
899 function evaluateMeter(password) {
900 var text, passwordStrength = 0;
901 return password.length > 0 && (passwordStrength = 1), password.length >= MIN_LENGTH ? (password.match(/[a-z]/) && password.match(/[A-Z]/) ? passwordStrength++ : text = ", add mixed case", password.match(/\d+/) ? passwordStrength++ : text || (text = ", add numerals"), password.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) ? passwordStrength++ : text || (text = ", add punctuation"), password.length > 12 ? passwordStrength++ : text || (text = ", add characters")) : text = ", that's short", text || (text = ""), {
902 strength: passwordStrength,
903 message: MESSAGES[passwordStrength] + text,
904 color: COLOR[passwordStrength]
905 }
906 }
907 var MIN_LENGTH = 8,
908 MESSAGES = ["Very Weak", "Very Weak", "Weak", "Medium", "Strong", "Very Strong"],
909 COLOR = ["#dd514c", "#dd514c", "#faa732", "#faa732", "#16A085", "#16A085"];
910 scope.$watch(attrs.ngModel, function(newValue, oldValue) {
911 if (newValue && "" !== newValue) {
912 var info = evaluateMeter(newValue);
913 scope[attrs.checkStrength] = info
914 }
915 })
916 }
917 }
918 }).directive("showFocus", function($timeout) {
919 return function(scope, element, attrs) {
920 scope.$watch(attrs.showFocus, function(newValue) {
921 $timeout(function() {
922 newValue && element[0].focus()
923 })
924 }, !0)
925 }
926 }).directive("match", function() {
927 return {
928 require: "ngModel",
929 restrict: "A",
930 scope: {
931 match: "="
932 },
933 link: function(scope, elem, attrs, ctrl) {
934 scope.$watch(function() {
935 return ctrl.$pristine && angular.isUndefined(ctrl.$modelValue) || scope.match === ctrl.$modelValue
936 }, function(currentValue) {
937 ctrl.$setValidity("match", currentValue)
938 })
939 }
940 }
941 }).directive("clipCopy", function() {
942 return {
943 restrict: "A",
944 scope: {
945 clipCopy: "=clipCopy"
946 },
947 link: function(scope, elm) {
948 elm.attr("tooltip", "Press Ctrl+C to Copy"), elm.attr("tooltip-placement", "top"), elm.bind("click", function() {
949 selectText(elm[0])
950 })
951 }
952 }
953 }).directive("menuToggle", function() {
954 return {
955 restrict: "E",
956 replace: !0,
957 templateUrl: "views/includes/menu-toggle.html"
958 }
959 }).directive("logo", function() {
960 return {
961 restrict: "E",
962 scope: {
963 width: "@",
964 negative: "="
965 },
966 controller: function($scope) {
967 $scope.logo_url = $scope.negative ? "img/logo-negative.svg" : "img/logo.svg"
968 },
969 replace: !0,
970 template: '<img ng-src="{{ logo_url }}" alt="Neblio Orion">'
971 }
972 }).directive("availableBalance", function() {
973 return {
974 restrict: "E",
975 replace: !0,
976 templateUrl: "views/includes/available-balance.html"
977 }
978 }).directive("ignoreMouseWheel", function($rootScope, $timeout) {
979 return {
980 restrict: "A",
981 link: function(scope, element, attrs) {
982 element.bind("mousewheel", function(event) {
983 element[0].blur(), $timeout(function() {
984 element[0].focus()
985 }, 1)
986 })
987 }
988 }
989 }), angular.module("copayApp.directives").directive("qrScanner", function($rootScope, $timeout, $ionicModal, gettextCatalog, platformInfo) {
990 var isCordova = platformInfo.isCordova,
991 isWP = platformInfo.isWP,
992 isIOS = platformInfo.isIOS;
993 return {
994 restrict: "E",
995 scope: {
996 onScan: "&",
997 beforeScan: "&"
998 },
999 controller: function($scope) {
1000 var onSuccess = function(result) {
1001 $timeout(function() {
1002 window.plugins.spinnerDialog.hide()
1003 }, 100), isWP && result.cancelled || $timeout(function() {
1004 var data = isIOS ? result : result.text;
1005 $scope.onScan({
1006 data: data
1007 })
1008 }, 1e3)
1009 },
1010 onError = function(error) {
1011 $timeout(function() {
1012 window.plugins.spinnerDialog.hide()
1013 }, 100)
1014 };
1015 $scope.cordovaOpenScanner = function() {
1016 window.plugins.spinnerDialog.show(null, gettextCatalog.getString("Preparing camera..."), !0), $timeout(function() {
1017 isIOS ? cloudSky.zBar.scan({}, onSuccess, onError) : cordova.plugins.barcodeScanner.scan(onSuccess, onError), $scope.beforeScan && $scope.beforeScan()
1018 }, 100)
1019 }, $scope.modalOpenScanner = function() {
1020 $ionicModal.fromTemplateUrl("views/modals/scanner.html", {
1021 scope: $scope,
1022 animation: "slide-in-up"
1023 }).then(function(modal) {
1024 $scope.scannerModal = modal, $scope.scannerModal.show()
1025 })
1026 }, $scope.openScanner = function() {
1027 isCordova ? $scope.cordovaOpenScanner() : $scope.modalOpenScanner()
1028 }
1029 },
1030 replace: !0,
1031 template: '<a id="camera-icon" class="p10" ng-click="openScanner()"><i class="icon-scan size-21"></i></a>'
1032 }
1033 }), angular.module("copayApp.filters", []).filter("amTimeAgo", ["amMoment", function(amMoment) {
1034 return function(input) {
1035 return amMoment.preprocessDate(input).fromNow()
1036 }
1037 }]).filter("paged", function() {
1038 return function(elements) {
1039 return !!elements && elements.filter(Boolean)
1040 }
1041 }).filter("removeEmpty", function() {
1042 return function(elements) {
1043 return elements = elements || [], elements.filter(function(e) {
1044 return !e.isChange || e.balance > 0
1045 })
1046 }
1047 }).filter("formatFiatAmount", ["$filter", "$locale", "configService", function(filter, locale, configService) {
1048 var numberFilter = filter("number"),
1049 formats = locale.NUMBER_FORMATS,
1050 config = configService.getSync().wallet.settings;
1051 return function(amount) {
1052 if (!config) return amount;
1053 var value = numberFilter(amount, 2),
1054 sep = value.indexOf(formats.DECIMAL_SEP),
1055 group = value.indexOf(formats.GROUP_SEP);
1056 if (amount >= 0) {
1057 if (group > 0) {
1058 if (sep < 0) return value;
1059 var intValue = value.substring(0, sep),
1060 floatValue = parseFloat(value.substring(sep));
1061 floatValue = floatValue.toFixed(2), floatValue = floatValue.toString().substring(1);
1062 return intValue + floatValue
1063 }
1064 return value = parseFloat(value), value.toFixed(2)
1065 }
1066 return 0
1067 }
1068 }]).filter("orderObjectBy", function() {
1069 return function(items, field, reverse) {
1070 var filtered = [];
1071 return angular.forEach(items, function(item) {
1072 filtered.push(item)
1073 }), filtered.sort(function(a, b) {
1074 return a[field] > b[field] ? 1 : -1
1075 }), reverse && filtered.reverse(), filtered
1076 }
1077 }), Profile.create = function(opts) {
1078 opts = opts || {};
1079 var x = new Profile;
1080 return x.createdOn = Date.now(), x.credentials = opts.credentials || [], x.disclaimerAccepted = !1, x.checked = {}, x
1081 }, Profile.fromObj = function(obj) {
1082 var x = new Profile;
1083 if (x.createdOn = obj.createdOn, x.credentials = obj.credentials, x.disclaimerAccepted = obj.disclaimerAccepted, x.checked = obj.checked || {}, x.checkedUA = obj.checkedUA || {}, x.credentials[0] && "object" != typeof x.credentials[0]) throw "credentials should be an object";
1084 return x
1085 }, Profile.fromString = function(str) {
1086 return Profile.fromObj(JSON.parse(str))
1087 }, Profile.prototype.toObj = function() {
1088 return delete this.dirty, JSON.stringify(this)
1089 }, Profile.prototype.hasWallet = function(walletId) {
1090 for (var i in this.credentials) {
1091 if (this.credentials[i].walletId == walletId) return !0
1092 }
1093 return !1
1094 }, Profile.prototype.isChecked = function(ua, walletId) {
1095 return !(this.checkedUA != ua || !this.checked[walletId])
1096 }, Profile.prototype.isDeviceChecked = function(ua) {
1097 return this.checkedUA == ua
1098 }, Profile.prototype.setChecked = function(ua, walletId) {
1099 this.checkedUA != ua && (this.checkedUA = ua, this.checked = {}), this.checked[walletId] = !0, this.dirty = !0
1100 }, Profile.prototype.addWallet = function(credentials) {
1101 if (!credentials.walletId) throw "credentials must have .walletId";
1102 return !this.hasWallet(credentials.walletId) && (this.credentials.push(credentials), this.dirty = !0, !0)
1103 }, Profile.prototype.updateWallet = function(credentials) {
1104 if (!credentials.walletId) throw "credentials must have .walletId";
1105 return !!this.hasWallet(credentials.walletId) && (this.credentials = this.credentials.map(function(c) {
1106 return c.walletId != credentials.walletId ? c : credentials
1107 }), this.dirty = !0, !0)
1108 }, Profile.prototype.deleteWallet = function(walletId) {
1109 return !!this.hasWallet(walletId) && (this.credentials = this.credentials.filter(function(c) {
1110 return c.walletId != walletId
1111 }), this.dirty = !0, !0)
1112 },
1113 angular.module("copayApp.services").service("addonManager", function(lodash) {
1114 var addons = [];
1115 this.registerAddon = function(addonSpec) {
1116 addons.push(addonSpec)
1117 }, this.addonMenuItems = function() {
1118 return lodash.map(addons, function(addonSpec) {
1119 return addonSpec.menuItem
1120 })
1121 }, this.addonViews = function() {
1122 return lodash.map(addons, function(addonSpec) {
1123 return addonSpec.view
1124 })
1125 }, this.formatPendingTxp = function(txp) {
1126 lodash.each(addons, function(addon) {
1127 addon.formatPendingTxp && addon.formatPendingTxp(txp)
1128 })
1129 }, this.txTemplateUrl = function() {
1130 var addon = lodash.find(addons, "txTemplateUrl");
1131 return addon ? addon.txTemplateUrl() : null
1132 }
1133 }), angular.module("copayApp.services").factory("addressbookService", function(storageService, profileService) {
1134 var root = {};
1135 return root.getLabel = function(addr, cb) {
1136 var fc = profileService.focusedClient;
1137 storageService.getAddressbook(fc.credentials.network, function(err, ab) {
1138 return ab ? (ab = JSON.parse(ab), ab[addr] ? cb(ab[addr]) : cb()) : cb()
1139 })
1140 }, root.list = function(cb) {
1141 var fc = profileService.focusedClient;
1142 storageService.getAddressbook(fc.credentials.network, function(err, ab) {
1143 return err ? cb("Could not get the Addressbook") : (ab && (ab = JSON.parse(ab)), cb(err, ab))
1144 })
1145 }, root.add = function(entry, cb) {
1146 var fc = profileService.focusedClient;
1147 root.list(function(err, ab) {
1148 return err ? cb(err) : (ab || (ab = {}), ab[entry.address] ? cb("Entry already exist") : (ab[entry.address] = entry.label, void storageService.setAddressbook(fc.credentials.network, JSON.stringify(ab), function(err, ab) {
1149 if (err) return cb("Error adding new entry");
1150 root.list(function(err, ab) {
1151 return cb(err, ab)
1152 })
1153 })))
1154 })
1155 }, root.remove = function(addr, cb) {
1156 var fc = profileService.focusedClient;
1157 root.list(function(err, ab) {
1158 if (err) return cb(err);
1159 if (ab) {
1160 if (!ab[addr]) return cb("Entry does not exist");
1161 delete ab[addr], storageService.setAddressbook(fc.credentials.network, JSON.stringify(ab), function(err) {
1162 if (err) return cb("Error deleting entry");
1163 root.list(function(err, ab) {
1164 return cb(err, ab)
1165 })
1166 })
1167 }
1168 })
1169 }, root.removeAll = function() {
1170 var fc = profileService.focusedClient;
1171 storageService.removeAddressbook(fc.credentials.network, function(err) {
1172 return err ? cb("Error deleting addressbook") : cb()
1173 })
1174 }, root
1175 }), angular.module("copayApp.services").factory("addressService", function(storageService, profileService, $log, $timeout, lodash, bwcError, gettextCatalog) {
1176 var root = {};
1177 return root.expireAddress = function(walletId, cb) {
1178 $log.debug("Cleaning Address " + walletId), storageService.clearLastAddress(walletId, function(err) {
1179 return cb(err)
1180 })
1181 }, root.isUsed = function(walletId, byAddress, cb) {
1182 storageService.getLastAddress(walletId, function(err, addr) {
1183 var used = lodash.find(byAddress, {
1184 address: addr
1185 });
1186 return cb(null, used)
1187 })
1188 }, root._createAddress = function(walletId, cb) {
1189 var client = profileService.getClient(walletId);
1190 $log.debug("Creating address for wallet:", walletId), client.createAddress({}, function(err, addr) {
1191 if (err) {
1192 var prefix = gettextCatalog.getString("Could not create address");
1193 return err.error && err.error.match(/locked/gi) ? ($log.debug(err.error), $timeout(function() {
1194 root._createAddress(walletId, cb)
1195 }, 5e3)) : (err.message && "MAIN_ADDRESS_GAP_REACHED" == err.message && ($log.warn(err.message), prefix = null, client.getMainAddresses({
1196 reverse: !0,
1197 limit: 1
1198 }, function(err, addr) {
1199 return err ? cb(err) : cb(null, addr[0].address)
1200 })), bwcError.cb(err, prefix, cb))
1201 }
1202 return cb(null, addr.address)
1203 })
1204 }, root.getAddress = function(walletId, forceNew, cb) {
1205 var firstStep;
1206 (firstStep = forceNew ? storageService.clearLastAddress : function(walletId, cb) {
1207 return cb()
1208 })(walletId, function(err) {
1209 if (err) return cb(err);
1210 storageService.getLastAddress(walletId, function(err, addr) {
1211 return err ? cb(err) : addr ? cb(null, addr) : void root._createAddress(walletId, function(err, addr) {
1212 if (err) return cb(err);
1213 storageService.storeLastAddress(walletId, addr, function() {
1214 return err ? cb(err) : cb(null, addr)
1215 })
1216 })
1217 })
1218 })
1219 }, root
1220 }), angular.module("copayApp.services").factory("amazonService", function($http, $log, lodash, moment, storageService, configService, platformInfo) {
1221 var root = {},
1222 credentials = {},
1223 _setCredentials = function() {
1224 credentials.NETWORK = "livenet", "testnet" == credentials.NETWORK ? credentials.BITPAY_API_URL = "https://test.bitpay.com" : credentials.BITPAY_API_URL = "https://bitpay.com"
1225 },
1226 _getBitPay = function(endpoint) {
1227 return _setCredentials(), {
1228 method: "GET",
1229 url: credentials.BITPAY_API_URL + endpoint,
1230 headers: {
1231 "content-type": "application/json"
1232 }
1233 }
1234 },
1235 _postBitPay = function(endpoint, data) {
1236 return _setCredentials(), {
1237 method: "POST",
1238 url: credentials.BITPAY_API_URL + endpoint,
1239 headers: {
1240 "content-type": "application/json"
1241 },
1242 data: data
1243 }
1244 };
1245 return root.getEnvironment = function() {
1246 return _setCredentials(), credentials.NETWORK
1247 }, root.savePendingGiftCard = function(gc, opts, cb) {
1248 var network = root.getEnvironment();
1249 storageService.getAmazonGiftCards(network, function(err, oldGiftCards) {
1250 lodash.isString(oldGiftCards) && (oldGiftCards = JSON.parse(oldGiftCards)), lodash.isString(gc) && (gc = JSON.parse(gc));
1251 var inv = oldGiftCards || {};
1252 inv[gc.invoiceId] = gc, opts && (opts.error || opts.status) && (inv[gc.invoiceId] = lodash.assign(inv[gc.invoiceId], opts)), opts && opts.remove && delete inv[gc.invoiceId], inv = JSON.stringify(inv), storageService.setAmazonGiftCards(network, inv, function(err) {
1253 return cb(err)
1254 })
1255 })
1256 }, root.getPendingGiftCards = function(cb) {
1257 var network = root.getEnvironment();
1258 storageService.getAmazonGiftCards(network, function(err, giftCards) {
1259 var _gcds = giftCards ? JSON.parse(giftCards) : null;
1260 return cb(err, _gcds)
1261 })
1262 }, root.createBitPayInvoice = function(data, cb) {
1263 var dataSrc = {
1264 currency: data.currency,
1265 amount: data.amount,
1266 clientId: data.uuid
1267 };
1268 $http(_postBitPay("/amazon-gift/pay", dataSrc)).then(function(data) {
1269 return $log.info("BitPay Create Invoice: SUCCESS"), cb(null, data.data)
1270 }, function(data) {
1271 return $log.error("BitPay Create Invoice: ERROR " + data.data.message), cb(data.data)
1272 })
1273 }, root.getBitPayInvoice = function(id, cb) {
1274 $http(_getBitPay("/invoices/" + id)).then(function(data) {
1275 return $log.info("BitPay Get Invoice: SUCCESS"), cb(null, data.data.data)
1276 }, function(data) {
1277 return $log.error("BitPay Get Invoice: ERROR " + data.data.error), cb(data.data.error)
1278 })
1279 }, root.createGiftCard = function(data, cb) {
1280 var dataSrc = {
1281 clientId: data.uuid,
1282 invoiceId: data.invoiceId,
1283 accessKey: data.accessKey
1284 };
1285 $http(_postBitPay("/amazon-gift/redeem", dataSrc)).then(function(data) {
1286 var status = "new" == data.data.status ? "PENDING" : "paid" == data.data.status ? "PENDING" : data.data.status;
1287 return data.data.status = status, $log.info("Amazon.com Gift Card Create/Update: " + status), cb(null, data.data)
1288 }, function(data) {
1289 return $log.error("Amazon.com Gift Card Create/Update: " + data.data.message), cb(data.data)
1290 })
1291 }, root.cancelGiftCard = function(data, cb) {
1292 var dataSrc = {
1293 clientId: data.uuid,
1294 invoiceId: data.invoiceId,
1295 accessKey: data.accessKey
1296 };
1297 $http(_postBitPay("/amazon-gift/cancel", dataSrc)).then(function(data) {
1298 return $log.info("Amazon.com Gift Card Cancel: SUCCESS"), cb(null, data.data)
1299 }, function(data) {
1300 return $log.error("Amazon.com Gift Card Cancel: " + data.data.message), cb(data.data)
1301 })
1302 }, root
1303 }), angular.module("copayApp.services").factory("applicationService", function($rootScope, $timeout, platformInfo, go) {
1304 var root = {},
1305 isChromeApp = platformInfo.isChromeApp,
1306 isNW = platformInfo.isNW;
1307 return root.restart = function() {
1308 var hashIndex = window.location.href.indexOf("#/");
1309 platformInfo.isCordova ? (window.location = window.location.href.substr(0, hashIndex), $timeout(function() {
1310 $rootScope.$digest()
1311 }, 1)) : isChromeApp ? chrome.runtime.reload() : isNW ? (go.walletHome(), $timeout(function() {
1312 var win = require("nw.gui").Window.get();
1313 win.reload(3), win.reloadDev()
1314 }, 100)) : window.location = window.location.href.substr(0, hashIndex)
1315 }, root
1316 }), angular.module("copayApp.services").factory("backupService", function($log, $timeout, profileService, sjcl) {
1317 var root = {},
1318 _download = function(ew, filename, cb) {
1319 var NewBlob = function(data, datatype) {
1320 var out;
1321 try {
1322 out = new Blob([data], {
1323 type: datatype
1324 }), $log.debug("case 1")
1325 } catch (e) {
1326 if (window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder, "TypeError" == e.name && window.BlobBuilder) {
1327 var bb = new BlobBuilder;
1328 bb.append(data), out = bb.getBlob(datatype), $log.debug("case 2")
1329 } else "InvalidStateError" == e.name ? (out = new Blob([data], {
1330 type: datatype
1331 }), $log.debug("case 3")) : $log.debug("Error")
1332 }
1333 return out
1334 },
1335 a = angular.element("<a></a>"),
1336 blob = new NewBlob(ew, "text/plain;charset=utf-8");
1337 return a.attr("href", window.URL.createObjectURL(blob)), a.attr("download", filename), a[0].click(), cb()
1338 };
1339 return root.addMetadata = function(b, opts) {
1340 return b = JSON.parse(b), opts.addressBook && (b.addressBook = opts.addressBook), JSON.stringify(b)
1341 }, root.walletExport = function(password, opts) {
1342 if (!password) return null;
1343 var fc = profileService.focusedClient;
1344 try {
1345 opts = opts || {};
1346 var b = fc.export(opts);
1347 opts.addressBook && (b = root.addMetadata(b, opts));
1348 return sjcl.encrypt(password, b, {
1349 iter: 1e4
1350 })
1351 } catch (err) {
1352 return $log.debug("Error exporting wallet: ", err), null
1353 }
1354 }, root.walletDownload = function(password, opts, cb) {
1355 var fc = profileService.focusedClient,
1356 ew = root.walletExport(password, opts);
1357 if (!ew) return cb("Could not create backup");
1358 var walletName = (fc.alias || "") + (fc.alias ? "-" : "") + fc.credentials.walletName;
1359 opts.noSign && (walletName += "-noSign"), _download(ew, walletName + "-NeblioOrionBackup.aes.json", cb)
1360 }, root
1361 }), angular.module("copayApp.services").factory("bitcore", function(bwcService) {
1362 return bwcService.getBitcore()
1363 }), angular.module("copayApp.services").factory("bwcError", function($log, gettextCatalog) {
1364 var root = {};
1365 return root.msg = function(err, prefix) {
1366 if (!err) return "Unknown error";
1367 var name;
1368 name = err.name ? "Error" == err.name ? err.message : err.name.replace(/^bwc.Error/g, "") : err;
1369 var body = "";
1370 if (prefix = prefix || "", name) switch (name) {
1371 case "INVALID_BACKUP":
1372 body = gettextCatalog.getString("Wallet Recovery Phrase is invalid");
1373 break;
1374 case "WALLET_DOES_NOT_EXIST":
1375 body = gettextCatalog.getString('Wallet not registered at the wallet service. Recreate it from "Create Wallet" using "Advanced Options" to set your recovery phrase');
1376 break;
1377 case "MISSING_PRIVATE_KEY":
1378 body = gettextCatalog.getString("Missing private keys to sign");
1379 break;
1380 case "ENCRYPTED_PRIVATE_KEY":
1381 body = gettextCatalog.getString("Private key is encrypted, cannot sign");
1382 break;
1383 case "SERVER_COMPROMISED":
1384 body = gettextCatalog.getString("Server response could not be verified");
1385 break;
1386 case "COULD_NOT_BUILD_TRANSACTION":
1387 body = gettextCatalog.getString("Could not build transaction");
1388 break;
1389 case "INSUFFICIENT_FUNDS":
1390 body = gettextCatalog.getString("Insufficient funds");
1391 break;
1392 case "CONNECTION_ERROR":
1393 body = gettextCatalog.getString("Network connection error");
1394 break;
1395 case "NOT_FOUND":
1396 body = gettextCatalog.getString("Wallet service not found");
1397 break;
1398 case "ECONNRESET_ERROR":
1399 body = gettextCatalog.getString("Connection reset by peer");
1400 break;
1401 case "BAD_RESPONSE_CODE":
1402 body = gettextCatalog.getString("The request could not be understood by the server");
1403 break;
1404 case "WALLET_ALREADY_EXISTS":
1405 body = gettextCatalog.getString("Wallet already exists");
1406 break;
1407 case "COPAYER_IN_WALLET":
1408 body = gettextCatalog.getString("Copayer already in this wallet");
1409 break;
1410 case "WALLET_FULL":
1411 body = gettextCatalog.getString("Wallet is full");
1412 break;
1413 case "WALLET_NOT_FOUND":
1414 body = gettextCatalog.getString("Wallet not found");
1415 break;
1416 case "INSUFFICIENT_FUNDS_FOR_FEE":
1417 body = gettextCatalog.getString("Insufficient funds for fee");
1418 break;
1419 case "LOCKED_FUNDS":
1420 body = gettextCatalog.getString("Funds are locked by pending spend proposals");
1421 break;
1422 case "COPAYER_VOTED":
1423 body = gettextCatalog.getString("Copayer already voted on this spend proposal");
1424 break;
1425 case "NOT_AUTHORIZED":
1426 body = gettextCatalog.getString("Not authorized");
1427 break;
1428 case "TX_ALREADY_BROADCASTED":
1429 body = gettextCatalog.getString("Transaction already broadcasted");
1430 break;
1431 case "TX_CANNOT_CREATE":
1432 body = gettextCatalog.getString("Locktime in effect. Please wait to create a new spend proposal");
1433 break;
1434 case "TX_CANNOT_REMOVE":
1435 body = gettextCatalog.getString("Locktime in effect. Please wait to remove this spend proposal");
1436 break;
1437 case "TX_NOT_ACCEPTED":
1438 body = gettextCatalog.getString("Spend proposal is not accepted");
1439 break;
1440 case "TX_NOT_FOUND":
1441 body = gettextCatalog.getString("Spend proposal not found");
1442 break;
1443 case "TX_NOT_PENDING":
1444 body = gettextCatalog.getString("The spend proposal is not pending");
1445 break;
1446 case "UPGRADE_NEEDED":
1447 body = gettextCatalog.getString("Please upgrade Copay to perform this action");
1448 break;
1449 case "BAD_SIGNATURES":
1450 body = gettextCatalog.getString("Signatures rejected by server");
1451 break;
1452 case "COPAYER_DATA_MISMATCH":
1453 body = gettextCatalog.getString("Copayer data mismatch");
1454 break;
1455 case "DUST_AMOUNT":
1456 body = gettextCatalog.getString("Amount below minimum allowed");
1457 break;
1458 case "INCORRECT_ADDRESS_NETWORK":
1459 body = gettextCatalog.getString("Incorrect address network");
1460 break;
1461 case "COPAYER_REGISTERED":
1462 body = gettextCatalog.getString("Key already associated with an existing wallet");
1463 break;
1464 case "INVALID_ADDRESS":
1465 body = gettextCatalog.getString("Invalid address");
1466 break;
1467 case "MAIN_ADDRESS_GAP_REACHED":
1468 body = gettextCatalog.getString("Empty addresses limit reached. New addresses cannot be generated.");
1469 break;
1470 case "WALLET_LOCKED":
1471 body = gettextCatalog.getString("Wallet is locked");
1472 break;
1473 case "WALLET_NOT_COMPLETE":
1474 body = gettextCatalog.getString("Wallet is not complete");
1475 break;
1476 case "WALLET_NEEDS_BACKUP":
1477 body = gettextCatalog.getString("Wallet needs backup");
1478 break;
1479 case "MISSING_PARAMETER":
1480 body = gettextCatalog.getString("Missing parameter");
1481 break;
1482 case "NO_PASSWORD_GIVEN":
1483 body = gettextCatalog.getString("Spending Password needed");
1484 break;
1485 case "PASSWORD_INCORRECT":
1486 body = gettextCatalog.getString("Wrong spending password");
1487 break;
1488 case "EXCEEDED_DAILY_LIMIT":
1489 body = gettextCatalog.getString("Exceeded daily limit of $500 per user");
1490 break;
1491 case "ERROR":
1492 body = err.message || err.error;
1493 break;
1494 default:
1495 $log.warn("Unknown error type:", name), body = err.message || name
1496 } else body = err.message ? err.message : err;
1497 return prefix + (body ? (prefix ? ": " : "") + body : "")
1498 }, root.cb = function(err, prefix, cb) {
1499 return cb(root.msg(err, prefix))
1500 }, root
1501 }), angular.module("copayApp.services").factory("coinbaseService", function($http, $log, platformInfo, lodash, storageService, configService) {
1502 var root = {},
1503 credentials = {},
1504 isCordova = platformInfo.isCordova;
1505 root.setCredentials = function(network) {
1506 credentials.SCOPE = "wallet:accounts:read,wallet:addresses:read,wallet:addresses:create,wallet:user:read,wallet:user:email,wallet:buys:read,wallet:buys:create,wallet:sells:read,wallet:sells:create,wallet:transactions:read,wallet:transactions:send,wallet:payment-methods:read", credentials.REDIRECT_URI = isCordova ? "copay://coinbase" : "urn:ietf:wg:oauth:2.0:oob", "testnet" == network ? (credentials.HOST = "https://sandbox.coinbase.com", credentials.API = "https://api.sandbox.coinbase.com", credentials.CLIENT_ID = "6cdcc82d5d46654c46880e93ab3d2a43c639776347dd88022904bd78cd067841", credentials.CLIENT_SECRET = "228cb6308951f4b6f41ba010c7d7981b2721a493c40c50fd2425132dcaccce59") : (credentials.HOST = "https://coinbase.com", credentials.API = "https://api.coinbase.com", credentials.CLIENT_ID = window.coinbase_client_id, credentials.CLIENT_SECRET = window.coinbase_client_secret)
1507 }, root.getOauthCodeUrl = function() {
1508 return credentials.HOST + "/oauth/authorize?response_type=code&client_id=" + credentials.CLIENT_ID + "&redirect_uri=" + credentials.REDIRECT_URI + "&state=SECURE_RANDOM&scope=" + credentials.SCOPE + "&meta[send_limit_amount]=1000&meta[send_limit_currency]=USD&meta[send_limit_period]=day"
1509 }, root.getToken = function(code, cb) {
1510 var req = {
1511 method: "POST",
1512 url: credentials.API + "/oauth/token",
1513 headers: {
1514 "Content-Type": "application/json",
1515 Accept: "application/json"
1516 },
1517 data: {
1518 grant_type: "authorization_code",
1519 code: code,
1520 client_id: credentials.CLIENT_ID,
1521 client_secret: credentials.CLIENT_SECRET,
1522 redirect_uri: credentials.REDIRECT_URI
1523 }
1524 };
1525 $http(req).then(function(data) {
1526 return $log.info("Coinbase Authorization Access Token: SUCCESS"), cb(null, data.data)
1527 }, function(data) {
1528 return $log.error("Coinbase Authorization Access Token: ERROR " + data.statusText), cb(data.data)
1529 })
1530 }, root.refreshToken = function(refreshToken, cb) {
1531 var req = {
1532 method: "POST",
1533 url: credentials.API + "/oauth/token",
1534 headers: {
1535 "Content-Type": "application/json",
1536 Accept: "application/json"
1537 },
1538 data: {
1539 grant_type: "refresh_token",
1540 client_id: credentials.CLIENT_ID,
1541 client_secret: credentials.CLIENT_SECRET,
1542 redirect_uri: credentials.REDIRECT_URI,
1543 refresh_token: refreshToken
1544 }
1545 };
1546 $http(req).then(function(data) {
1547 return $log.info("Coinbase Refresh Access Token: SUCCESS"), cb(null, data.data)
1548 }, function(data) {
1549 return $log.error("Coinbase Refresh Access Token: ERROR " + data.statusText), cb(data.data)
1550 })
1551 };
1552 var _get = function(endpoint, token) {
1553 return {
1554 method: "GET",
1555 url: credentials.API + "/v2" + endpoint,
1556 headers: {
1557 "Content-Type": "application/json",
1558 Accept: "application/json",
1559 Authorization: "Bearer " + token
1560 }
1561 }
1562 };
1563 root.getAccounts = function(token, cb) {
1564 if (!token) return cb("Invalid Token");
1565 $http(_get("/accounts", token)).then(function(data) {
1566 return $log.info("Coinbase Get Accounts: SUCCESS"), cb(null, data.data)
1567 }, function(data) {
1568 return $log.error("Coinbase Get Accounts: ERROR " + data.statusText), cb(data.data)
1569 })
1570 }, root.getAccount = function(token, accountId, cb) {
1571 if (!token) return cb("Invalid Token");
1572 $http(_get("/accounts/" + accountId, token)).then(function(data) {
1573 return $log.info("Coinbase Get Account: SUCCESS"), cb(null, data.data)
1574 }, function(data) {
1575 return $log.error("Coinbase Get Account: ERROR " + data.statusText), cb(data.data)
1576 })
1577 }, root.getAuthorizationInformation = function(token, cb) {
1578 if (!token) return cb("Invalid Token");
1579 $http(_get("/user/auth", token)).then(function(data) {
1580 return $log.info("Coinbase Autorization Information: SUCCESS"), cb(null, data.data)
1581 }, function(data) {
1582 return $log.error("Coinbase Autorization Information: ERROR " + data.statusText), cb(data.data)
1583 })
1584 }, root.getCurrentUser = function(token, cb) {
1585 if (!token) return cb("Invalid Token");
1586 $http(_get("/user", token)).then(function(data) {
1587 return $log.info("Coinbase Get Current User: SUCCESS"), cb(null, data.data)
1588 }, function(data) {
1589 return $log.error("Coinbase Get Current User: ERROR " + data.statusText), cb(data.data)
1590 })
1591 }, root.getTransaction = function(token, accountId, transactionId, cb) {
1592 if (!token) return cb("Invalid Token");
1593 $http(_get("/accounts/" + accountId + "/transactions/" + transactionId, token)).then(function(data) {
1594 return $log.info("Coinbase Transaction: SUCCESS"), cb(null, data.data)
1595 }, function(data) {
1596 return $log.error("Coinbase Transaction: ERROR " + data.statusText), cb(data.data)
1597 })
1598 }, root.getTransactions = function(token, accountId, cb) {
1599 if (!token) return cb("Invalid Token");
1600 $http(_get("/accounts/" + accountId + "/transactions", token)).then(function(data) {
1601 return $log.info("Coinbase Transactions: SUCCESS"), cb(null, data.data)
1602 }, function(data) {
1603 return $log.error("Coinbase Transactions: ERROR " + data.statusText), cb(data.data)
1604 })
1605 }, root.paginationTransactions = function(token, Url, cb) {
1606 if (!token) return cb("Invalid Token");
1607 $http(_get(Url.replace("/v2", ""), token)).then(function(data) {
1608 return $log.info("Coinbase Pagination Transactions: SUCCESS"), cb(null, data.data)
1609 }, function(data) {
1610 return $log.error("Coinbase Pagination Transactions: ERROR " + data.statusText), cb(data.data)
1611 })
1612 }, root.sellPrice = function(token, currency, cb) {
1613 $http(_get("/prices/sell?currency=" + currency, token)).then(function(data) {
1614 return $log.info("Coinbase Sell Price: SUCCESS"), cb(null, data.data)
1615 }, function(data) {
1616 return $log.error("Coinbase Sell Price: ERROR " + data.statusText), cb(data.data)
1617 })
1618 }, root.buyPrice = function(token, currency, cb) {
1619 $http(_get("/prices/buy?currency=" + currency, token)).then(function(data) {
1620 return $log.info("Coinbase Buy Price: SUCCESS"), cb(null, data.data)
1621 }, function(data) {
1622 return $log.error("Coinbase Buy Price: ERROR " + data.statusText), cb(data.data)
1623 })
1624 }, root.getPaymentMethods = function(token, cb) {
1625 $http(_get("/payment-methods", token)).then(function(data) {
1626 return $log.info("Coinbase Get Payment Methods: SUCCESS"), cb(null, data.data)
1627 }, function(data) {
1628 return $log.error("Coinbase Get Payment Methods: ERROR " + data.statusText), cb(data.data)
1629 })
1630 }, root.getPaymentMethod = function(token, paymentMethodId, cb) {
1631 $http(_get("/payment-methods/" + paymentMethodId, token)).then(function(data) {
1632 return $log.info("Coinbase Get Payment Method: SUCCESS"), cb(null, data.data)
1633 }, function(data) {
1634 return $log.error("Coinbase Get Payment Method: ERROR " + data.statusText), cb(data.data)
1635 })
1636 };
1637 var _post = function(endpoint, token, data) {
1638 return {
1639 method: "POST",
1640 url: credentials.API + "/v2" + endpoint,
1641 headers: {
1642 "Content-Type": "application/json",
1643 Accept: "application/json",
1644 Authorization: "Bearer " + token
1645 },
1646 data: data
1647 }
1648 };
1649 return root.sellRequest = function(token, accountId, data, cb) {
1650 var data = {
1651 amount: data.amount,
1652 currency: data.currency,
1653 payment_method: data.payment_method || null,
1654 commit: data.commit || !1
1655 };
1656 $http(_post("/accounts/" + accountId + "/sells", token, data)).then(function(data) {
1657 return $log.info("Coinbase Sell Request: SUCCESS"), cb(null, data.data)
1658 }, function(data) {
1659 return $log.error("Coinbase Sell Request: ERROR " + data.statusText), cb(data.data)
1660 })
1661 }, root.sellCommit = function(token, accountId, sellId, cb) {
1662 $http(_post("/accounts/" + accountId + "/sells/" + sellId + "/commit", token)).then(function(data) {
1663 return $log.info("Coinbase Sell Commit: SUCCESS"), cb(null, data.data)
1664 }, function(data) {
1665 return $log.error("Coinbase Sell Commit: ERROR " + data.statusText), cb(data.data)
1666 })
1667 }, root.buyRequest = function(token, accountId, data, cb) {
1668 var data = {
1669 amount: data.amount,
1670 currency: data.currency,
1671 payment_method: data.payment_method || null,
1672 commit: !1
1673 };
1674 $http(_post("/accounts/" + accountId + "/buys", token, data)).then(function(data) {
1675 return $log.info("Coinbase Buy Request: SUCCESS"), cb(null, data.data)
1676 }, function(data) {
1677 return $log.error("Coinbase Buy Request: ERROR " + data.statusText), cb(data.data)
1678 })
1679 }, root.buyCommit = function(token, accountId, buyId, cb) {
1680 $http(_post("/accounts/" + accountId + "/buys/" + buyId + "/commit", token)).then(function(data) {
1681 return $log.info("Coinbase Buy Commit: SUCCESS"), cb(null, data.data)
1682 }, function(data) {
1683 return $log.error("Coinbase Buy Commit: ERROR " + data.statusText), cb(data.data)
1684 })
1685 }, root.createAddress = function(token, accountId, data, cb) {
1686 var data = {
1687 name: data.name
1688 };
1689 $http(_post("/accounts/" + accountId + "/addresses", token, data)).then(function(data) {
1690 return $log.info("Coinbase Create Address: SUCCESS"), cb(null, data.data)
1691 }, function(data) {
1692 return $log.error("Coinbase Create Address: ERROR " + data.statusText), cb(data.data)
1693 })
1694 }, root.sendTo = function(token, accountId, data, cb) {
1695 var data = {
1696 type: "send",
1697 to: data.to,
1698 amount: data.amount,
1699 currency: data.currency,
1700 description: data.description
1701 };
1702 $http(_post("/accounts/" + accountId + "/transactions", token, data)).then(function(data) {
1703 return $log.info("Coinbase Create Address: SUCCESS"), cb(null, data.data)
1704 }, function(data) {
1705 return $log.error("Coinbase Create Address: ERROR " + data.statusText), cb(data.data)
1706 })
1707 }, root.savePendingTransaction = function(ctx, opts, cb) {
1708 var network = configService.getSync().coinbase.testnet ? "testnet" : "livenet";
1709 storageService.getCoinbaseTxs(network, function(err, oldTxs) {
1710 lodash.isString(oldTxs) && (oldTxs = JSON.parse(oldTxs)), lodash.isString(ctx) && (ctx = JSON.parse(ctx));
1711 var tx = oldTxs || {};
1712 tx[ctx.id] = ctx, opts && (opts.error || opts.status) && (tx[ctx.id] = lodash.assign(tx[ctx.id], opts)), opts && opts.remove && delete tx[ctx.id], tx = JSON.stringify(tx), storageService.setCoinbaseTxs(network, tx, function(err) {
1713 return cb(err)
1714 })
1715 })
1716 }, root.getPendingTransactions = function(cb) {
1717 var network = configService.getSync().coinbase.testnet ? "testnet" : "livenet";
1718 storageService.getCoinbaseTxs(network, function(err, txs) {
1719 var _txs = txs ? JSON.parse(txs) : {};
1720 return cb(err, _txs)
1721 })
1722 }, root.logout = function(network, cb) {
1723 storageService.removeCoinbaseToken(network, function() {
1724 storageService.removeCoinbaseRefreshToken(network, function() {
1725 return cb()
1726 })
1727 })
1728 }, root
1729 }), angular.module("copayApp.services").factory("configService", function(storageService, lodash, $log) {
1730 var root = {},
1731 defaultConfig = {
1732 limits: {
1733 totalCopayers: 6,
1734 mPlusN: 100
1735 },
1736 bws: {
1737 url: "https://ntp1node.nebl.io:3232/bws/api"
1738 },
1739 wallet: {
1740 requiredCopayers: 2,
1741 totalCopayers: 3,
1742 spendUnconfirmed: !1,
1743 reconnectDelay: 5e3,
1744 idleDurationMin: 4,
1745 settings: {
1746 unitName: "NEBL",
1747 unitToSatoshi: 1e8,
1748 unitDecimals: 8,
1749 unitCode: "btc",
1750 alternativeName: "US Dollar",
1751 alternativeIsoCode: "USD"
1752 }
1753 },
1754 glidera: {
1755 enabled: !0,
1756 testnet: !1
1757 },
1758 coinbase: {
1759 enabled: !0,
1760 testnet: !1
1761 },
1762 rates: {
1763 url: "https://insight.bitpay.com:443/api/rates"
1764 },
1765 release: {
1766 url: "https://api.github.com/repos/bitpay/copay/releases/latest"
1767 },
1768 pushNotifications: {
1769 enabled: !0,
1770 config: {
1771 android: {
1772 senderID: "1036948132229",
1773 icon: "push",
1774 iconColor: "#2F4053"
1775 },
1776 ios: {
1777 alert: "true",
1778 badge: "true",
1779 sound: "true"
1780 },
1781 windows: {}
1782 }
1783 }
1784 },
1785 configCache = null;
1786 return root.getSync = function() {
1787 if (!configCache) throw new Error("configService#getSync called when cache is not initialized");
1788 return configCache
1789 }, root.get = function(cb) {
1790 storageService.getConfig(function(err, localConfig) {
1791 return localConfig ? (configCache = JSON.parse(localConfig), configCache.bws || (configCache.bws = defaultConfig.bws), configCache.wallet || (configCache.wallet = defaultConfig.wallet), configCache.wallet.settings.unitCode || (configCache.wallet.settings.unitCode = defaultConfig.wallet.settings.unitCode), configCache.glidera || (configCache.glidera = defaultConfig.glidera), configCache.coinbase || (configCache.coinbase = defaultConfig.coinbase), configCache.pushNotifications || (configCache.pushNotifications = defaultConfig.pushNotifications)) : configCache = lodash.clone(defaultConfig), configCache.glidera.testnet = !1, configCache.coinbase.testnet = !1, $log.debug("Preferences read:", configCache), cb(err, configCache)
1792 })
1793 }, root.set = function(newOpts, cb) {
1794 var config = lodash.cloneDeep(defaultConfig);
1795 storageService.getConfig(function(err, oldOpts) {
1796 oldOpts = oldOpts || {}, lodash.isString(oldOpts) && (oldOpts = JSON.parse(oldOpts)), lodash.isString(config) && (config = JSON.parse(config)), lodash.isString(newOpts) && (newOpts = JSON.parse(newOpts)), lodash.merge(config, oldOpts, newOpts), configCache = config, storageService.storeConfig(JSON.stringify(config), cb)
1797 })
1798 }, root.reset = function(cb) {
1799 configCache = lodash.clone(defaultConfig), storageService.removeConfig(cb)
1800 }, root.getDefaults = function() {
1801 return lodash.clone(defaultConfig)
1802 }, root
1803 }), angular.module("copayApp.services").factory("confirmDialog", function($log, $timeout, profileService, configService, gettextCatalog, platformInfo) {
1804 var root = {},
1805 acceptMsg = gettextCatalog.getString("Accept"),
1806 cancelMsg = gettextCatalog.getString("Cancel"),
1807 confirmMsg = gettextCatalog.getString("Confirm");
1808 return root.show = function(msg, cb) {
1809 if (!platformInfo.isCordova) return cb(platformInfo.isChromeApp ? !0 : confirm(msg));
1810 navigator.notification.confirm(msg, function(buttonIndex) {
1811 if (1 != buttonIndex) return cb(!1);
1812 $timeout(function() {
1813 return cb(!0)
1814 }, 1)
1815 }, confirmMsg, [acceptMsg, cancelMsg])
1816 }, root
1817 }), angular.module("copayApp.services").factory("derivationPathHelper", function(lodash) {
1818 var root = {};
1819 return root.default = "m/44'/0'/0'", root.defaultTestnet = "m/44'/1'/0'", root.parse = function(str) {
1820 var arr = str.split("/"),
1821 ret = {};
1822 if ("m" != arr[0]) return !1;
1823 switch (arr[1]) {
1824 case "44'":
1825 ret.derivationStrategy = "BIP44";
1826 break;
1827 case "45'":
1828 return {
1829 derivationStrategy: "BIP45",
1830 networkName: "livenet",
1831 account: 0
1832 };
1833 case "48'":
1834 ret.derivationStrategy = "BIP48";
1835 break;
1836 default:
1837 return !1
1838 }
1839 switch (arr[2]) {
1840 case "0'":
1841 ret.networkName = "livenet";
1842 break;
1843 case "1'":
1844 ret.networkName = "testnet";
1845 break;
1846 default:
1847 return !1
1848 }
1849 var match = arr[3].match(/(\d+)'/);
1850 return !!match && (ret.account = +match[1], ret)
1851 }, root
1852 }), angular.module("copayApp.services").factory("feeService", function($log, bwcService, profileService, configService, gettext, lodash) {
1853 var root = {};
1854 return root.feeOpts = {
1855 priority: gettext("Priority"),
1856 normal: gettext("Normal"),
1857 economy: gettext("Economy"),
1858 superEconomy: gettext("Super Economy")
1859 }, root.getCurrentFeeLevel = function() {
1860 return configService.getSync().wallet.settings.feeLevel || "normal"
1861 }, root.getCurrentFeeValue = function(cb) {
1862 var fc = profileService.focusedClient,
1863 feeLevel = root.getCurrentFeeLevel();
1864 fc.getFeeLevels(fc.credentials.network, function(err, levels) {
1865 if (err) return cb({
1866 message: "Could not get dynamic fee"
1867 });
1868 var feeLevelValue = lodash.find(levels, {
1869 level: feeLevel
1870 });
1871 if (!feeLevelValue || !feeLevelValue.feePerKB) return cb({
1872 message: "Could not get dynamic fee for level: " + feeLevel
1873 });
1874 var fee = feeLevelValue.feePerKB;
1875 return fee = fee < 1e4 ? 1e4 : fee, fee = 1e4 * Math.ceil(fee / 1e4), $log.debug("Dynamic fee: " + feeLevel + " " + fee + " SAT"), cb(null, fee)
1876 })
1877 }, root.getFeeLevels = function(cb) {
1878 var walletClient = bwcService.getClient(),
1879 unitName = configService.getSync().wallet.settings.unitName;
1880 walletClient.getFeeLevels("livenet", function(errLivenet, levelsLivenet) {
1881 walletClient.getFeeLevels("testnet", function(errTestnet, levelsTestnet) {
1882 if (errLivenet || errTestnet) $log.debug("Could not get dynamic fee");
1883 else
1884 for (var i = 0; i < 4; i++) levelsLivenet[i].feePerKBUnit = profileService.formatAmount(levelsLivenet[i].feePerKB) + " " + unitName, levelsTestnet[i].feePerKBUnit = profileService.formatAmount(levelsTestnet[i].feePerKB) + " " + unitName;
1885 return cb({
1886 livenet: levelsLivenet,
1887 testnet: levelsTestnet
1888 })
1889 })
1890 })
1891 }, root
1892 }), angular.module("copayApp.services").factory("fileStorageService", function(lodash, $log) {
1893 var _fs, _dir, root = {};
1894 root.init = function(cb) {
1895 function onFileSystemSuccess(fileSystem) {
1896 console.log("File system started: ", fileSystem.name, fileSystem.root.name), _fs = fileSystem, root.getDir(function(err, newDir) {
1897 return err || !newDir.nativeURL ? cb(err) : (_dir = newDir, $log.debug("Got main dir:", _dir.nativeURL), cb(null, _fs, _dir))
1898 })
1899 }
1900
1901 function fail(evt) {
1902 var msg = "Could not init file system: " + evt.target.error.code;
1903 return console.log(msg), cb(msg)
1904 }
1905 if (_dir) return cb(null, _fs, _dir);
1906 window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFileSystemSuccess, fail)
1907 }, root.get = function(k, cb) {
1908 root.init(function(err, fs, dir) {
1909 if (err) return cb(err);
1910 dir.getFile(k, {
1911 create: !1
1912 }, function(fileEntry) {
1913 if (!fileEntry) return cb();
1914 fileEntry.file(function(file) {
1915 var reader = new FileReader;
1916 reader.onloadend = function(e) {
1917 return cb(null, this.result)
1918 }, reader.readAsText(file)
1919 })
1920 }, function(err) {
1921 return 1 == err.code ? cb() : cb(err)
1922 })
1923 })
1924 };
1925 var writelock = {};
1926 return root.set = function(k, v, cb, delay) {
1927 if (delay = delay || 100, writelock[k]) return setTimeout(function() {
1928 return console.log("## Writelock for:" + k + " Retrying in " + delay), root.set(k, v, cb, delay + 100)
1929 }, delay);
1930 writelock[k] = !0, root.init(function(err, fs, dir) {
1931 if (err) return writelock[k] = !1, cb(err);
1932 dir.getFile(k, {
1933 create: !0
1934 }, function(fileEntry) {
1935 fileEntry.createWriter(function(fileWriter) {
1936 fileWriter.onwriteend = function(e) {
1937 return console.log("Write completed:" + k), writelock[k] = !1, cb()
1938 }, fileWriter.onerror = function(e) {
1939 var err = e.error ? e.error : JSON.stringify(e);
1940 return console.log("Write failed: " + err), writelock[k] = !1, cb("Fail to write:" + err)
1941 }, lodash.isObject(v) && (v = JSON.stringify(v)), lodash.isString(v) || (v = v.toString()), $log.debug("Writing:", k, v), fileWriter.write(v)
1942 }, cb)
1943 })
1944 })
1945 }, root.getDir = function(cb) {
1946 if (!cordova.file) return cb("Could not write on device storage");
1947 var url = cordova.file.dataDirectory;
1948 window.resolveLocalFileSystemURL(url, function(dir) {
1949 return cb(null, dir)
1950 }, function(err) {
1951 return $log.warn(err), cb(err || "Could not resolve filesystem:" + url)
1952 })
1953 }, root.remove = function(k, cb) {
1954 root.init(function(err, fs, dir) {
1955 if (err) return cb(err);
1956 dir.getFile(k, {
1957 create: !1
1958 }, function(fileEntry) {
1959 fileEntry.remove(function() {
1960 return console.log("File removed."), cb()
1961 }, cb)
1962 }, cb)
1963 })
1964 }, root.create = function(name, value, callback) {
1965 root.get(name, function(err, data) {
1966 return data ? callback("EEXISTS") : root.set(name, value, callback)
1967 })
1968 }, root
1969 }), angular.module("copayApp.services").factory("fingerprintService", function($log, gettextCatalog, configService, platformInfo) {
1970 var root = {},
1971 _isAvailable = !1;
1972 platformInfo.isCordova && !platformInfo.isWP && (window.plugins.touchid = window.plugins.touchid || {}, window.plugins.touchid.isAvailable(function(msg) {
1973 _isAvailable = "IOS"
1974 }, function(msg) {
1975 FingerprintAuth.isAvailable(function(result) {
1976 result.isAvailable && (_isAvailable = "ANDROID")
1977 }, function() {
1978 _isAvailable = !1
1979 })
1980 }));
1981 var requestFinger = function(cb) {
1982 try {
1983 FingerprintAuth.show({
1984 clientId: "Copay",
1985 clientSecret: "hVu1NvCZOyUuGgr46bFL"
1986 }, function(result) {
1987 return result.withFingerprint ? ($log.debug("Finger OK"), cb()) : result.withPassword ? ($log.debug("Finger: Authenticated with backup password"), cb()) : void 0
1988 }, function(msg) {
1989 return $log.debug("Finger Failed:" + JSON.stringify(msg)), cb(gettextCatalog.getString("Finger Scan Failed") + ": " + msg.localizedDescription)
1990 })
1991 } catch (e) {
1992 return $log.warn("Finger Scan Failed:" + JSON.stringify(e)), cb(gettextCatalog.getString("Finger Scan Failed"))
1993 }
1994 },
1995 requestTouchId = function(cb) {
1996 try {
1997 window.plugins.touchid.verifyFingerprint(gettextCatalog.getString("Scan your fingerprint please"), function(msg) {
1998 return $log.debug("Touch ID OK"), cb()
1999 }, function(msg) {
2000 return $log.debug("Touch ID Failed:" + JSON.stringify(msg)), cb(gettextCatalog.getString("Touch ID Failed") + ": " + msg.localizedDescription)
2001 })
2002 } catch (e) {
2003 return $log.debug("Touch ID Failed:" + JSON.stringify(e)), cb(gettextCatalog.getString("Touch ID Failed"))
2004 }
2005 },
2006 isNeeded = function(client) {
2007 if (!_isAvailable) return !1;
2008 var config = configService.getSync();
2009 return config.touchIdFor = config.touchIdFor || {},
2010 config.touchIdFor[client.credentials.walletId]
2011 };
2012 return root.isAvailable = function(client) {
2013 return _isAvailable
2014 }, root.check = function(client, cb) {
2015 return isNeeded(client) ? ($log.debug("FingerPrint Service:", _isAvailable), "IOS" == _isAvailable ? requestTouchId(cb) : requestFinger(cb)) : cb()
2016 }, root
2017 }), angular.module("copayApp.services").factory("glideraService", function($http, $log, platformInfo) {
2018 var root = {},
2019 credentials = {},
2020 isCordova = platformInfo.isCordova;
2021 root.setCredentials = function(network) {
2022 "testnet" == network ? (credentials.HOST = "https://sandbox.glidera.io", isCordova ? (credentials.REDIRECT_URI = "copay://glidera", credentials.CLIENT_ID = "6163427a2f37d1b2022ececd6d6c9cdd", credentials.CLIENT_SECRET = "599cc3af26108c6fece8ab17c3f35867") : (credentials.REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob", credentials.CLIENT_ID = "c402f4a753755456e8c384fb65b7be1d", credentials.CLIENT_SECRET = "3ce826198e3618d0b8ed341ab91fe4e5")) : (credentials.HOST = "https://glidera.io", isCordova ? (credentials.REDIRECT_URI = "copay://glidera", credentials.CLIENT_ID = "9c8023f0ac0128235b7b27a6f2610c83", credentials.CLIENT_SECRET = "30431511407b47f25a83bffd72881d55") : (credentials.REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob", credentials.CLIENT_ID = "8a9e8a9cf155db430c1ea6c7889afed1", credentials.CLIENT_SECRET = "24ddec578f38d5488bfe13601933c05f"))
2023 }, root.getOauthCodeUrl = function() {
2024 return credentials.HOST + "/oauth2/auth?response_type=code&client_id=" + credentials.CLIENT_ID + "&redirect_uri=" + credentials.REDIRECT_URI
2025 }, root.getToken = function(code, cb) {
2026 var req = {
2027 method: "POST",
2028 url: credentials.HOST + "/api/v1/oauth/token",
2029 headers: {
2030 "Content-Type": "application/json",
2031 Accept: "application/json"
2032 },
2033 data: {
2034 grant_type: "authorization_code",
2035 code: code,
2036 client_id: credentials.CLIENT_ID,
2037 client_secret: credentials.CLIENT_SECRET,
2038 redirect_uri: credentials.REDIRECT_URI
2039 }
2040 };
2041 $http(req).then(function(data) {
2042 return $log.info("Glidera Authorization Access Token: SUCCESS"), cb(null, data.data)
2043 }, function(data) {
2044 return $log.error("Glidera Authorization Access Token: ERROR " + data.statusText), cb("Glidera Authorization Access Token: ERROR " + data.statusText)
2045 })
2046 };
2047 var _get = function(endpoint, token) {
2048 return {
2049 method: "GET",
2050 url: credentials.HOST + "/api/v1" + endpoint,
2051 headers: {
2052 "Content-Type": "application/json",
2053 Accept: "application/json",
2054 Authorization: "Bearer " + token
2055 }
2056 }
2057 };
2058 root.getAccessTokenPermissions = function(token, cb) {
2059 if (!token) return cb("Invalid Token");
2060 $http(_get("/oauth/token", token)).then(function(data) {
2061 return $log.info("Glidera Access Token Permissions: SUCCESS"), cb(null, data.data)
2062 }, function(data) {
2063 return $log.error("Glidera Access Token Permissions: ERROR " + data.statusText), cb("Glidera Access Token Permissions: ERROR " + data.statusText)
2064 })
2065 }, root.getEmail = function(token, cb) {
2066 if (!token) return cb("Invalid Token");
2067 $http(_get("/user/email", token)).then(function(data) {
2068 return $log.info("Glidera Get Email: SUCCESS"), cb(null, data.data)
2069 }, function(data) {
2070 return $log.error("Glidera Get Email: ERROR " + data.statusText), cb("Glidera Get Email: ERROR " + data.statusText)
2071 })
2072 }, root.getPersonalInfo = function(token, cb) {
2073 if (!token) return cb("Invalid Token");
2074 $http(_get("/user/personalinfo", token)).then(function(data) {
2075 return $log.info("Glidera Get Personal Info: SUCCESS"), cb(null, data.data)
2076 }, function(data) {
2077 return $log.error("Glidera Get Personal Info: ERROR " + data.statusText), cb("Glidera Get Personal Info: ERROR " + data.statusText)
2078 })
2079 }, root.getStatus = function(token, cb) {
2080 if (!token) return cb("Invalid Token");
2081 $http(_get("/user/status", token)).then(function(data) {
2082 return $log.info("Glidera User Status: SUCCESS"), cb(null, data.data)
2083 }, function(data) {
2084 return $log.error("Glidera User Status: ERROR " + data.statusText), cb("Glidera User Status: ERROR " + data.statusText)
2085 })
2086 }, root.getLimits = function(token, cb) {
2087 if (!token) return cb("Invalid Token");
2088 $http(_get("/user/limits", token)).then(function(data) {
2089 return $log.info("Glidera Transaction Limits: SUCCESS"), cb(null, data.data)
2090 }, function(data) {
2091 return $log.error("Glidera Transaction Limits: ERROR " + data.statusText), cb("Glidera Transaction Limits: ERROR " + data.statusText)
2092 })
2093 }, root.getTransactions = function(token, cb) {
2094 if (!token) return cb("Invalid Token");
2095 $http(_get("/transaction", token)).then(function(data) {
2096 return $log.info("Glidera Transactions: SUCCESS"), cb(null, data.data.transactions)
2097 }, function(data) {
2098 return $log.error("Glidera Transactions: ERROR " + data.statusText), cb("Glidera Transactions: ERROR " + data.statusText)
2099 })
2100 }, root.getTransaction = function(token, txid, cb) {
2101 return token ? txid ? void $http(_get("/transaction/" + txid, token)).then(function(data) {
2102 return $log.info("Glidera Transaction: SUCCESS"), cb(null, data.data)
2103 }, function(data) {
2104 return $log.error("Glidera Transaction: ERROR " + data.statusText), cb("Glidera Transaction: ERROR " + data.statusText)
2105 }) : cb("TxId required") : cb("Invalid Token")
2106 }, root.getSellAddress = function(token, cb) {
2107 if (!token) return cb("Invalid Token");
2108 $http(_get("/user/create_sell_address", token)).then(function(data) {
2109 return $log.info("Glidera Create Sell Address: SUCCESS"), cb(null, data.data.sellAddress)
2110 }, function(data) {
2111 return $log.error("Glidera Create Sell Address: ERROR " + data.statusText), cb("Glidera Create Sell Address: ERROR " + data.statusText)
2112 })
2113 }, root.get2faCode = function(token, cb) {
2114 if (!token) return cb("Invalid Token");
2115 $http(_get("/authentication/get2faCode", token)).then(function(data) {
2116 return $log.info("Glidera Sent 2FA code by SMS: SUCCESS"), cb(null, 200 == data.status)
2117 }, function(data) {
2118 return $log.error("Glidera Sent 2FA code by SMS: ERROR " + data.statusText), cb("Glidera Sent 2FA code by SMS: ERROR " + data.statusText)
2119 })
2120 };
2121 var _post = function(endpoint, token, twoFaCode, data) {
2122 return {
2123 method: "POST",
2124 url: credentials.HOST + "/api/v1" + endpoint,
2125 headers: {
2126 "Content-Type": "application/json",
2127 Accept: "application/json",
2128 Authorization: "Bearer " + token,
2129 "2FA_CODE": twoFaCode
2130 },
2131 data: data
2132 }
2133 };
2134 return root.sellPrice = function(token, price, cb) {
2135 var data = {
2136 qty: price.qty,
2137 fiat: price.fiat
2138 };
2139 $http(_post("/prices/sell", token, null, data)).then(function(data) {
2140 return $log.info("Glidera Sell Price: SUCCESS"), cb(null, data.data)
2141 }, function(data) {
2142 return $log.error("Glidera Sell Price: ERROR " + data.statusText), cb("Glidera Sell Price: ERROR " + data.statusText)
2143 })
2144 }, root.sell = function(token, twoFaCode, data, cb) {
2145 var data = {
2146 refundAddress: data.refundAddress,
2147 signedTransaction: data.signedTransaction,
2148 priceUuid: data.priceUuid,
2149 useCurrentPrice: data.useCurrentPrice,
2150 ip: data.ip
2151 };
2152 $http(_post("/sell", token, twoFaCode, data)).then(function(data) {
2153 return $log.info("Glidera Sell: SUCCESS"), cb(null, data.data)
2154 }, function(data) {
2155 return $log.error("Glidera Sell Request: ERROR " + data.statusText), cb("Glidera Sell Request: ERROR " + data.statusText)
2156 })
2157 }, root.buyPrice = function(token, price, cb) {
2158 var data = {
2159 qty: price.qty,
2160 fiat: price.fiat
2161 };
2162 $http(_post("/prices/buy", token, null, data)).then(function(data) {
2163 return $log.info("Glidera Buy Price: SUCCESS"), cb(null, data.data)
2164 }, function(data) {
2165 return $log.error("Glidera Buy Price: ERROR " + data.statusText), cb("Glidera Buy Price: ERROR " + data.statusText)
2166 })
2167 }, root.buy = function(token, twoFaCode, data, cb) {
2168 var data = {
2169 destinationAddress: data.destinationAddress,
2170 qty: data.qty,
2171 priceUuid: data.priceUuid,
2172 useCurrentPrice: data.useCurrentPrice,
2173 ip: data.ip
2174 };
2175 $http(_post("/buy", token, twoFaCode, data)).then(function(data) {
2176 return $log.info("Glidera Buy: SUCCESS"), cb(null, data.data)
2177 }, function(data) {
2178 return $log.error("Glidera Buy Request: ERROR " + data.statusText), cb("Glidera Buy Request: ERROR " + data.statusText)
2179 })
2180 }, root
2181 }), angular.module("copayApp.services").factory("go", function($window, $ionicSideMenuDelegate, $rootScope, $location, $state, $timeout, $log, profileService, platformInfo, nodeWebkit) {
2182 var root = {};
2183 return root.openExternalLink = function(url, target) {
2184 if (platformInfo.isNW) nodeWebkit.openExternalLink(url);
2185 else {
2186 target = target || "_blank";
2187 window.open(url, target, "location=no")
2188 }
2189 }, root.is = function(name) {
2190 return $state.is(name)
2191 }, root.path = function(path, cb) {
2192 $state.transitionTo(path).then(function() {
2193 if (cb) return cb()
2194 }, function() {
2195 if (cb) return cb("animation in progress")
2196 })
2197 }, root.toggleLeftMenu = function() {
2198 $ionicSideMenuDelegate.toggleLeft()
2199 }, root.walletHome = function() {
2200 var fc = profileService.focusedClient;
2201 fc && !fc.isComplete() ? ($log.debug("Wallet not complete at startup... redirecting"), root.path("copayers")) : root.path("walletHome", function() {
2202 $rootScope.$emit("Local/SetTab", "walletHome", !0)
2203 })
2204 }, root.send = function() {
2205 root.path("walletHome", function() {
2206 $rootScope.$emit("Local/SetTab", "send")
2207 })
2208 }, root.addWallet = function() {
2209 $state.transitionTo("add")
2210 }, root.preferences = function() {
2211 $state.transitionTo("preferences")
2212 }, root.preferencesGlobal = function() {
2213 $state.transitionTo("preferencesGlobal")
2214 }, root.reload = function() {
2215 $state.reload()
2216 }, $rootScope.go = function(path) {
2217 root.path(path)
2218 }, $rootScope.openExternalLink = function(url, target) {
2219 root.openExternalLink(url, target)
2220 }, root
2221 });
2222var logs = [];
2223angular.module("copayApp.services").factory("historicLog", function() {
2224 var root = {};
2225 return root.add = function(level, msg) {
2226 logs.push({
2227 level: level,
2228 msg: msg
2229 })
2230 }, root.get = function() {
2231 return logs
2232 }, root
2233}), angular.module("copayApp.services").factory("hwWallet", function($log, bwcService) {
2234 var root = {};
2235 return root.ENTROPY_INDEX_PATH = "0xb11e/", root.UNISIG_ROOTPATH = 44, root.MULTISIG_ROOTPATH = 48, root.LIVENET_PATH = 0, root._err = function(data) {
2236 var msg = "Hardware Wallet Error: " + (data.error || data.message || "unknown");
2237 return $log.warn(msg), msg
2238 }, root.getRootPath = function(device, isMultisig, account) {
2239 return isMultisig ? "ledger" == device && 0 == account ? root.UNISIG_ROOTPATH : root.MULTISIG_ROOTPATH : root.UNISIG_ROOTPATH
2240 }, root.getAddressPath = function(device, isMultisig, account) {
2241 return root.getRootPath(device, isMultisig, account) + "'/" + root.LIVENET_PATH + "'/" + account + "'"
2242 }, root.getEntropyPath = function(device, isMultisig, account) {
2243 return "ledger" == device && 0 == account ? root.ENTROPY_INDEX_PATH + "0'" : root.ENTROPY_INDEX_PATH + root.getRootPath(device, isMultisig, account) + "'/" + account + "'"
2244 }, root.pubKeyToEntropySource = function(xPubKey) {
2245 return bwcService.getBitcore().HDPublicKey(xPubKey).publicKey.toString()
2246 }, root
2247}), angular.module("copayApp.services").factory("latestReleaseService", function($log, $http, configService) {
2248 function requestLatestRelease(releaseURL, cb) {
2249 $log.debug("Retrieving latest relsease information..."), $http({
2250 url: releaseURL,
2251 method: "GET",
2252 json: !0
2253 }).then(function(release) {
2254 return $log.debug("Latest release: " + release.data.name), cb(null, release)
2255 }, function(err) {
2256 return cb("Cannot get the release information: " + err)
2257 })
2258 }
2259 var root = {};
2260 return root.checkLatestRelease = function(cb) {
2261 function verifyTagFormat(tag) {
2262 return /^v?\d+\.\d+\.\d+$/i.exec(tag)
2263 }
2264
2265 function formatTagNumber(tag) {
2266 var formattedNumber = tag.replace(/^v/i, "").split(".");
2267 return {
2268 major: +formattedNumber[0],
2269 minor: +formattedNumber[1],
2270 patch: +formattedNumber[2]
2271 }
2272 }
2273 requestLatestRelease(configService.getDefaults().release.url, function(err, release) {
2274 if (err) return cb(err);
2275 var currentVersion = window.version,
2276 latestVersion = release.data.tag_name;
2277 if (!verifyTagFormat(currentVersion)) return cb("Cannot verify the format of version tag: " + currentVersion);
2278 if (!verifyTagFormat(latestVersion)) return cb("Cannot verify the format of latest release tag: " + latestVersion);
2279 var current = formatTagNumber(currentVersion),
2280 latest = formatTagNumber(latestVersion);
2281 return latest.major < current.major || latest.major == current.major && latest.minor <= current.minor ? cb(null, !1) : ($log.debug("A new version of Copay is available: " + latestVersion), cb(null, !0))
2282 })
2283 }, root
2284}), angular.module("copayApp.services").factory("ledger", function($log, bwcService, gettext, hwWallet) {
2285 var root = {};
2286 return root.callbacks = {}, root.hasSession = function() {
2287 root._message({
2288 command: "has_session"
2289 })
2290 }, root.getEntropySource = function(isMultisig, account, callback) {
2291 root.getXPubKey(hwWallet.getEntropyPath("ledger", isMultisig, account), function(data) {
2292 return data.success ? callback(null, hwWallet.pubKeyToEntropySource(data.xpubkey)) : callback(hwWallet._err(data))
2293 })
2294 }, root.getXPubKey = function(path, callback) {
2295 $log.debug("Ledger deriving xPub path:", path), root.callbacks.get_xpubkey = callback, root._messageAfterSession({
2296 command: "get_xpubkey",
2297 path: path
2298 })
2299 }, root.getInfoForNewWallet = function(isMultisig, account, callback) {
2300 var opts = {};
2301 root.getEntropySource(isMultisig, account, function(err, entropySource) {
2302 if (err) return callback(err);
2303 opts.entropySource = entropySource, root.getXPubKey(hwWallet.getAddressPath("ledger", isMultisig, account), function(data) {
2304 return data.success ? (opts.extendedPublicKey = data.xpubkey, opts.externalSource = "ledger", opts.account = account, opts.derivationStrategy = account ? "BIP48" : "BIP44", callback(null, opts)) : ($log.warn(data.message), callback(data))
2305 })
2306 })
2307 }, root._signP2SH = function(txp, account, isMultisig, callback) {
2308 root.callbacks.sign_p2sh = callback;
2309 for (var redeemScripts = [], paths = [], tx = bwcService.getUtils().buildTx(txp), i = 0; i < tx.inputs.length; i++) redeemScripts.push(new ByteString(tx.inputs[i].redeemScript.toBuffer().toString("hex"), GP.HEX).toString()), paths.push(hwWallet.getAddressPath("ledger", isMultisig, account) + txp.inputs[i].path.substring(1));
2310 for (var splitTransaction = root._splitTransaction(new ByteString(tx.toString(), GP.HEX)), inputs = [], i = 0; i < splitTransaction.inputs.length; i++) {
2311 var input = splitTransaction.inputs[i];
2312 inputs.push([root._reverseBytestring(input.prevout.bytes(0, 32)).toString(), root._reverseBytestring(input.prevout.bytes(32)).toString()])
2313 }
2314 $log.debug("Ledger signing paths:", paths), root._messageAfterSession({
2315 command: "sign_p2sh",
2316 inputs: inputs,
2317 scripts: redeemScripts,
2318 outputs_number: splitTransaction.outputs.length,
2319 outputs_script: splitTransaction.outputScript.toString(),
2320 paths: paths
2321 })
2322 }, root.signTx = function(txp, account, callback) {
2323 if ("P2PKH" == txp.addressType) {
2324 var msg = "P2PKH wallets are not supported with ledger";
2325 return $log.error(msg), callback(msg)
2326 }
2327 root._signP2SH(txp, account, !0, callback)
2328 }, root._message = function(data) {
2329 chrome.runtime.sendMessage("kkdpmhnladdopljabkgpacgpliggeeaf", {
2330 request: data
2331 }, function(response) {
2332 root._callback(response)
2333 })
2334 }, root._messageAfterSession = function(data) {
2335 root._after_session = data, root._message({
2336 command: "launch"
2337 }), root._should_poll_session = !0, root._do_poll_session()
2338 }, root._do_poll_session = function() {
2339 root.hasSession(), root._should_poll_session && setTimeout(root._do_poll_session, 500)
2340 }, root._callback = function(data) {
2341 "object" == typeof data ? "has_session" == data.command && data.success ? (root._message(root._after_session), root._after_session = null, root._should_poll_session = !1) : "function" == typeof root.callbacks[data.command] && root.callbacks[data.command](data) : (root._should_poll_session = !1, Object.keys(root.callbacks).forEach(function(key) {
2342 root.callbacks[key]({
2343 success: !1,
2344 message: gettext("The Ledger Chrome application is not installed")
2345 })
2346 }))
2347 }, root._splitTransaction = function(transaction) {
2348 var result = {},
2349 inputs = [],
2350 outputs = [],
2351 offset = 0,
2352 version = transaction.bytes(offset, 4);
2353 offset += 4;
2354 var varint = root._getVarint(transaction, offset),
2355 numberInputs = varint[0];
2356 offset += varint[1];
2357 for (var i = 0; i < numberInputs; i++) {
2358 var input = {};
2359 input.prevout = transaction.bytes(offset, 36), offset += 36, varint = root._getVarint(transaction, offset), offset += varint[1], input.script = transaction.bytes(offset, varint[0]), offset += varint[0], input.sequence = transaction.bytes(offset, 4), offset += 4, inputs.push(input)
2360 }
2361 varint = root._getVarint(transaction, offset);
2362 var numberOutputs = varint[0];
2363 offset += varint[1];
2364 for (var outputStartOffset = offset, i = 0; i < numberOutputs; i++) {
2365 var output = {};
2366 output.amount = transaction.bytes(offset, 8), offset += 8, varint = root._getVarint(transaction, offset), offset += varint[1], output.script = transaction.bytes(offset, varint[0]), offset += varint[0], outputs.push(output)
2367 }
2368 var locktime = transaction.bytes(offset, 4);
2369 return result.version = version, result.inputs = inputs, result.outputs = outputs, result.locktime = locktime, result.outputScript = transaction.bytes(outputStartOffset, offset - outputStartOffset), result
2370 }, root._getVarint = function(data, offset) {
2371 return data.byteAt(offset) < 253 ? [data.byteAt(offset), 1] : 253 == data.byteAt(offset) ? [(data.byteAt(offset + 2) << 8) + data.byteAt(offset + 1), 3] : 254 == data.byteAt(offset) ? [(data.byteAt(offset + 4) << 24) + (data.byteAt(offset + 3) << 16) + (data.byteAt(offset + 2) << 8) + data.byteAt(offset + 1), 5] : void 0
2372 }, root._reverseBytestring = function(x) {
2373 for (var res = "", i = x.length - 1; i >= 0; i--) res += Convert.toHexByte(x.byteAt(i));
2374 return new ByteString(res, GP.HEX)
2375 }, root
2376});
2377var Convert = {};
2378Convert.stringToHex = function(src) {
2379 for (var r = "", hexes = new Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"), i = 0; i < src.length; i++) r += hexes[src.charCodeAt(i) >> 4] + hexes[15 & src.charCodeAt(i)];
2380 return r
2381}, Convert.hexToBin = function(src) {
2382 var result = "",
2383 digits = "0123456789ABCDEF";
2384 if (src.length % 2 != 0) throw "Invalid string";
2385 src = src.toUpperCase();
2386 for (var i = 0; i < src.length; i += 2) {
2387 var x1 = digits.indexOf(src.charAt(i));
2388 if (x1 < 0) return "";
2389 var x2 = digits.indexOf(src.charAt(i + 1));
2390 if (x2 < 0) return "";
2391 result += String.fromCharCode((x1 << 4) + x2)
2392 }
2393 return result
2394}, Convert.readHexDigit = function(data, offset) {
2395 var digits = "0123456789ABCDEF";
2396 return void 0 === offset && (offset = 0), (digits.indexOf(data.substring(offset, offset + 1).toUpperCase()) << 4) + digits.indexOf(data.substring(offset + 1, offset + 2).toUpperCase())
2397}, Convert.toHexDigit = function(number) {
2398 var digits = "0123456789abcdef";
2399 return digits.charAt(number >> 4) + digits.charAt(15 & number)
2400}, Convert.toHexByte = function(number) {
2401 return Convert.toHexDigit(number)
2402}, Convert.toHexByteBCD = function(numberBCD) {
2403 var number = numberBCD / 10 * 16 + numberBCD % 10;
2404 return Convert.toHexDigit(number)
2405}, Convert.toHexShort = function(number) {
2406 return Convert.toHexDigit(number >> 8 & 255) + Convert.toHexDigit(255 & number)
2407}, Convert.toHexInt = function(number) {
2408 return Convert.toHexDigit(number >> 24 & 255) + Convert.toHexDigit(number >> 16 & 255) + Convert.toHexDigit(number >> 8 & 255) + Convert.toHexDigit(255 & number)
2409};
2410var GP = {};
2411GP.ASCII = 1, GP.HEX = 5;
2412var ByteString = function(value, encoding) {
2413 if (this.encoding = encoding, this.hasBuffer = "undefined" != typeof Buffer, this.hasBuffer && value instanceof Buffer) this.value = value, this.encoding = GP.HEX;
2414 else switch (encoding) {
2415 case GP.HEX:
2416 this.hasBuffer ? this.value = new Buffer(value, "hex") : this.value = Convert.hexToBin(value);
2417 break;
2418 case GP.ASCII:
2419 this.hasBuffer ? this.value = new Buffer(value, "ascii") : this.value = value;
2420 break;
2421 default:
2422 throw "Invalid arguments"
2423 }
2424 this.length = this.value.length
2425};
2426ByteString.prototype.byteAt = function(index) {
2427 if (arguments.length < 1) throw "Argument missing";
2428 if ("number" != typeof index) throw "Invalid index";
2429 if (index < 0 || index >= this.value.length) throw "Invalid index offset";
2430 return this.hasBuffer ? this.value[index] : Convert.readHexDigit(Convert.stringToHex(this.value.substring(index, index + 1)))
2431}, ByteString.prototype.bytes = function(offset, count) {
2432 var result;
2433 if (arguments.length < 1) throw "Argument missing";
2434 if ("number" != typeof offset) throw "Invalid offset";
2435 if (offset < 0) throw "Invalid offset";
2436 if ("number" == typeof count) {
2437 if (count < 0) throw "Invalid count";
2438 this.hasBuffer ? (result = new Buffer(count), this.value.copy(result, 0, offset, offset + count)) : result = new ByteString(this.value.substring(offset, offset + count), GP.ASCII)
2439 } else {
2440 if (void 0 !== count) throw "Invalid count";
2441 this.hasBuffer ? (result = new Buffer(this.value.length - offset), this.value.copy(result, 0, offset, this.value.length)) : result = new ByteString(this.value.substring(offset), GP.ASCII)
2442 }
2443 return this.hasBuffer ? new ByteString(result, GP.HEX) : (result.encoding = this.encoding, result)
2444}, ByteString.prototype.concat = function(target) {
2445 if (arguments.length < 1) throw "Not enough arguments";
2446 if (!(target instanceof ByteString)) throw "Invalid argument";
2447 if (this.hasBuffer) {
2448 var result = Buffer.concat([this.value, target.value]);
2449 return new ByteString(result, GP.HEX)
2450 }
2451 var result = this.value + target.value,
2452 x = new ByteString(result, GP.ASCII);
2453 return x.encoding = this.encoding, x
2454}, ByteString.prototype.equals = function(target) {
2455 if (arguments.length < 1) throw "Not enough arguments";
2456 if (!(target instanceof ByteString)) throw "Invalid argument";
2457 return this.hasBuffer ? Buffer.equals(this.value, target.value) : this.value == target.value
2458}, ByteString.prototype.toString = function(encoding) {
2459 var targetEncoding = this.encoding;
2460 if (arguments.length >= 1) {
2461 if ("number" != typeof encoding) throw "Invalid encoding";
2462 switch (encoding) {
2463 case GP.HEX:
2464 case GP.ASCII:
2465 targetEncoding = encoding;
2466 break;
2467 default:
2468 throw "Unsupported arguments"
2469 }
2470 targetEncoding = encoding
2471 }
2472 switch (targetEncoding) {
2473 case GP.HEX:
2474 return this.hasBuffer ? this.value.toString("hex") : Convert.stringToHex(this.value);
2475 case GP.ASCII:
2476 return this.hasBuffer ? this.value.toString() : this.value;
2477 default:
2478 throw "Unsupported"
2479 }
2480}, ByteString.prototype.toStringIE = function(encoding) {
2481 return this.toString(encoding)
2482}, ByteString.prototype.toBuffer = function() {
2483 return this.value
2484}, angular.module("copayApp.services").factory("localStorageService", function(platformInfo, $timeout, $log) {
2485 var isNW = platformInfo.isNW,
2486 isChromeApp = platformInfo.isChromeApp,
2487 root = {},
2488 ls = void 0 !== window.localStorage ? window.localStorage : null;
2489 if (!isChromeApp || isNW || ls || ($log.info("Using CHROME storage"), ls = chrome.storage.local), !ls) throw new Error("localstorage not available");
2490 if (root.get = function(k, cb) {
2491 if (!isChromeApp && !isNW) return cb(null, ls.getItem(k));
2492 chrome.storage.local.get(k, function(data) {
2493 return cb(null, data[k])
2494 })
2495 }, root.create = function(name, value, callback) {
2496 root.get(name, function(err, data) {
2497 return data ? callback("EEXISTS") : root.set(name, value, callback)
2498 })
2499 }, root.set = function(k, v, cb) {
2500 if (!isChromeApp && !isNW) return ls.setItem(k, v), cb();
2501 var obj = {};
2502 obj[k] = v, chrome.storage.local.set(obj, cb)
2503 }, root.remove = function(k, cb) {
2504 if (!isChromeApp && !isNW) return ls.removeItem(k), cb();
2505 chrome.storage.local.remove(k, cb)
2506 }, isNW) {
2507 $log.info("Using chrome storage for NW.JS");
2508 var ts = ls.getItem("migrationToChromeStorage"),
2509 p = ls.getItem("profile");
2510 root.get("profile", function(err, newP) {
2511 if (ts || newP || !p) p && $log.info("# Data already migrated to Chrome storage." + (ts || ""));
2512 else {
2513 $log.info("### MIGRATING DATA! TO CHROME STORAGE");
2514 for (var j = 0, i = 0; i < localStorage.length; i++) {
2515 var k = ls.key(i),
2516 v = ls.getItem(k);
2517 $log.debug(" Key: " + k), root.set(k, v, function() {
2518 ++j == localStorage.length && ($log.info("### MIGRATION DONE"), ls.setItem("migrationToChromeStorage", Date.now()), ls = chrome.storage.local)
2519 })
2520 }
2521 }
2522 })
2523 }
2524 return root
2525}), angular.module("copayApp.services").factory("logHeader", function($log, platformInfo) {
2526 return $log.info("Starting " + window.version + " #" + window.commitHash), $log.info("Client: " + JSON.stringify(platformInfo)), {}
2527}), angular.module("copayApp.services").factory("nodeWebkit", function() {
2528 var root = {},
2529 isNodeWebkit = function() {
2530 if ("undefined" != typeof process && "undefined" != typeof require) try {
2531 return void 0 !== require("nw.gui")
2532 } catch (e) {
2533 return !1
2534 }
2535 };
2536 return root.readFromClipboard = function() {
2537 if (isNodeWebkit()) {
2538 return require("nw.gui").Clipboard.get().get()
2539 }
2540 }, root.writeToClipboard = function(text) {
2541 if (isNodeWebkit()) {
2542 return require("nw.gui").Clipboard.get().set(text)
2543 }
2544 }, root.openExternalLink = function(url) {
2545 if (isNodeWebkit()) {
2546 return require("nw.gui").Shell.openExternal(url)
2547 }
2548 }, root
2549}), angular.module("copayApp.services").factory("notification", function($timeout, platformInfo) {
2550 function html5Notify(icon, title, content, ondisplay, onclose) {
2551 if (window.webkitNotifications && 0 === window.webkitNotifications.checkPermission()) {
2552 icon || (icon = "img/favicon.ico");
2553 var noti = window.webkitNotifications.createNotification(icon, title, content);
2554 "function" == typeof ondisplay && (noti.ondisplay = ondisplay), "function" == typeof onclose && (noti.onclose = onclose), noti.show()
2555 } else settings.html5Mode = !1
2556 }
2557 var isCordova = platformInfo.isCordova,
2558 notifications = [],
2559 queue = [],
2560 settings = {
2561 info: {
2562 duration: 6e3,
2563 enabled: !0
2564 },
2565 funds: {
2566 duration: 7e3,
2567 enabled: !0
2568 },
2569 version: {
2570 duration: 6e4,
2571 enabled: !0
2572 },
2573 warning: {
2574 duration: 7e3,
2575 enabled: !0
2576 },
2577 error: {
2578 duration: 7e3,
2579 enabled: !0
2580 },
2581 success: {
2582 duration: 5e3,
2583 enabled: !0
2584 },
2585 progress: {
2586 duration: 0,
2587 enabled: !0
2588 },
2589 custom: {
2590 duration: 35e3,
2591 enabled: !0
2592 },
2593 details: !0,
2594 localStorage: !1,
2595 html5Mode: !1,
2596 html5DefaultIcon: "img/favicon.ico"
2597 };
2598 return {
2599 disableHtml5Mode: function() {
2600 settings.html5Mode = !1
2601 },
2602 disableType: function(notificationType) {
2603 settings[notificationType].enabled = !1
2604 },
2605 enableHtml5Mode: function() {
2606 settings.html5Mode = this.requestHtml5ModePermissions()
2607 },
2608 enableType: function(notificationType) {
2609 settings[notificationType].enabled = !0
2610 },
2611 getSettings: function() {
2612 return settings
2613 },
2614 toggleType: function(notificationType) {
2615 settings[notificationType].enabled = !settings[notificationType].enabled
2616 },
2617 toggleHtml5Mode: function() {
2618 settings.html5Mode = !settings.html5Mode
2619 },
2620 requestHtml5ModePermissions: function() {
2621 return !!window.webkitNotifications && (0 === window.webkitNotifications.checkPermission() || (window.webkitNotifications.requestPermission(function() {
2622 0 === window.webkitNotifications.checkPermission() ? settings.html5Mode = !0 : settings.html5Mode = !1
2623 }), !1))
2624 },
2625 getAll: function() {
2626 return notifications
2627 },
2628 getQueue: function() {
2629 return queue
2630 },
2631 info: function(title, content, userData) {
2632 return this.awesomeNotify("info", "fi-info", title, content, userData)
2633 },
2634 funds: function(title, content, userData) {
2635 return this.awesomeNotify("funds", "icon-receive", title, content, userData)
2636 },
2637 version: function(title, content, severe) {
2638 return this.awesomeNotify("version", severe ? "fi-alert" : "fi-flag", title, content)
2639 },
2640 error: function(title, content, userData) {
2641 return this.awesomeNotify("error", "fi-x", title, content, userData)
2642 },
2643 success: function(title, content, userData) {
2644 return this.awesomeNotify("success", "fi-check", title, content, userData)
2645 },
2646 warning: function(title, content, userData) {
2647 return this.awesomeNotify("warning", "fi-alert", title, content, userData)
2648 },
2649 new: function(title, content, userData) {
2650 return this.awesomeNotify("warning", "fi-plus", title, content, userData)
2651 },
2652 sent: function(title, content, userData) {
2653 return this.awesomeNotify("warning", "icon-paperplane", title, content, userData)
2654 },
2655 awesomeNotify: function(type, icon, title, content, userData) {
2656 return this.makeNotification(type, !1, icon, title, content, userData)
2657 },
2658 notify: function(image, title, content, userData) {
2659 return this.makeNotification("custom", image, !0, title, content, userData)
2660 },
2661 makeNotification: function(type, image, icon, title, content, userData) {
2662 var notification = {
2663 type: type,
2664 image: image,
2665 icon: icon,
2666 title: title,
2667 content: content,
2668 timestamp: +new Date,
2669 userData: userData
2670 };
2671 return notifications.push(notification), settings.html5Mode && html5Notify(image, title, content, function() {}, function() {}), settings.html5Mode || (queue.push(notification), $timeout(function() {
2672 queue.splice(queue.indexOf(notification), 1)
2673 }, settings[type].duration)), window && window.navigator && window.navigator.vibrate && window.navigator.vibrate([200, 100, 200]), !document.hidden || "info" != type && "funds" != type || isCordova || new window.Notification(title, {
2674 body: content,
2675 icon: "img/notification.png"
2676 }), this.save(), notification
2677 },
2678 save: function() {
2679 settings.localStorage && localStorage.setItem("notifications", JSON.stringify(notifications))
2680 },
2681 restore: function() {},
2682 clear: function() {
2683 notifications = [], this.save()
2684 }
2685 }
2686}).directive("notifications", function(notification, $compile) {
2687 function link(scope, element, attrs) {
2688 var position = attrs.notifications;
2689 position = position.split(" "), element.addClass("dr-notification-container");
2690 for (var i = 0; i < position.length; i++) element.addClass(position[i])
2691 }
2692 return {
2693 restrict: "A",
2694 scope: {},
2695 templateUrl: "views/includes/notifications.html",
2696 link: link,
2697 controller: ["$scope", function($scope) {
2698 $scope.queue = notification.getQueue(), $scope.removeNotification = function(noti) {
2699 $scope.queue.splice($scope.queue.indexOf(noti), 1)
2700 }
2701 }]
2702 }
2703}), angular.module("copayApp.services").factory("notificationService", function($filter, notification, lodash, configService, gettext) {
2704 var root = {},
2705 lastNotificationOnWallet = {};
2706 return root.getLast = function(walletId) {
2707 var last = lastNotificationOnWallet[walletId];
2708 return last && Date.now() - last.ts < 5e3 ? last : null
2709 }, root.storeLast = function(notificationData, walletId) {
2710 "NewAddress" != notificationData.type && (lastNotificationOnWallet[walletId] = {
2711 creatorId: notificationData.creatorId,
2712 type: notificationData.type,
2713 ts: Date.now()
2714 })
2715 }, root.shouldSkip = function(notificationData, last) {
2716 return !!last && ("NewTxProposal" === last.type && "TxProposalAcceptedBy" === notificationData.type || ("TxProposalFinallyAccepted" === last.type && "NewOutgoingTx" === notificationData.type || "TxProposalRejectedBy" === last.type && "TxProposalFinallyRejected" === notificationData.type))
2717 }, root.newBWCNotification = function(notificationData, walletId, walletName) {
2718 var last = root.getLast(walletId);
2719 if (root.storeLast(notificationData, walletId), !root.shouldSkip(notificationData, last)) {
2720 var config = configService.getSync();
2721 config.colorFor = config.colorFor || {};
2722 var color = config.colorFor[walletId] || "#4A90E2",
2723 name = config.aliasFor[walletId] || walletName;
2724 switch (notificationData.type) {
2725 case "NewTxProposal":
2726 notification.new(gettext("New Payment Proposal"), name, {
2727 color: color
2728 });
2729 break;
2730 case "TxProposalAcceptedBy":
2731 notification.success(gettext("Payment Proposal Signed by Copayer"), name, {
2732 color: color
2733 });
2734 break;
2735 case "TxProposalRejectedBy":
2736 notification.error(gettext("Payment Proposal Rejected by Copayer"), name, {
2737 color: color
2738 });
2739 break;
2740 case "TxProposalFinallyRejected":
2741 notification.error(gettext("Payment Proposal Rejected"), name, {
2742 color: color
2743 });
2744 break;
2745 case "NewOutgoingTx":
2746 notification.sent(gettext("Payment Sent"), name, {
2747 color: color
2748 });
2749 break;
2750 case "NewIncomingTx":
2751 break;
2752 case "ScanFinished":
2753 notification.success(gettext("Scan Finished"), name, {
2754 color: color
2755 })
2756 }
2757 }
2758 }, root
2759}), angular.module("copayApp.services").factory("ongoingProcess", function($log, $timeout, $filter, lodash, $ionicLoading, gettext, platformInfo) {
2760 var root = {},
2761 isCordova = platformInfo.isCordova,
2762 ongoingProcess = {},
2763 processNames = {
2764 scanning: gettext("Scanning Wallet funds..."),
2765 recreating: gettext("Recreating Wallet..."),
2766 generatingCSV: gettext("Generating .csv file..."),
2767 creatingTx: gettext("Creating transaction"),
2768 sendingTx: gettext("Sending transaction"),
2769 signingTx: gettext("Signing transaction"),
2770 broadcastingTx: gettext("Broadcasting transaction"),
2771 rejectTx: gettext("Rejecting payment proposal"),
2772 removeTx: gettext("Deleting payment proposal"),
2773 fetchingPayPro: gettext("Fetching Payment Information"),
2774 calculatingFee: gettext("Calculating fee"),
2775 joiningWallet: gettext("Joining Wallet..."),
2776 retrivingInputs: gettext("Retrieving inputs information"),
2777 creatingWallet: gettext("Creating Wallet..."),
2778 validatingWallet: gettext("Validating wallet integrity..."),
2779 connectingledger: gettext("Waiting for Ledger..."),
2780 connectingtrezor: gettext("Waiting for Trezor..."),
2781 validatingWords: gettext("Validating recovery phrase..."),
2782 connectingCoinbase: gettext("Connecting to Coinbase..."),
2783 connectingGlidera: gettext("Connecting to Glidera..."),
2784 importingWallet: gettext("Importing Wallet..."),
2785 sweepingWallet: gettext("Sweeping Wallet..."),
2786 deletingWallet: gettext("Deleting Wallet..."),
2787 extractingWalletInfo: gettext("Extracting Wallet Information...")
2788 };
2789 return root.clear = function() {
2790 ongoingProcess = {}, isCordova ? window.plugins.spinnerDialog.hide() : $ionicLoading.hide()
2791 }, root.get = function(processName) {
2792 return ongoingProcess[processName]
2793 }, root.set = function(processName, isOn) {
2794 $log.debug("ongoingProcess", processName, isOn), root[processName] = isOn, ongoingProcess[processName] = isOn;
2795 var name;
2796 root.any = lodash.any(ongoingProcess, function(isOn, processName) {
2797 return isOn && (name = name || processName), isOn
2798 }), root.onGoingProcessName = name;
2799 var showName = $filter("translate")(processNames[name] || name);
2800 if (root.onGoingProcessName)
2801 if (isCordova) window.plugins.spinnerDialog.show(null, showName, !0);
2802 else {
2803 var tmpl = '<ion-spinner class="spinner-stable" icon="lines"></ion-spinner>' + showName;
2804 $ionicLoading.show({
2805 template: tmpl
2806 })
2807 }
2808 else isCordova ? window.plugins.spinnerDialog.hide() : $ionicLoading.hide()
2809 }, root
2810}), angular.module("copayApp.services").factory("openURLService", function($rootScope, $ionicHistory, $document, $log, $state, go, platformInfo, lodash, profileService) {
2811 var root = {};
2812 root.registeredUriHandlers = [{
2813 name: "Neblio BIP21 URL",
2814 startsWith: "neblio:",
2815 transitionTo: "uripayment"
2816 }, {
2817 name: "Glidera Authentication Callback",
2818 startsWith: "copay:glidera",
2819 transitionTo: "uriglidera"
2820 }, {
2821 name: "Coinbase Authentication Callback",
2822 startsWith: "copay:coinbase",
2823 transitionTo: "uricoinbase"
2824 }];
2825 var handleOpenURL = function(args) {
2826 if ($log.info("Handling Open URL: " + JSON.stringify(args)), !profileService.isBound) return $log.warn("Profile not bound yet. Waiting"), $rootScope.$on("Local/ProfileBound", function() {
2827 setTimeout(function() {
2828 $log.warn("Profile ready, retrying..."), handleOpenURL(args)
2829 }, 2e3)
2830 });
2831 $ionicHistory.nextViewOptions({
2832 historyRoot: !0,
2833 disableBack: !0,
2834 disableAnimation: !0
2835 });
2836 var url = args.url;
2837 if (!url) return void $log.error("No url provided");
2838 url && ("cordova" in window && (window.cordova.removeDocumentEventHandler("handleopenurl"), window.cordova.addStickyDocumentEventHandler("handleopenurl")), document.removeEventListener("handleopenurl", handleOpenURL)), document.addEventListener("handleopenurl", handleOpenURL, !1);
2839 var x = lodash.find(root.registeredUriHandlers, function(x) {
2840 return 0 == url.indexOf(x.startsWith) || 0 == url.indexOf("web+" + x.startsWith) || 0 == url.indexOf(x.startsWith.replace(":", "://"))
2841 });
2842 if (x) return $log.debug("openURL GOT " + x.name + " URL"), $state.transitionTo(x.transitionTo, {
2843 url: url
2844 });
2845 $log.warn("Unknown URL! : " + url)
2846 },
2847 handleResume = function() {
2848 $log.debug("Handle Resume @ openURL..."), document.addEventListener("handleopenurl", handleOpenURL, !1)
2849 };
2850 return root.init = function() {
2851 if ($log.debug("Initializing openURL"), document.addEventListener("handleopenurl", handleOpenURL, !1), document.addEventListener("resume", handleResume, !1), platformInfo.isChromeApp) $log.debug("Registering Chrome message listener"), chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
2852 request.url && handleOpenURL(request.url)
2853 });
2854 else if (platformInfo.isNW) {
2855 var gui = require("nw.gui");
2856 gui.App.on("open", function(pathData) {
2857 -1 != pathData.indexOf("neblio:") ? ($log.debug("Neblio URL found"), handleOpenURL({
2858 url: pathData.substring(pathData.indexOf("neblio:"))
2859 })) : -1 != pathData.indexOf("copay:") && ($log.debug("Copay URL found"), handleOpenURL({
2860 url: pathData.substring(pathData.indexOf("copay:"))
2861 }))
2862 });
2863 var argv = gui.App.argv;
2864 argv && argv[0] && handleOpenURL({
2865 url: argv[0]
2866 })
2867 } else if (platformInfo.isDevel) {
2868 window.location.origin
2869 }
2870 }, root.registerHandler = function(x) {
2871 $log.debug("Registering URL Handler: " + x.name), root.registeredUriHandlers.push(x)
2872 }, root.handleURL = handleOpenURL, root
2873}), angular.module("copayApp.services").factory("platformInfo", function($window) {
2874 var ua = navigator ? navigator.userAgent : null;
2875 ua || (console.log("Could not determine navigator. Using fixed string"), ua = "dummy user-agent"), ua = ua.replace(/\(\d+\)$/, "");
2876 var ret = {
2877 isAndroid: !!ua.match(/Android/i),
2878 isIOS: /iPad|iPhone|iPod/.test(ua) && !$window.MSStream,
2879 isWP: !!ua.match(/IEMobile/i),
2880 isSafari: Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") > 0,
2881 ua: ua,
2882 isCordova: !!$window.cordova,
2883 isNW: function() {
2884 if ("undefined" != typeof process && "undefined" != typeof require) try {
2885 return void 0 !== require("nw.gui")
2886 } catch (e) {
2887 return !1
2888 }
2889 }()
2890 };
2891 return ret.isMobile = ret.isAndroid || ret.isIOS || ret.isWP, ret.isChromeApp = $window.chrome && chrome.runtime && chrome.runtime.id && !ret.isNW, ret.isDevel = !ret.isMobile && !ret.isChromeApp && !ret.isNW, ret
2892}), angular.module("copayApp.services").factory("profileService", function($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, notificationService, pushNotificationsService, gettext, gettextCatalog, bwcError, uxLanguage, bitcore, platformInfo, walletService) {
2893 var isChromeApp = platformInfo.isChromeApp,
2894 isCordova = platformInfo.isCordova,
2895 isWP = platformInfo.isWP,
2896 isIOS = platformInfo.isIOS,
2897 root = {},
2898 errors = bwcService.getErrors(),
2899 usePushNotifications = isCordova && !isWP;
2900 root.profile = null, root.focusedClient = null, root.walletClients = {}, root.Utils = bwcService.getUtils(), root.formatAmount = function(amount, fullPrecision) {
2901 var config = configService.getSync().wallet.settings;
2902 if ("sat" == config.unitCode) return amount;
2903 var opts = {
2904 fullPrecision: !!fullPrecision
2905 };
2906 return this.Utils.formatAmount(amount, config.unitCode, opts)
2907 }, root._setFocus = function(walletId, cb) {
2908 return $log.debug("Set focus:", walletId), root.focusedClient = walletId ? root.walletClients[walletId] : [], lodash.isEmpty(root.focusedClient) && (root.focusedClient = root.walletClients[lodash.keys(root.walletClients)[0]]), lodash.isEmpty(root.focusedClient) ? $rootScope.$emit("Local/NoWallets") : ($rootScope.$emit("Local/NewFocusedWallet"), lodash.each(root.walletClients, function(client, id) {
2909 client.setNotificationsInterval(30)
2910 }), root.focusedClient.setNotificationsInterval(5)), cb()
2911 }, root.setAndStoreFocus = function(walletId, cb) {
2912 root._setFocus(walletId, function() {
2913 storageService.storeFocusedWalletId(walletId, cb)
2914 })
2915 }, root.bindWalletClient = function(client, opts) {
2916 var opts = opts || {},
2917 walletId = client.credentials.walletId;
2918 return !(root.walletClients[walletId] && root.walletClients[walletId].started || opts.force) && (root.walletClients[walletId] = client, root.walletClients[walletId].started = !0, root.walletClients[walletId].doNotVerifyPayPro = isChromeApp, client.removeAllListeners(), client.on("report", function(n) {
2919 $log.info("BWC Report:" + n)
2920 }), client.on("notification", function(n) {
2921 $log.debug("BWC Notification:", n), notificationService.newBWCNotification(n, walletId, client.credentials.walletName), root.focusedClient.credentials.walletId == walletId ? $rootScope.$emit(n.type, n) : $rootScope.$apply()
2922 }), client.on("walletCompleted", function() {
2923 $log.debug("Wallet completed"), root.updateCredentials(JSON.parse(client.export()), function() {
2924 $rootScope.$emit("Local/WalletCompleted", walletId)
2925 })
2926 }), client.hasPrivKeyEncrypted() && !client.isPrivKeyEncrypted() && ($log.warn("Auto locking unlocked wallet:" + walletId), client.lock()), client.initialize({}, function(err) {
2927 if (err) return void $log.error("Could not init notifications err:", err);
2928 client.setNotificationsInterval(30)
2929 }), !0)
2930 };
2931 var validationLock = !1;
2932 root.runValidation = function(client, delay, retryDelay) {
2933 if (delay = delay || 500, retryDelay = retryDelay || 50, validationLock) return $timeout(function() {
2934 return $log.debug("ValidatingWallet Locked: Retrying in: " + retryDelay), root.runValidation(client, delay, retryDelay)
2935 }, retryDelay);
2936 validationLock = !0;
2937 var skipDeviceValidation = isIOS || root.profile.isDeviceChecked(platformInfo.ua),
2938 walletId = client.credentials.walletId;
2939 $log.debug("ValidatingWallet: " + walletId + " skip Device:" + skipDeviceValidation), $timeout(function() {
2940 client.validateKeyDerivation({
2941 skipDeviceValidation: skipDeviceValidation
2942 }, function(err, isOK) {
2943 validationLock = !1, $log.debug("ValidatingWallet End: " + walletId + " isOK:" + isOK), isOK ? root.profile.setChecked(platformInfo.ua, walletId) : ($log.warn("Key Derivation failed for wallet:" + walletId), storageService.clearLastAddress(walletId, function() {})), root.storeProfileIfDirty(), $rootScope.$emit("Local/ValidatingWalletEnded", walletId, isOK)
2944 })
2945 }, delay)
2946 }, root.bindWallet = function(credentials, cb) {
2947 if (!credentials.walletId) return cb("bindWallet should receive credentials JSON");
2948 var client = bwcService.getClient(JSON.stringify(credentials), {
2949 bwsurl: function(walletId) {
2950 var config = configService.getSync(),
2951 defaults = configService.getDefaults();
2952 return config.bwsFor && config.bwsFor[walletId] || defaults.bws.url
2953 }(credentials.walletId)
2954 }),
2955 skipKeyValidation = root.profile.isChecked(platformInfo.ua, credentials.walletId);
2956 return skipKeyValidation || root.runValidation(client, 500), $log.info("Binding wallet:" + credentials.walletId + " Validating?:" + !skipKeyValidation), cb(null, root.bindWalletClient(client))
2957 }, root.bindProfile = function(profile, cb) {
2958 root.profile = profile, configService.get(function(err) {
2959 if ($log.debug("Preferences read"), err) return cb(err);
2960 ! function(cb) {
2961 var l = root.profile.credentials.length,
2962 i = 0,
2963 totalBound = 0;
2964 if (!l) return cb();
2965 lodash.each(root.profile.credentials, function(credentials) {
2966 root.bindWallet(credentials, function(err, bound) {
2967 if (i++, totalBound += bound, i == l) return $log.info("Bound " + totalBound + " out of " + l + " wallets"), totalBound && $rootScope.$emit("Local/WalletListUpdated"), cb()
2968 })
2969 })
2970 }(function() {
2971 storageService.getFocusedWalletId(function(err, focusedWalletId) {
2972 if (err) return cb(err);
2973 root._setFocus(focusedWalletId, function() {
2974 usePushNotifications && root.pushNotificationsInit(), root.isBound = !0, $rootScope.$emit("Local/ProfileBound"), root.isDisclaimerAccepted(function(val) {
2975 return val ? ($rootScope.$emit("disclaimerAccepted"), cb()) : cb(new Error("NONAGREEDDISCLAIMER: Non agreed disclaimer"))
2976 })
2977 })
2978 })
2979 })
2980 })
2981 }, root.pushNotificationsInit = function() {
2982 configService.getDefaults();
2983 pushNotificationsService.init(root.walletClients).on("notification", function(data) {
2984 data.additionalData.foreground || ($log.debug("Push notification event: ", data.message), $timeout(function() {
2985 var wallets = root.getWallets(),
2986 walletToFind = data.additionalData.walletId,
2987 walletFound = lodash.find(wallets, function(w) {
2988 return lodash.isEqual(walletToFind, sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(w.id)))
2989 });
2990 if (!walletFound) return $log.debug("Wallet not found");
2991 root.setAndStoreFocus(walletFound.id, function() {})
2992 }, 100))
2993 })
2994 }, root.loadAndBindProfile = function(cb) {
2995 storageService.getProfile(function(err, profile) {
2996 return err ? ($rootScope.$emit("Local/DeviceError", err), cb(err)) : profile ? ($log.debug("Profile read"), root.bindProfile(profile, cb)) : void storageService.tryToMigrate(function(err, migratedProfile) {
2997 return err ? cb(err) : migratedProfile ? (profile = migratedProfile, root.bindProfile(profile, cb)) : cb(new Error("NOPROFILE: No profile"))
2998 })
2999 })
3000 };
3001 var seedWallet = function(opts, cb) {
3002 opts = opts || {};
3003 var walletClient = bwcService.getClient(null, opts),
3004 network = opts.networkName || "livenet";
3005 if ($log.debug("network: " + opts.networkName), opts.mnemonic) try {
3006 opts.mnemonic = root._normalizeMnemonic(opts.mnemonic), walletClient.seedFromMnemonic(opts.mnemonic, {
3007 network: network,
3008 passphrase: opts.passphrase,
3009 account: opts.account || 0,
3010 derivationStrategy: opts.derivationStrategy || "BIP44"
3011 })
3012 } catch (ex) {
3013 return $log.info(ex), cb(gettext("Could not create: Invalid wallet recovery phrase"))
3014 } else if (opts.extendedPrivateKey) try {
3015 walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey)
3016 } catch (ex) {
3017 return $log.warn(ex), cb(gettext("Could not create using the specified extended private key"))
3018 } else if (opts.extendedPublicKey) try {
3019 walletClient.seedFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
3020 account: opts.account || 0,
3021 derivationStrategy: opts.derivationStrategy || "BIP44"
3022 })
3023 } catch (ex) {
3024 return $log.warn("Creating wallet from Extended Public Key Arg:", ex, opts), cb(gettext("Could not create using the specified extended public key"))
3025 } else {
3026 var lang = uxLanguage.getCurrentLanguage();
3027 try {
3028 walletClient.seedFromRandomWithMnemonic({
3029 network: network,
3030 passphrase: opts.passphrase,
3031 language: lang,
3032 account: 0
3033 })
3034 } catch (e) {
3035 if ($log.info("Error creating recovery phrase: " + e.message), !(e.message.indexOf("language") > 0)) return cb(e);
3036 $log.info("Using default language for recovery phrase"), walletClient.seedFromRandomWithMnemonic({
3037 network: network,
3038 passphrase: opts.passphrase,
3039 account: 0
3040 })
3041 }
3042 }
3043 return cb(null, walletClient)
3044 },
3045 doCreateWallet = function(opts, cb) {
3046 $log.debug("Creating Wallet:", opts), $timeout(function() {
3047 seedWallet(opts, function(err, walletClient) {
3048 if (err) return cb(err);
3049 var name = opts.name || gettextCatalog.getString("Personal Wallet"),
3050 myName = opts.myName || gettextCatalog.getString("me");
3051 walletClient.createWallet(name, myName, opts.m, opts.n, {
3052 network: opts.networkName,
3053 singleAddress: !0,
3054 walletPrivKey: opts.walletPrivKey
3055 }, function(err, secret) {
3056 return err ? bwcError.cb(err, gettext("Error creating wallet"), cb) : cb(null, walletClient, secret)
3057 })
3058 })
3059 }, 50)
3060 };
3061 return root.createDefaultProfile = function(opts, cb) {
3062 var p = Profile.create();
3063 if (opts.noWallet) return cb(null, p);
3064 opts.m = 1, opts.n = 1, opts.networkName = "livenet", doCreateWallet(opts, function(err, walletClient) {
3065 return err ? cb(err) : (p.addWallet(JSON.parse(walletClient.export())), cb(null, p))
3066 })
3067 }, root.createWallet = function(opts, cb) {
3068 doCreateWallet(opts, function(err, walletClient, secret) {
3069 if (err) return cb(err);
3070 root.addAndBindWalletClient(walletClient, {
3071 bwsurl: opts.bwsurl
3072 }, cb)
3073 })
3074 }, root.joinWallet = function(opts, cb) {
3075 bwcService.getClient();
3076 $log.debug("Joining Wallet:", opts);
3077 try {
3078 var walletData = bwcService.parseSecret(opts.secret);
3079 if (lodash.find(root.profile.credentials, {
3080 walletId: walletData.walletId
3081 })) return cb(gettext("Cannot join the same wallet more that once"))
3082 } catch (ex) {
3083 return $log.debug(ex), cb(gettext("Bad wallet invitation"))
3084 }
3085 opts.networkName = walletData.network, $log.debug("Joining Wallet:", opts), seedWallet(opts, function(err, walletClient) {
3086 if (err) return cb(err);
3087 walletClient.joinWallet(opts.secret, opts.myName || "me", {}, function(err) {
3088 if (err) return bwcError.cb(err, gettext("Could not join wallet"), cb);
3089 root.addAndBindWalletClient(walletClient, {
3090 bwsurl: opts.bwsurl
3091 }, cb)
3092 })
3093 })
3094 }, root.getClient = function(walletId) {
3095 return root.walletClients[walletId]
3096 }, root.deleteWalletClient = function(client, cb) {
3097 var walletId = client.credentials.walletId;
3098 pushNotificationsService.unsubscribe(root.getClient(walletId), function(err) {
3099 err ? $log.warn("Unsubscription error: " + err.message) : $log.debug("Unsubscribed from push notifications service")
3100 }), $log.debug("Deleting Wallet:", client.credentials.walletName), client.removeAllListeners(), root.profile.deleteWallet(walletId), delete root.walletClients[walletId], root.focusedClient = null, storageService.removeAllWalletData(walletId, function(err) {
3101 err && $log.warn(err)
3102 }), $timeout(function() {
3103 $rootScope.$emit("Local/WalletListUpdated"), root.setAndStoreFocus(null, function() {
3104 storageService.storeProfile(root.profile, function(err) {
3105 return err ? cb(err) : cb()
3106 })
3107 })
3108 })
3109 }, root.setMetaData = function(walletClient, addressBook, cb) {
3110 storageService.getAddressbook(walletClient.credentials.network, function(err, localAddressBook) {
3111 var localAddressBook1 = {};
3112 try {
3113 localAddressBook1 = JSON.parse(localAddressBook)
3114 } catch (ex) {
3115 $log.warn(ex)
3116 }
3117 lodash.merge(addressBook, localAddressBook1);
3118 storageService.setAddressbook(walletClient.credentials.network, JSON.stringify(addressBook), function(err) {
3119 return cb(err ? err : null)
3120 })
3121 })
3122 }, root.addAndBindWalletClient = function(client, opts, cb) {
3123 if (!client || !client.credentials) return cb(gettext("Could not access wallet"));
3124 var walletId = client.credentials.walletId;
3125 if (!root.profile.addWallet(JSON.parse(client.export()))) return cb(gettext("Wallet already in Copay"));
3126 root.profile.isChecked(platformInfo.ua, walletId) || root.runValidation(client), root.bindWalletClient(client), $rootScope.$emit("Local/WalletListUpdated", client);
3127 walletService.updateRemotePreferences(client, {}, function() {
3128 $log.debug("Remote preferences saved for:" + walletId)
3129 }),
3130 function(cb) {
3131 var defaults = configService.getDefaults(),
3132 bwsFor = {};
3133 if (bwsFor[walletId] = opts.bwsurl || defaults.bws.url, bwsFor[walletId] == defaults.bws.url) return cb();
3134 configService.set({
3135 bwsFor: bwsFor
3136 }, function(err) {
3137 return err && $log.warn(err), cb()
3138 })
3139 }(function() {
3140 root.setAndStoreFocus(walletId, function() {
3141 storageService.storeProfile(root.profile, function(err) {
3142 return configService.getSync().pushNotifications.enabled && pushNotificationsService.enableNotifications(root.walletClients), cb(err, walletId)
3143 })
3144 })
3145 })
3146 }, root.storeProfileIfDirty = function(cb) {
3147 if (root.profile.dirty) storageService.storeProfile(root.profile, function(err) {
3148 if ($log.debug("Saved modified Profile"), cb) return cb(err)
3149 });
3150 else if (cb) return cb()
3151 }, root.importWallet = function(str, opts, cb) {
3152 var walletClient = bwcService.getClient(null, opts);
3153 $log.debug("Importing Wallet:", opts);
3154 try {
3155 walletClient.import(str, {
3156 compressed: opts.compressed,
3157 password: opts.password
3158 })
3159 } catch (err) {
3160 return cb(gettext("Could not import. Check input file and spending password"))
3161 }
3162 if (walletClient.hasPrivKeyEncrypted()) try {
3163 walletClient.disablePrivateKeyEncryption()
3164 } catch (e) {
3165 $log.warn(e)
3166 }
3167 str = JSON.parse(str);
3168 var addressBook = str.addressBook || {};
3169 root.addAndBindWalletClient(walletClient, {
3170 bwsurl: opts.bwsurl
3171 }, function(err, walletId) {
3172 if (err) return cb(err);
3173 root.setMetaData(walletClient, addressBook, function(error) {
3174 return error && $log.warn(error), cb(err, walletId)
3175 })
3176 })
3177 }, root.importExtendedPrivateKey = function(xPrivKey, opts, cb) {
3178 var walletClient = bwcService.getClient(null, opts);
3179 $log.debug("Importing Wallet xPrivKey"), walletClient.importFromExtendedPrivateKey(xPrivKey, opts, function(err) {
3180 if (err) return err instanceof errors.NOT_AUTHORIZED ? cb(err) : bwcError.cb(err, gettext("Could not import"), cb);
3181 root.addAndBindWalletClient(walletClient, {
3182 bwsurl: opts.bwsurl
3183 }, cb)
3184 })
3185 }, root._normalizeMnemonic = function(words) {
3186 var isJA = words.indexOf(" ") > -1;
3187 return words.split(/[\u3000\s]+/).join(isJA ? " " : " ")
3188 }, root.importMnemonic = function(words, opts, cb) {
3189 var walletClient = bwcService.getClient(null, opts);
3190 $log.debug("Importing Wallet Mnemonic"), words = root._normalizeMnemonic(words), walletClient.importFromMnemonic(words, {
3191 network: opts.networkName,
3192 passphrase: opts.passphrase,
3193 account: opts.account || 0
3194 }, function(err) {
3195 if (err) return err instanceof errors.NOT_AUTHORIZED ? cb(err) : bwcError.cb(err, gettext("Could not import"), cb);
3196 root.addAndBindWalletClient(walletClient, {
3197 bwsurl: opts.bwsurl
3198 }, cb)
3199 })
3200 }, root.importExtendedPublicKey = function(opts, cb) {
3201 var walletClient = bwcService.getClient(null, opts);
3202 $log.debug("Importing Wallet XPubKey"), walletClient.importFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
3203 account: opts.account || 0,
3204 derivationStrategy: opts.derivationStrategy || "BIP44"
3205 }, function(err) {
3206 if (err) return err instanceof errors.NOT_AUTHORIZED && (err.name = "WALLET_DOES_NOT_EXIST"), bwcError.cb(err, gettext("Could not import"), cb);
3207 root.addAndBindWalletClient(walletClient, {
3208 bwsurl: opts.bwsurl
3209 }, cb)
3210 })
3211 }, root.create = function(opts, cb) {
3212 $log.info("Creating profile", opts);
3213 configService.getDefaults();
3214 configService.get(function(err) {
3215 root.createDefaultProfile(opts, function(err, p) {
3216 if (err) return cb(err);
3217 storageService.storeNewProfile(p, function(err) {
3218 if (err) return cb(err);
3219 root.bindProfile(p, function(err) {
3220 return err && err.toString().match("NONAGREEDDISCLAIMER") ? cb() : cb(err)
3221 })
3222 })
3223 })
3224 })
3225 }, root.setDisclaimerAccepted = function(cb) {
3226 root.profile.disclaimerAccepted = !0, storageService.storeProfile(root.profile, function(err) {
3227 return cb(err)
3228 })
3229 }, root.isDisclaimerAccepted = function(cb) {
3230 if (root.profile && root.profile.disclaimerAccepted) return cb(!0);
3231 storageService.getCopayDisclaimerFlag(function(err, val) {
3232 return val ? (root.profile.disclaimerAccepted = !0, cb(!0)) : cb()
3233 })
3234 }, root.updateCredentials = function(credentials, cb) {
3235 root.profile.updateWallet(credentials), storageService.storeProfile(root.profile, cb)
3236 }, root.getClients = function() {
3237 return lodash.values(root.walletClients)
3238 }, root.needsBackup = function(client, cb) {
3239 if (!walletService.needsBackup(client)) return cb(!1);
3240 storageService.getBackupFlag(client.credentials.walletId, function(err, val) {
3241 return err && $log.error(err), cb(val ? !1 : !0)
3242 })
3243 }, root.isReady = function(client, cb) {
3244 if (!client.isComplete()) return cb("WALLET_NOT_COMPLETE");
3245 root.needsBackup(client, function(needsBackup) {
3246 return needsBackup ? cb("WALLET_NEEDS_BACKUP") : cb()
3247 })
3248 }, root.getWallets = function(network, n) {
3249 if (!root.profile) return [];
3250 var config = configService.getSync();
3251 config.colorFor = config.colorFor || {}, config.aliasFor = config.aliasFor || {};
3252 var ret = lodash.map(root.profile.credentials, function(c) {
3253 return {
3254 m: c.m,
3255 n: c.n,
3256 name: config.aliasFor[c.walletId] || c.walletName,
3257 id: c.walletId,
3258 network: c.network,
3259 color: config.colorFor[c.walletId] || "#4A90E2",
3260 copayerId: c.copayerId
3261 }
3262 });
3263 return network && (ret = lodash.filter(ret, function(w) {
3264 return w.network == network
3265 })), n && (ret = lodash.filter(ret, function(w) {
3266 return w.n == n
3267 })), lodash.sortBy(ret, "name")
3268 }, root
3269}), angular.module("copayApp.services").factory("pushNotificationsService", function($log, platformInfo, storageService, configService, lodash) {
3270 var root = {},
3271 isCordova = platformInfo.isCordova,
3272 isWP = platformInfo.isWP,
3273 isIOS = platformInfo.isIOS,
3274 isAndroid = platformInfo.isAndroid,
3275 usePushNotifications = isCordova && !isWP;
3276 return root.init = function(walletsClients) {
3277 var defaults = configService.getDefaults(),
3278 push = PushNotification.init(defaults.pushNotifications.config);
3279 return push.on("registration", function(data) {
3280 if (!root.token) {
3281 $log.debug("Starting push notification registration"), root.token = data.registrationId;
3282 configService.getSync().pushNotifications.enabled && root.enableNotifications(walletsClients)
3283 }
3284 }), push
3285 }, root.enableNotifications = function(walletsClients) {
3286 if (usePushNotifications) {
3287 if (configService.getSync().pushNotifications.enabled) return root.token ? void lodash.forEach(walletsClients, function(walletClient) {
3288 var opts = {};
3289 opts.type = isIOS ? "ios" : isAndroid ? "android" : null, opts.token = root.token, root.subscribe(opts, walletClient, function(err, response) {
3290 err ? $log.warn("Subscription error: " + err.message + ": " + JSON.stringify(opts)) : $log.debug("Subscribed to push notifications service: " + JSON.stringify(response))
3291 })
3292 }) : void $log.warn("No token available for this device. Cannot set push notifications")
3293 }
3294 }, root.disableNotifications = function(walletsClients) {
3295 usePushNotifications && lodash.forEach(walletsClients, function(walletClient) {
3296 root.unsubscribe(walletClient, function(err) {
3297 err ? $log.warn("Unsubscription error: " + err.message) : $log.debug("Unsubscribed from push notifications service")
3298 })
3299 })
3300 }, root.subscribe = function(opts, walletClient, cb) {
3301 if (!usePushNotifications) return cb();
3302 configService.getSync().pushNotifications.enabled && walletClient.pushNotificationsSubscribe(opts, function(err, resp) {
3303 return err ? cb(err) : cb(null, resp)
3304 })
3305 }, root.unsubscribe = function(walletClient, cb) {
3306 if (!usePushNotifications) return cb();
3307 walletClient.pushNotificationsUnsubscribe(function(err) {
3308 return cb(err ? err : null)
3309 })
3310 }, root
3311});
3312var RateService = function(opts) {
3313 var self = this;
3314 opts = opts || {}, self.httprequest = opts.httprequest, self.lodash = opts.lodash, self.SAT_TO_BTC = 1e-8, self.BTC_TO_SAT = 1e8, self.UNAVAILABLE_ERROR = "Service is not available - check for service.isAvailable() or use service.whenAvailable()", self.UNSUPPORTED_CURRENCY_ERROR = "Currency not supported", self._url = opts.url || "https://insight.bitpay.com:443/api/rates", self._isAvailable = !1, self._rates = {}, self._alternatives = [], self._queued = [], self._fetchCurrencies()
3315 },
3316 _instance;
3317RateService.singleton = function(opts) {
3318 return _instance || (_instance = new RateService(opts)), _instance
3319 }, RateService.prototype._fetchCurrencies = function() {
3320 var self = this,
3321 res = JSON.parse('[{"code":"BTC","name":"Bitcoin","rate":1},{"code":"USD","name":"US Dollar","rate":9707.13},{"code":"EUR","name":"Eurozone Euro","rate":7884.606635},{"code":"GBP","name":"Pound Sterling","rate":6934.501872},{"code":"JPY","name":"Japanese Yen","rate":1034358.876404},{"code":"CAD","name":"Canadian Dollar","rate":12265.638254},{"code":"AUD","name":"Australian Dollar","rate":12352.953888},{"code":"CNY","name":"Chinese Yuan","rate":61607.271258},{"code":"CHF","name":"Swiss Franc","rate":9068.35231},{"code":"SEK","name":"Swedish Krona","rate":79220.722743},{"code":"NZD","name":"New Zealand Dollar","rate":13271.908471},{"code":"KRW","name":"South Korean Won","rate":10416381.45345},{"code":"BCH","name":"Bitcoin Cash","rate":8.229869},{"code":"AED","name":"UAE Dirham","rate":35655.230082},{"code":"AFN","name":"Afghan Afghani","rate":674781.43482},{"code":"ALL","name":"Albanian Lek","rate":1042480.88925},{"code":"AMD","name":"Armenian Dram","rate":4670128.170413},{"code":"ANG","name":"Netherlands Antillean Guilder","rate":17348.650686},{"code":"AOA","name":"Angolan Kwanza","rate":2068026.38946},{"code":"ARS","name":"Argentine Peso","rate":189548.215371},{"code":"AWG","name":"Aruban Florin","rate":17351.494875},{"code":"AZN","name":"Azerbaijani Manat","rate":16388.062223},{"code":"BAM","name":"Bosnia-Herzegovina Convertible Mark","rate":15446.635634},{"code":"BBD","name":"Barbadian Dollar","rate":19414.26},{"code":"BDT","name":"Bangladeshi Taka","rate":806832.484553},{"code":"BGN","name":"Bulgarian Lev","rate":15440.160978},{"code":"BHD","name":"Bahraini Dinar","rate":3664.490111},{"code":"BIF","name":"Burundian Franc","rate":17068658.752375},{"code":"BMD","name":"Bermudan Dollar","rate":9707.13},{"code":"BND","name":"Brunei Dollar","rate":12848.803796},{"code":"BOB","name":"Bolivian Boliviano","rate":67162.78795},{"code":"BRL","name":"Brazilian Real","rate":31449.218017},{"code":"BSD","name":"Bahamian Dollar","rate":9707.13},{"code":"BTN","name":"Bhutanese Ngultrum","rate":629412.095312},{"code":"BWP","name":"Botswanan Pula","rate":92083.766899},{"code":"BZD","name":"Belize Dollar","rate":19535.647661},{"code":"CDF","name":"Congolese Franc","rate":15784994.579095},{"code":"CLF","name":"Chilean Unit of Account (UF)","rate":218.895782},{"code":"CLP","name":"Chilean Peso","rate":5732060.265},{"code":"COP","name":"Colombian Peso","rate":27790542.477},{"code":"CRC","name":"Costa Rican Colón","rate":5537370.590567},{"code":"CUP","name":"Cuban Peso","rate":247531.815},{"code":"CVE","name":"Cape Verdean Escudo","rate":872670.987},{"code":"CZK","name":"Czech Koruna","rate":199949.890523},{"code":"DJF","name":"Djiboutian Franc","rate":1719132.723},{"code":"DKK","name":"Danish Krone","rate":58719.011798},{"code":"DOP","name":"Dominican Peso","rate":478730.92754},{"code":"DZD","name":"Algerian Dinar","rate":1109855.00142},{"code":"EGP","name":"Egyptian Pound","rate":171481.305015},{"code":"ETB","name":"Ethiopian Birr","rate":266414.017498},{"code":"FJD","name":"Fijian Dollar","rate":19654.530882},{"code":"FKP","name":"Falkland Islands Pound","rate":6934.501872},{"code":"GEL","name":"Georgian Lari","rate":23934.36567},{"code":"GHS","name":"Ghanaian Cedi","rate":43373.48563},{"code":"GIP","name":"Gibraltar Pound","rate":6934.501872},{"code":"GMD","name":"Gambian Dalasi","rate":457691.1795},{"code":"GNF","name":"Guinean Franc","rate":87725760.5925},{"code":"GTQ","name":"Guatemalan Quetzal","rate":71586.162069},{"code":"GYD","name":"Guyanaese Dollar","rate":2012725.908513},{"code":"HKD","name":"Hong Kong Dollar","rate":75941.304773},{"code":"HNL","name":"Honduran Lempira","rate":229847.433516},{"code":"HRK","name":"Croatian Kuna","rate":58682.512989},{"code":"HTG","name":"Haitian Gourde","rate":625160.343251},{"code":"HUF","name":"Hungarian Forint","rate":2468717.3016},{"code":"IDR","name":"Indonesian Rupiah","rate":132402475.378558},{"code":"ILS","name":"Israeli Shekel","rate":33833.764794},{"code":"INR","name":"Indian Rupee","rate":628862.351419},{"code":"IQD","name":"Iraqi Dinar","rate":11574060.795505},{"code":"IRR","name":"Iranian Rial","rate":361055993.253529},{"code":"ISK","name":"Icelandic Króna","rate":975469.4937},{"code":"JEP","name":"Jersey Pound","rate":6934.501872},{"code":"JMD","name":"Jamaican Dollar","rate":1229134.807326},{"code":"JOD","name":"Jordanian Dinar","rate":6887.237856},{"code":"KES","name":"Kenyan Shilling","rate":989303.54207},{"code":"KGS","name":"Kyrgystani Som","rate":660191.880523},{"code":"KHR","name":"Cambodian Riel","rate":39337092.722486},{"code":"KMF","name":"Comorian Franc","rate":3889783.919776},{"code":"KPW","name":"North Korean Won","rate":8736417},{"code":"KWD","name":"Kuwaiti Dinar","rate":2912.702014},{"code":"KYD","name":"Cayman Islands Dollar","rate":8099.153623},{"code":"KZT","name":"Kazakhstani Tenge","rate":3108322.436718},{"code":"LAK","name":"Laotian Kip","rate":80527438.341},{"code":"LBP","name":"Lebanese Pound","rate":14720131.824304},{"code":"LKR","name":"Sri Lankan Rupee","rate":1508019.322349},{"code":"LRD","name":"Liberian Dollar","rate":1261465.859861},{"code":"LSL","name":"Lesotho Loti","rate":112817.575323},{"code":"LYD","name":"Libyan Dinar","rate":12974.384937},{"code":"MAD","name":"Moroccan Dirham","rate":89467.151765},{"code":"MDL","name":"Moldovan Leu","rate":162553.453704},{"code":"MGA","name":"Malagasy Ariary","rate":30623341.346851},{"code":"MKD","name":"Macedonian Denar","rate":486902.146896},{"code":"MMK","name":"Myanma Kyat","rate":13076951.31689},{"code":"MNT","name":"Mongolian Tugrik","rate":23243233.603891},{"code":"MOP","name":"Macanese Pataca","rate":78318.503252},{"code":"MRO","name":"Mauritanian Ouguiya","rate":3455738.28},{"code":"MUR","name":"Mauritian Rupee","rate":317340.640395},{"code":"MVR","name":"Maldivian Rufiyaa","rate":150072.336578},{"code":"MWK","name":"Malawian Kwacha","rate":7056488.191131},{"code":"MXN","name":"Mexican Peso","rate":180152.975458},{"code":"MYR","name":"Malaysian Ringgit","rate":37950.364485},{"code":"MZN","name":"Mozambican Metical","rate":593676.97555},{"code":"NAD","name":"Namibian Dollar","rate":112817.575323},{"code":"NGN","name":"Nigerian Naira","rate":3504304.03181},{"code":"NIO","name":"Nicaraguan Córdoba","rate":302530.928389},{"code":"NOK","name":"Norwegian Krone","rate":76087.057329},{"code":"NPR","name":"Nepalese Rupee","rate":1007057.989618},{"code":"OMR","name":"Omani Rial","rate":3737.342121},{"code":"PAB","name":"Panamanian Balboa","rate":9707.13},{"code":"PEN","name":"Peruvian Nuevo Sol","rate":31560.413191},{"code":"PGK","name":"Papua New Guinean Kina","rate":31528.787361},{"code":"PHP","name":"Philippine Peso","rate":502974.94095},{"code":"PKR","name":"Pakistani Rupee","rate":1076721.091577},{"code":"PLN","name":"Polish Zloty","rate":32911.684515},{"code":"PYG","name":"Paraguayan Guarani","rate":54306538.785},{"code":"QAR","name":"Qatari Rial","rate":35388.798484},{"code":"RON","name":"Romanian Leu","rate":36736.371392},{"code":"RSD","name":"Serbian Dinar","rate":930865.23135},{"code":"RUB","name":"Russian Ruble","rate":544376.821113},{"code":"RWF","name":"Rwandan Franc","rate":8399783.040738},{"code":"SAR","name":"Saudi Riyal","rate":36403.678926},{"code":"SBD","name":"Solomon Islands Dollar","rate":75336.773837},{"code":"SCR","name":"Seychellois Rupee","rate":130778.639133},{"code":"SDG","name":"Sudanese Pound","rate":175887.419858},{"code":"SGD","name":"Singapore Dollar","rate":12799.462454},{"code":"SHP","name":"Saint Helena Pound","rate":6934.501872},{"code":"SLL","name":"Sierra Leonean Leone","rate":74601857.732154},{"code":"SOS","name":"Somali Shilling","rate":5627352.754114},{"code":"SRD","name":"Surinamese Dollar","rate":72492.84684},{"code":"STD","name":"São Tomé and PrÃncipe Dobra","rate":193287220.809026},{"code":"SVC","name":"Salvadoran Colón","rate":85045.389028},{"code":"SYP","name":"Syrian Pound","rate":4999074.781629},{"code":"SZL","name":"Swazi Lilangeni","rate":112758.031787},{"code":"THB","name":"Thai Baht","rate":304682.542875},{"code":"TJS","name":"Tajikistani Somoni","rate":85759.610532},{"code":"TMT","name":"Turkmenistani Manat","rate":34071.832157},{"code":"TND","name":"Tunisian Dinar","rate":23338.804123},{"code":"TOP","name":"Tongan PaÊ»anga","rate":21519.105534},{"code":"TRY","name":"Turkish Lira","rate":36773.763257},{"code":"TTD","name":"Trinidad and Tobago Dollar","rate":65411.194584},{"code":"TWD","name":"New Taiwan Dollar","rate":283999.095042},{"code":"TZS","name":"Tanzanian Shilling","rate":21842376.007277},{"code":"UAH","name":"Ukrainian Hryvnia","rate":262065.34945},{"code":"UGX","name":"Ugandan Shilling","rate":35442023.348082},{"code":"UYU","name":"Uruguayan Peso","rate":276601.912525},{"code":"UZS","name":"Uzbekistan Som","rate":79530516.09},{"code":"VEF","name":"Venezuelan BolÃvar Fuerte","rate":242056993.68},{"code":"VND","name":"Vietnamese Dong","rate":220641397.467451},{"code":"VUV","name":"Vanuatu Vatu","rate":1019214.121739},{"code":"WST","name":"Samoan Tala","rate":24323.660412},{"code":"XAF","name":"CFA Franc BEAC","rate":5171961.145176},{"code":"XCD","name":"East Caribbean Dollar","rate":26234.004181},{"code":"XOF","name":"CFA Franc BCEAO","rate":5171961.145176},{"code":"XPF","name":"CFP Franc","rate":940883.523402},{"code":"YER","name":"Yemeni Rial","rate":2429516.435507},{"code":"ZAR","name":"South African Rand","rate":112047.945521},{"code":"ZMW","name":"Zambian Kwacha","rate":95005.622736},{"code":"ZWL","name":"Zimbabwean Dollar","rate":3129141.997928},{"code":"XAG","name":"Silver (troy ounce)","rate":583.53781},{"code":"XAU","name":"Gold (troy ounce)","rate":7.26569}]'),
3322 retrieve = function() {
3323 self.lodash.each(res, function(currency) {
3324 self._rates[currency.code] = currency.rate, self._alternatives.push({
3325 name: currency.name,
3326 isoCode: currency.code,
3327 rate: currency.rate
3328 })
3329 }), self._isAvailable = !0, self.lodash.each(self._queued, function(callback) {
3330 setTimeout(callback, 1)
3331 }), setTimeout(retrieve, 3e5)
3332 };
3333 retrieve()
3334 }, RateService.prototype.getRate = function(code) {
3335 return this._rates[code]
3336 }, RateService.prototype.getHistoricRate = function(code, date, cb) {
3337 var self = this;
3338 self.httprequest.get(self._url + "/" + code + "?ts=" + date).success(function(body) {
3339 return cb(null, body.rate)
3340 }).error(function(err) {
3341 return cb(err)
3342 })
3343 }, RateService.prototype.getHistoricRates = function(code, dates, cb) {
3344 var self = this,
3345 tsList = dates.join(",");
3346 self.httprequest.get(self._url + "/" + code + "?ts=" + tsList).success(function(body) {
3347 return self.lodash.isArray(body) || (body = [{
3348 ts: dates[0],
3349 rate: body.rate
3350 }]), cb(null, body)
3351 }).error(function(err) {
3352 return cb(err)
3353 })
3354 }, RateService.prototype.getAlternatives = function() {
3355 return this._alternatives
3356 }, RateService.prototype.isAvailable = function() {
3357 return this._isAvailable
3358 }, RateService.prototype.whenAvailable = function(callback) {
3359 this.isAvailable() ? setTimeout(callback, 1) : this._queued.push(callback)
3360 }, RateService.prototype.toFiat = function(satoshis, code) {
3361 return this.isAvailable() ? satoshis * this.SAT_TO_BTC * this.getRate(code) : null
3362 }, RateService.prototype.toFiatHistoric = function(satoshis, code, date, cb) {
3363 var self = this;
3364 self.getHistoricRate(code, date, function(err, rate) {
3365 return err ? cb(err) : cb(null, satoshis * self.SAT_TO_BTC * rate)
3366 })
3367 }, RateService.prototype.fromFiat = function(amount, code) {
3368 return this.isAvailable() ? amount / this.getRate(code) * this.BTC_TO_SAT : null
3369 },
3370 RateService.prototype.listAlternatives = function() {
3371 var self = this;
3372 return this.isAvailable() ? self.lodash.map(this.getAlternatives(), function(item) {
3373 return {
3374 name: item.name,
3375 isoCode: item.isoCode
3376 }
3377 }) : []
3378 }, angular.module("copayApp.services").factory("rateService", function($http, lodash) {
3379 var cfg = {
3380 httprequest: $http,
3381 lodash: lodash
3382 };
3383 return RateService.singleton(cfg)
3384 }), angular.module("copayApp.services").factory("sjcl", function(bwcService) {
3385 return bwcService.getSJCL()
3386 }), angular.module("copayApp.services").factory("storageService", function(logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo) {
3387 var root = {},
3388 shouldUseFileStorage = platformInfo.isCordova && !platformInfo.isWP;
3389 $log.debug("Using file storage:", shouldUseFileStorage);
3390 var storage = shouldUseFileStorage ? fileStorageService : localStorageService,
3391 getUUID = function(cb) {
3392 if (!window || !window.plugins || !window.plugins.uniqueDeviceID) return cb(null);
3393 window.plugins.uniqueDeviceID.get(function(uuid) {
3394 return cb(uuid)
3395 }, cb)
3396 },
3397 decryptOnMobile = function(text, cb) {
3398 var json;
3399 try {
3400 json = JSON.parse(text)
3401 } catch (e) {
3402 $log.warn("Could not open profile:" + text);
3403 var i = text.lastIndexOf("}{");
3404 if (i > 0) {
3405 text = text.substr(i + 1), $log.warn("trying last part only:" + text);
3406 try {
3407 json = JSON.parse(text), $log.warn("Worked... saving."), storage.set("profile", text, function() {})
3408 } catch (e) {
3409 $log.warn("Could not open profile (2nd try):" + e)
3410 }
3411 }
3412 }
3413 return json ? json.iter && json.ct ? ($log.debug("Profile is encrypted"), void getUUID(function(uuid) {
3414 if ($log.debug("Device UUID:" + uuid), !uuid) return cb("Could not decrypt storage: could not get device ID");
3415 try {
3416 return text = sjcl.decrypt(uuid, text), $log.info("Migrating to unencrypted profile"), storage.set("profile", text, function(err) {
3417 return cb(err, text)
3418 })
3419 } catch (e) {
3420 return $log.warn("Decrypt error: ", e), cb("Could not decrypt storage: device ID mismatch")
3421 }
3422 return cb(null, text)
3423 })) : ($log.debug("Profile is not encrypted"), cb(null, text)) : cb("Could not access storage")
3424 };
3425 return root.tryToMigrate = function(cb) {
3426 if (!shouldUseFileStorage) return cb();
3427 localStorageService.get("profile", function(err, str) {
3428 return err ? cb(err) : str ? ($log.info("Starting Migration profile to File storage..."), void fileStorageService.create("profile", str, function(err) {
3429 err && cb(err), $log.info("Profile Migrated successfully"), localStorageService.get("config", function(err, c) {
3430 return err ? cb(err) : c ? void fileStorageService.create("config", c, function(err) {
3431 return err ? ($log.info("Error migrating config: ignoring", err), root.getProfile(cb)) : ($log.info("Config Migrated successfully"), root.getProfile(cb))
3432 }) : root.getProfile(cb)
3433 })
3434 })) : cb()
3435 })
3436 }, root.storeNewProfile = function(profile, cb) {
3437 storage.create("profile", profile.toObj(), cb)
3438 }, root.storeProfile = function(profile, cb) {
3439 storage.set("profile", profile.toObj(), cb)
3440 }, root.getProfile = function(cb) {
3441 storage.get("profile", function(err, str) {
3442 if (err || !str) return cb(err);
3443 decryptOnMobile(str, function(err, str) {
3444 if (err) return cb(err);
3445 var p, err;
3446 try {
3447 p = Profile.fromString(str)
3448 } catch (e) {
3449 $log.debug("Could not read profile:", e), err = new Error("Could not read profile:" + p)
3450 }
3451 return cb(err, p)
3452 })
3453 })
3454 }, root.deleteProfile = function(cb) {
3455 storage.remove("profile", cb)
3456 }, root.storeFocusedWalletId = function(id, cb) {
3457 storage.set("focusedWalletId", id || "", cb)
3458 }, root.getFocusedWalletId = function(cb) {
3459 storage.get("focusedWalletId", cb)
3460 }, root.getLastAddress = function(walletId, cb) {
3461 storage.get("lastAddress-" + walletId, cb)
3462 }, root.storeLastAddress = function(walletId, address, cb) {
3463 storage.set("lastAddress-" + walletId, address, cb)
3464 }, root.clearLastAddress = function(walletId, cb) {
3465 storage.remove("lastAddress-" + walletId, cb)
3466 }, root.setBackupFlag = function(walletId, cb) {
3467 storage.set("backup-" + walletId, Date.now(), cb)
3468 }, root.getBackupFlag = function(walletId, cb) {
3469 storage.get("backup-" + walletId, cb)
3470 }, root.clearBackupFlag = function(walletId, cb) {
3471 storage.remove("backup-" + walletId, cb)
3472 }, root.setCleanAndScanAddresses = function(walletId, cb) {
3473 storage.set("CleanAndScanAddresses", walletId, cb)
3474 }, root.getCleanAndScanAddresses = function(cb) {
3475 storage.get("CleanAndScanAddresses", cb)
3476 }, root.removeCleanAndScanAddresses = function(cb) {
3477 storage.remove("CleanAndScanAddresses", cb)
3478 }, root.getConfig = function(cb) {
3479 storage.get("config", cb)
3480 }, root.storeConfig = function(val, cb) {
3481 $log.debug("Storing Preferences", val), storage.set("config", val, cb)
3482 }, root.clearConfig = function(cb) {
3483 storage.remove("config", cb)
3484 }, root.setHideBalanceFlag = function(walletId, val, cb) {
3485 storage.set("hideBalance-" + walletId, val, cb)
3486 }, root.getHideBalanceFlag = function(walletId, cb) {
3487 storage.get("hideBalance-" + walletId, cb)
3488 }, root.getCopayDisclaimerFlag = function(cb) {
3489 storage.get("agreeDisclaimer", cb)
3490 }, root.setRemotePrefsStoredFlag = function(cb) {
3491 storage.set("remotePrefStored", !0, cb)
3492 }, root.getRemotePrefsStoredFlag = function(cb) {
3493 storage.get("remotePrefStored", cb)
3494 }, root.setGlideraToken = function(network, token, cb) {
3495 storage.set("glideraToken-" + network, token, cb)
3496 }, root.getGlideraToken = function(network, cb) {
3497 storage.get("glideraToken-" + network, cb)
3498 }, root.removeGlideraToken = function(network, cb) {
3499 storage.remove("glideraToken-" + network, cb)
3500 }, root.setCoinbaseRefreshToken = function(network, token, cb) {
3501 storage.set("coinbaseRefreshToken-" + network, token, cb)
3502 }, root.getCoinbaseRefreshToken = function(network, cb) {
3503 storage.get("coinbaseRefreshToken-" + network, cb)
3504 }, root.removeCoinbaseRefreshToken = function(network, cb) {
3505 storage.remove("coinbaseRefreshToken-" + network, cb)
3506 }, root.setCoinbaseToken = function(network, token, cb) {
3507 storage.set("coinbaseToken-" + network, token, cb)
3508 }, root.getCoinbaseToken = function(network, cb) {
3509 storage.get("coinbaseToken-" + network, cb)
3510 }, root.removeCoinbaseToken = function(network, cb) {
3511 storage.remove("coinbaseToken-" + network, cb)
3512 }, root.setAddressbook = function(network, addressbook, cb) {
3513 storage.set("addressbook-" + network, addressbook, cb)
3514 }, root.getAddressbook = function(network, cb) {
3515 storage.get("addressbook-" + network, cb)
3516 }, root.removeAddressbook = function(network, cb) {
3517 storage.remove("addressbook-" + network, cb)
3518 }, root.checkQuota = function() {
3519 for (var block = "", i = 0; i < 1048576; ++i) block += "12345678901234567890123456789012345678901234567890";
3520 storage.set("test", block, function(err) {
3521 $log.error("CheckQuota Return:" + err)
3522 })
3523 }, root.setTxHistory = function(txs, walletId, cb) {
3524 try {
3525 storage.set("txsHistory-" + walletId, txs, cb)
3526 } catch (e) {
3527 return $log.error("Error saving tx History. Size:" + txs.length), $log.error(e), cb(e)
3528 }
3529 }, root.getTxHistory = function(walletId, cb) {
3530 storage.get("txsHistory-" + walletId, cb)
3531 }, root.removeTxHistory = function(walletId, cb) {
3532 storage.remove("txsHistory-" + walletId, cb)
3533 }, root.setCoinbaseTxs = function(network, ctx, cb) {
3534 storage.set("coinbaseTxs-" + network, ctx, cb)
3535 }, root.getCoinbaseTxs = function(network, cb) {
3536 storage.get("coinbaseTxs-" + network, cb)
3537 }, root.removeCoinbaseTxs = function(network, cb) {
3538 storage.remove("coinbaseTxs-" + network, cb)
3539 }, root.removeAllWalletData = function(walletId, cb) {
3540 root.clearLastAddress(walletId, function(err) {
3541 if (err) return cb(err);
3542 root.removeTxHistory(walletId, function(err) {
3543 if (err) return cb(err);
3544 root.clearBackupFlag(walletId, function(err) {
3545 return cb(err)
3546 })
3547 })
3548 })
3549 }, root.setAmazonGiftCards = function(network, gcs, cb) {
3550 storage.set("amazonGiftCards-" + network, gcs, cb)
3551 }, root.getAmazonGiftCards = function(network, cb) {
3552 storage.get("amazonGiftCards-" + network, cb)
3553 }, root.removeAmazonGiftCards = function(network, cb) {
3554 storage.remove("amazonGiftCards-" + network, cb)
3555 }, root
3556 }), angular.module("copayApp.services").factory("$swipe", [function() {
3557 function getCoordinates(event) {
3558 var originalEvent = event.originalEvent || event,
3559 touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent],
3560 e = originalEvent.changedTouches && originalEvent.changedTouches[0] || touches[0];
3561 return {
3562 x: e.clientX,
3563 y: e.clientY
3564 }
3565 }
3566
3567 function getEvents(pointerTypes, eventType) {
3568 var res = [];
3569 return angular.forEach(pointerTypes, function(pointerType) {
3570 var eventName = POINTER_EVENTS[pointerType][eventType];
3571 eventName && res.push(eventName)
3572 }), res.join(" ")
3573 }
3574 var POINTER_EVENTS = {
3575 touch: {
3576 start: "touchstart",
3577 move: "touchmove",
3578 end: "touchend",
3579 cancel: "touchcancel"
3580 }
3581 };
3582 return {
3583 bind: function(element, eventHandlers, pointerTypes) {
3584 var totalX, totalY, startCoords, lastPos, active = !1;
3585 pointerTypes = pointerTypes || ["touch"], element.on(getEvents(pointerTypes, "start"), function(event) {
3586 startCoords = getCoordinates(event), active = !0, totalX = 0, totalY = 0, lastPos = startCoords, eventHandlers.start && eventHandlers.start(startCoords, event)
3587 });
3588 var events = getEvents(pointerTypes, "cancel");
3589 events && element.on(events, function(event) {
3590 active = !1, eventHandlers.cancel && eventHandlers.cancel(event)
3591 }), element.on(getEvents(pointerTypes, "move"), function(event) {
3592 if (active && startCoords) {
3593 var coords = getCoordinates(event);
3594 if (totalX += Math.abs(coords.x - lastPos.x), totalY += Math.abs(coords.y - lastPos.y), lastPos = coords, !(totalX < 10 && totalY < 10)) return totalY > totalX ? (active = !1, void(eventHandlers.cancel && eventHandlers.cancel(event))) : (event.preventDefault(), void(eventHandlers.move && eventHandlers.move(coords, event)))
3595 }
3596 }), element.on(getEvents(pointerTypes, "end"), function(event) {
3597 active && (active = !1, eventHandlers.end && eventHandlers.end(getCoordinates(event), event))
3598 })
3599 }
3600 }
3601 }]), angular.module("copayApp.services").factory("trezor", function($log, $timeout, gettext, lodash, bitcore, hwWallet) {
3602 var root = {};
3603 return root.callbacks = {}, root.getEntropySource = function(isMultisig, account, callback) {
3604 root.getXPubKey(hwWallet.getEntropyPath("trezor", isMultisig, account), function(data) {
3605 return data.success ? callback(null, hwWallet.pubKeyToEntropySource(data.xpubkey)) : callback(hwWallet._err(data))
3606 })
3607 }, root.getXPubKey = function(path, callback) {
3608 $log.debug("TREZOR deriving xPub path:", path), TrezorConnect.getXPubKey(path, callback)
3609 }, root.getInfoForNewWallet = function(isMultisig, account, callback) {
3610 var opts = {};
3611 root.getEntropySource(isMultisig, account, function(err, data) {
3612 if (err) return callback(err);
3613 opts.entropySource = data, $log.debug("Waiting TREZOR to settle..."), $timeout(function() {
3614 root.getXPubKey(hwWallet.getAddressPath("trezor", isMultisig, account), function(data) {
3615 return data.success ? (opts.extendedPublicKey = data.xpubkey, opts.externalSource = "trezor", opts.account = account, isMultisig && (opts.derivationStrategy = "BIP48"), callback(null, opts)) : callback(hwWallet._err(data))
3616 })
3617 }, 3e3)
3618 })
3619 }, root._orderPubKeys = function(xPub, np) {
3620 var xPubKeys = lodash.clone(xPub),
3621 path = lodash.clone(np);
3622 path.unshift("m"), path = path.join("/");
3623 var keys = lodash.map(xPubKeys, function(x) {
3624 return {
3625 xpub: x,
3626 pub: new bitcore.HDPublicKey(x).derive(path).publicKey.toString("hex")
3627 }
3628 }),
3629 sorted = lodash.sortBy(keys, function(x) {
3630 return x.pub
3631 });
3632 return lodash.pluck(sorted, "xpub")
3633 }, root.signTx = function(xPubKeys, txp, account, callback) {
3634 var inputs = [],
3635 outputs = [],
3636 tmpOutputs = [];
3637 if (txp.type && "simple" != txp.type) return callback("Only TXPs type SIMPLE are supported in TREZOR");
3638 if (!txp.outputs) return callback("Unknown TXP at TREZOR");
3639 if (txp.outputs.length > 1) return callback("Only single output TXPs are supported in TREZOR");
3640 if (txp.outputs && (txp.toAddress || (txp.toAddress = txp.outputs[0].toAddress), txp.amount || (txp.amount = txp.outputs[0].amount)), !txp.toAddress || !txp.amount) return callback("No address or amount at TREZOR signing");
3641 var toScriptType = "PAYTOADDRESS";
3642 if ("2" != txp.toAddress.charAt(0) && "3" != txp.toAddress.charAt(0) || (toScriptType = "PAYTOSCRIPTHASH"), tmpOutputs.push({
3643 address: txp.toAddress,
3644 amount: txp.amount,
3645 script_type: toScriptType
3646 }), "P2PKH" == txp.addressType) {
3647 $log.debug("Trezor signing uni-sig p2pkh. Account:", account);
3648 var inAmount = 0;
3649 inputs = lodash.map(txp.inputs, function(i) {
3650 $log.debug("Trezor TX input path:", i.path);
3651 var pathArr = i.path.split("/"),
3652 n = [2147483648 | hwWallet.UNISIG_ROOTPATH, -2147483648, 2147483648 | account, parseInt(pathArr[1]), parseInt(pathArr[2])];
3653 return inAmount += i.satoshis, {
3654 address_n: n,
3655 prev_index: i.vout,
3656 prev_hash: i.txid
3657 }
3658 });
3659 var change = inAmount - txp.fee - txp.amount;
3660 if (change > 0) {
3661 $log.debug("Trezor TX change path:", txp.changeAddress.path);
3662 var pathArr = txp.changeAddress.path.split("/"),
3663 n = [2147483648 | hwWallet.UNISIG_ROOTPATH, -2147483648, 2147483648 | account, parseInt(pathArr[1]), parseInt(pathArr[2])];
3664 tmpOutputs.push({
3665 address_n: n,
3666 amount: change,
3667 script_type: "PAYTOADDRESS"
3668 })
3669 }
3670 } else {
3671 var inAmount = 0;
3672 $log.debug("Trezor signing multi-sig p2sh. Account:", account);
3673 var sigs = xPubKeys.map(function(v) {
3674 return ""
3675 });
3676 inputs = lodash.map(txp.inputs, function(i) {
3677 $log.debug("Trezor TX input path:", i.path);
3678 var pathArr = i.path.split("/"),
3679 n = [2147483648 | hwWallet.MULTISIG_ROOTPATH, -2147483648, 2147483648 | account, parseInt(pathArr[1]), parseInt(pathArr[2])],
3680 np = n.slice(3);
3681 inAmount += i.satoshis;
3682 var orderedPubKeys = root._orderPubKeys(xPubKeys, np),
3683 pubkeys = lodash(orderedPubKeys.map(function(v) {
3684 return {
3685 node: v,
3686 address_n: np
3687 }
3688 }));
3689 return {
3690 address_n: n,
3691 prev_index: i.vout,
3692 prev_hash: i.txid,
3693 script_type: "SPENDMULTISIG",
3694 multisig: {
3695 pubkeys: pubkeys,
3696 signatures: sigs,
3697 m: txp.requiredSignatures
3698 }
3699 }
3700 });
3701 var change = inAmount - txp.fee - txp.amount;
3702 if (change > 0) {
3703 $log.debug("Trezor TX change path:", txp.changeAddress.path);
3704 var pathArr = txp.changeAddress.path.split("/"),
3705 n = [2147483648 | hwWallet.MULTISIG_ROOTPATH, -2147483648, 2147483648 | account, parseInt(pathArr[1]), parseInt(pathArr[2])],
3706 np = n.slice(3),
3707 orderedPubKeys = root._orderPubKeys(xPubKeys, np),
3708 pubkeys = lodash(orderedPubKeys.map(function(v) {
3709 return {
3710 node: v,
3711 address_n: np
3712 }
3713 }));
3714 tmpOutputs.push({
3715 address_n: n,
3716 amount: change,
3717 script_type: "PAYTOMULTISIG",
3718 multisig: {
3719 pubkeys: pubkeys,
3720 signatures: sigs,
3721 m: txp.requiredSignatures
3722 }
3723 })
3724 }
3725 }
3726 if (tmpOutputs.length > 1) {
3727 if (outputs = new Array(tmpOutputs.length), lodash.each(txp.outputOrder, function(order) {
3728 outputs[order] = tmpOutputs.shift()
3729 }), tmpOutputs.length) return cb("Error creating transaction: tmpOutput order")
3730 } else outputs = tmpOutputs;
3731 inputs = JSON.parse(JSON.stringify(inputs)), outputs = JSON.parse(JSON.stringify(outputs)), $log.debug("Signing with TREZOR", inputs, outputs), TrezorConnect.signTx(inputs, outputs, function(res) {
3732 if (!res.success) return callback(hwWallet._err(res));
3733 callback(null, res)
3734 })
3735 }, root
3736 }), angular.module("copayApp.services").factory("txFormatService", function(profileService, rateService, configService, addressService, lodash) {
3737 var root = {},
3738 formatAmountStr = function(amount) {
3739 if (amount) {
3740 var config = configService.getSync().wallet.settings;
3741 return profileService.formatAmount(amount) + " " + config.unitName
3742 }
3743 },
3744 formatAlternativeStr = function(amount) {
3745 if (amount) {
3746 var config = configService.getSync().wallet.settings;
3747 return (rateService.toFiat(amount, config.alternativeIsoCode) ? rateService.toFiat(amount, config.alternativeIsoCode).toFixed(2) : "N/A") + " " + config.alternativeIsoCode
3748 }
3749 },
3750 formatFeeStr = function(fee) {
3751 if (fee) {
3752 var config = configService.getSync().wallet.settings;
3753 return profileService.formatAmount(fee) + " " + config.unitName
3754 }
3755 };
3756 return root.processTx = function(tx) {
3757 if (!tx || "invalid" == tx.action) return tx;
3758 if (tx.outputs && tx.outputs.length) {
3759 var outputsNr = tx.outputs.length;
3760 "received" != tx.action && (outputsNr > 1 && (tx.recipientCount = outputsNr, tx.hasMultiplesOutputs = !0), addressService.getAddress(profileService.focusedClient.credentials.walletId, !1, function(err, addr) {
3761 tx.amount = lodash.reduce(tx.outputs, function(total, o) {
3762 return o.amountStr = formatAmountStr(o.amount), o.alternativeAmountStr = formatAlternativeStr(o.amount), void 0 !== o.address && o.address.toString() != addr.toString() ? total + o.amount : total
3763 }, 0)
3764 })), tx.toAddress = tx.outputs[0].toAddress
3765 }
3766 return tx.amountStr = formatAmountStr(tx.amount), tx.alternativeAmountStr = formatAlternativeStr(tx.amount), tx.isToken = tx.customData && tx.customData.token, tx.isToken && (tx.tokenAmountStr = tx.customData.token.amount + " token" + (tx.customData.token.amount > 1 ? "s" : ""), tx.addressTo = tx.outputs[0].address), tx.feeStr = formatFeeStr(tx.fee || tx.fees), tx
3767 }, root
3768 }), angular.module("copayApp.services").factory("txStatus", function(lodash, profileService, $timeout, platformInfo) {
3769 var root = {};
3770 platformInfo.isCordova;
3771 return root.notify = function(txp) {
3772 var type, fc = profileService.focusedClient,
3773 status = txp.status;
3774 if ("broadcasted" == status) type = "broadcasted";
3775 else {
3776 var n = txp.actions.length,
3777 action = lodash.find(txp.actions, {
3778 copayerId: fc.credentials.copayerId
3779 });
3780 if (action)
3781 if ("accept" == action.type) type = 1 == n && action.createdOn - txp.createdOn < 10 ? "created" : "accepted";
3782 else {
3783 if ("reject" != action.type) throw new Error("Unknown type:" + type);
3784 type = "rejected"
3785 }
3786 else type = "created"
3787 }
3788 return type
3789 }, root
3790 }), angular.module("copayApp.services").factory("uxLanguage", function($log, lodash, gettextCatalog, amMoment, configService) {
3791 var root = {};
3792 return root.currentLanguage = null, root.availableLanguages = [{
3793 name: "English",
3794 isoCode: "en"
3795 }], root._detect = function(cb) {
3796 var userLang;
3797 if (!navigator || !navigator.globalization) return userLang = navigator.userLanguage || navigator.language, userLang = userLang ? userLang.split("-", 1)[0] || "en" : "en", userLang = root.isAvailableLanguage(userLang), cb(userLang);
3798 navigator.globalization.getPreferredLanguage(function(preferedLanguage) {
3799 return userLang = preferedLanguage.value, userLang = userLang ? userLang.split("-", 1)[0] || "en" : "en", userLang = root.isAvailableLanguage(userLang), cb(userLang)
3800 })
3801 }, root.isAvailableLanguage = function(userLang) {
3802 return lodash.find(root.availableLanguages, {
3803 isoCode: userLang
3804 }) ? userLang : "en"
3805 }, root._set = function(lang) {
3806 $log.debug("Setting default language: " + lang), gettextCatalog.setCurrentLanguage(lang), root.currentLanguage = lang, "zh" == lang && (lang += "-CN"), amMoment.changeLocale(lang)
3807 }, root.getCurrentLanguage = function() {
3808 return root.currentLanguage
3809 }, root.getCurrentLanguageName = function() {
3810 return root.getName(root.currentLanguage)
3811 }, root.getCurrentLanguageInfo = function() {
3812 return lodash.find(root.availableLanguages, {
3813 isoCode: root.currentLanguage
3814 })
3815 }, root.getLanguages = function() {
3816 return root.availableLanguages
3817 }, root.init = function() {
3818 root._detect(function(lang) {
3819 root._set(lang)
3820 })
3821 }, root.update = function(cb) {
3822 var userLang = configService.getSync().wallet.settings.defaultLanguage;
3823 if (userLang) {
3824 if (userLang != root.currentLanguage && root._set(userLang), cb) return cb(userLang)
3825 } else root._detect(function(lang) {
3826 if (userLang = lang, userLang != root.currentLanguage && root._set(lang), cb) return cb(userLang)
3827 })
3828 }, root.getName = function(lang) {
3829 return lodash.result(lodash.find(root.availableLanguages, {
3830 isoCode: lang
3831 }), "name")
3832 }, root
3833 }), angular.module("copayApp.services").factory("walletService", function($log, lodash, trezor, ledger, storageService, configService, uxLanguage) {
3834 var root = {},
3835 _signWithLedger = function(client, txp, cb) {
3836 $log.info("Requesting Ledger Chrome app to sign the transaction"), ledger.signTx(txp, client.credentials.account, function(result) {
3837 return $log.debug("Ledger response", result), result.success ? (txp.signatures = lodash.map(result.signatures, function(s) {
3838 return s.substring(0, s.length - 2)
3839 }), client.signTxProposal(txp, cb)) : cb(result.message || result.error)
3840 })
3841 },
3842 _signWithTrezor = function(client, txp, cb) {
3843 $log.info("Requesting Trezor to sign the transaction");
3844 var xPubKeys = lodash.pluck(client.credentials.publicKeyRing, "xPubKey");
3845 trezor.signTx(xPubKeys, txp, client.credentials.account, function(err, result) {
3846 return err ? cb(err) : ($log.debug("Trezor response", result), txp.signatures = result.signatures, client.signTxProposal(txp, cb))
3847 })
3848 };
3849 return root.needsBackup = function(client) {
3850 return !client.isPrivKeyExternal() && (!!client.credentials.mnemonic && "testnet" != client.credentials.network)
3851 }, root.isEncrypted = function(client) {
3852 if (!lodash.isEmpty(client)) {
3853 var isEncrypted = client.isPrivKeyEncrypted();
3854 return isEncrypted && $log.debug("Wallet is encrypted"), isEncrypted
3855 }
3856 }, root.lock = function(client) {
3857 try {
3858 client.lock()
3859 } catch (e) {
3860 $log.warn("Encrypting wallet:", e)
3861 }
3862 }, root.unlock = function(client, password) {
3863 if (lodash.isEmpty(client)) return "MISSING_PARAMETER";
3864 if (lodash.isEmpty(password)) return "NO_PASSWORD_GIVEN";
3865 try {
3866 client.unlock(password)
3867 } catch (e) {
3868 return $log.warn("Decrypting wallet:", e), "PASSWORD_INCORRECT"
3869 }
3870 }, root.createTx = function(client, txp, cb) {
3871 if (lodash.isEmpty(txp) || lodash.isEmpty(client)) return cb("MISSING_PARAMETER");
3872 txp.sendMax ? client.createTxProposal(txp, function(err, createdTxp) {
3873 return err ? cb(err) : cb(null, createdTxp)
3874 }) : (void 0 === txp.fee && ($log.debug("fee: 10000 SAT"), txp.feePerKb = 1e4), client.createTxProposal(txp, function(err, createdTxp) {
3875 return err ? cb(err) : ($log.debug("Transaction created"), cb(null, createdTxp))
3876 }))
3877 }, root.publishTx = function(client, txp, cb) {
3878 if (lodash.isEmpty(txp) || lodash.isEmpty(client)) return cb("MISSING_PARAMETER");
3879 client.publishTxProposal({
3880 txp: txp
3881 }, function(err, publishedTx) {
3882 return err ? cb(err) : ($log.debug("Transaction published"), cb(null, publishedTx))
3883 })
3884 }, root.signTx = function(client, txp, cb) {
3885 if (lodash.isEmpty(txp) || lodash.isEmpty(client)) return cb("MISSING_PARAMETER");
3886 if (client.isPrivKeyExternal()) switch (client.getPrivKeyExternalSourceName()) {
3887 case "ledger":
3888 return _signWithLedger(client, txp, cb);
3889 case "trezor":
3890 return _signWithTrezor(client, txp, cb);
3891 default:
3892 var msg = "Unsupported External Key:" + client.getPrivKeyExternalSourceName();
3893 return $log.error(msg), cb(msg)
3894 } else try {
3895 client.signTxProposal(txp, function(err, signedTxp) {
3896 return $log.debug("Transaction signed"), cb(err, signedTxp)
3897 })
3898 } catch (e) {
3899 return $log.warn("Error at signTxProposal:", e), cb(e)
3900 }
3901 }, root.broadcastTx = function(client, txp, cb) {
3902 return lodash.isEmpty(txp) || lodash.isEmpty(client) ? cb("MISSING_PARAMETER") : "accepted" != txp.status ? cb("TX_NOT_ACCEPTED") : void client.broadcastTxProposal(txp, function(err, broadcastedTxp, memo) {
3903 return err ? cb(err) : ($log.debug("Transaction broadcasted"), memo && $log.info(memo), cb(null, broadcastedTxp))
3904 })
3905 }, root.rejectTx = function(client, txp, cb) {
3906 if (lodash.isEmpty(txp) || lodash.isEmpty(client)) return cb("MISSING_PARAMETER");
3907 client.rejectTxProposal(txp, null, function(err, rejectedTxp) {
3908 return $log.debug("Transaction rejected"), cb(err, rejectedTxp)
3909 })
3910 }, root.removeTx = function(client, txp, cb) {
3911 if (lodash.isEmpty(txp) || lodash.isEmpty(client)) return cb("MISSING_PARAMETER");
3912 client.removeTxProposal(txp, function(err) {
3913 return $log.debug("Transaction removed"), cb(err)
3914 })
3915 }, root.updateRemotePreferences = function(clients, prefs, cb) {
3916 function updateRemotePreferencesFor(clients, prefs, cb) {
3917 var client = clients.shift();
3918 if (!client) return cb();
3919 $log.debug("Saving remote preferences", client.credentials.walletName, prefs), client.savePreferences(prefs, function(err) {
3920 err && $log.warn(err), updateRemotePreferencesFor(clients, prefs, cb)
3921 })
3922 }
3923 prefs = prefs || {}, lodash.isArray(clients) || (clients = [clients]);
3924 var config = configService.getSync().wallet.settings;
3925 prefs.language = uxLanguage.getCurrentLanguage(), prefs.unit = config.unitCode, updateRemotePreferencesFor(clients, prefs, function(err) {
3926 return err ? cb(err) : (lodash.each(clients, function(c) {
3927 c.preferences = lodash.assign(prefs, c.preferences)
3928 }), cb())
3929 })
3930 }, root
3931 }), angular.module("copayApp.controllers").controller("amazonController", function($scope, $timeout, $ionicModal, $log, lodash, bwcError, amazonService, platformInfo) {
3932 platformInfo.isCordova && StatusBar.isVisible && StatusBar.backgroundColorByHexString("#4B6178"), this.init = function() {
3933 var self = this;
3934 self.sandbox = "testnet" == amazonService.getEnvironment(), amazonService.getPendingGiftCards(function(err, gcds) {
3935 if (err) return void(self.error = err);
3936 $scope.giftCards = lodash.isEmpty(gcds) ? null : gcds, $timeout(function() {
3937 $scope.$digest()
3938 })
3939 }), this.updatePendingGiftCards()
3940 }, this.updatePendingGiftCards = lodash.debounce(function() {
3941 var self = this;
3942 amazonService.getPendingGiftCards(function(err, gcds) {
3943 lodash.forEach(gcds, function(dataFromStorage) {
3944 "PENDING" == dataFromStorage.status && ($log.debug("creating gift card"), amazonService.createGiftCard(dataFromStorage, function(err, giftCard) {
3945 if (err) return void $log.debug(bwcError.msg(err));
3946 if ("PENDING" != giftCard.status) {
3947 var newData = {};
3948 lodash.merge(newData, dataFromStorage, giftCard), "expired" == newData.status && amazonService.savePendingGiftCard(newData, {
3949 remove: !0
3950 }, function(err) {}), amazonService.savePendingGiftCard(newData, null, function(err) {
3951 $log.debug("Saving new gift card"), amazonService.getPendingGiftCards(function(err, gcds) {
3952 if (err) return void(self.error = err);
3953 $scope.giftCards = gcds, $timeout(function() {
3954 $scope.$digest()
3955 })
3956 })
3957 })
3958 } else $log.debug("pending gift card not available yet")
3959 }))
3960 })
3961 })
3962 }, 1e3), this.openCardModal = function(card) {
3963 var self = this;
3964 $scope.card = card, $ionicModal.fromTemplateUrl("views/modals/amazon-card-details.html", {
3965 scope: $scope
3966 }).then(function(modal) {
3967 $scope.amazonCardDetailsModal = modal, $scope.amazonCardDetailsModal.show()
3968 }), $scope.$on("UpdateAmazonList", function(event) {
3969 self.init()
3970 })
3971 }
3972 }), angular.module("copayApp.controllers").controller("backupController", function($rootScope, $scope, $timeout, $log, go, lodash, fingerprintService, platformInfo, configService, profileService, gettext, bwcService, walletService, ongoingProcess) {
3973 function shuffledWords(words) {
3974 var sort = lodash.sortBy(words);
3975 return lodash.map(sort, function(w) {
3976 return {
3977 word: w,
3978 selected: !1
3979 }
3980 })
3981 }
3982
3983 function isDeletedSeed() {
3984 return !(!lodash.isEmpty(fc.credentials.mnemonic) || !lodash.isEmpty(fc.credentials.mnemonicEncrypted))
3985 }
3986
3987 function confirm(cb) {
3988 $scope.backupError = !1;
3989 var customWordList = lodash.pluck($scope.customWords, "word");
3990 if (!lodash.isEqual($scope.mnemonicWords, customWordList)) return cb("Mnemonic string mismatch");
3991 $timeout(function() {
3992 if ($scope.mnemonicHasPassphrase) {
3993 var walletClient = bwcService.getClient(),
3994 separator = $scope.useIdeograms ? " " : " ",
3995 customSentence = customWordList.join(separator),
3996 passphrase = $scope.passphrase || "";
3997 try {
3998 walletClient.seedFromMnemonic(customSentence, {
3999 network: fc.credentials.network,
4000 passphrase: passphrase,
4001 account: fc.credentials.account
4002 })
4003 } catch (err) {
4004 return cb(err)
4005 }
4006 if (walletClient.credentials.xPrivKey != $scope.xPrivKey) return cb("Private key mismatch")
4007 }
4008 return $rootScope.$emit("Local/BackupDone"), cb()
4009 }, 1)
4010 }
4011
4012 function handleEncryptedWallet(client, cb) {
4013 if (!walletService.isEncrypted(client)) return $scope.credentialsEncrypted = !1, cb();
4014 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
4015 return cb(err ? err : walletService.unlock(client, password))
4016 })
4017 }
4018
4019 function backupError(err) {
4020 ongoingProcess.set("validatingWords", !1), $log.debug("Failed to verify backup: ", err), $scope.backupError = !0, $timeout(function() {
4021 $scope.$apply()
4022 }, 1)
4023 }
4024 var prevState, fc = profileService.focusedClient;
4025 $scope.customWords = [], $scope.walletName = fc.credentials.walletName, $scope.credentialsEncrypted = fc.isPrivKeyEncrypted, $scope.init = function(state) {
4026 prevState = state || "walletHome", $scope.step = 1, $scope.deleted = isDeletedSeed(), $scope.deleted || fingerprintService.check(fc, function(err) {
4027 if (err) return void go.path(prevState);
4028 handleEncryptedWallet(fc, function(err) {
4029 if (err) return $log.warn("Error decrypting credentials:", $scope.error), void go.path(prevState);
4030 $scope.credentialsEncrypted = !1, $scope.initFlow()
4031 })
4032 })
4033 }, $scope.initFlow = function() {
4034 var words = fc.getMnemonic();
4035 $scope.xPrivKey = fc.credentials.xPrivKey, $scope.mnemonicWords = words.split(/[\u3000\s]+/), $scope.shuffledMnemonicWords = shuffledWords($scope.mnemonicWords), $scope.mnemonicHasPassphrase = fc.mnemonicHasPassphrase(), $scope.useIdeograms = words.indexOf(" ") >= 0, $scope.passphrase = "", $scope.customWords = [], $scope.step = 1, $scope.selectComplete = !1, $scope.backupError = !1, $timeout(function() {
4036 $scope.$apply()
4037 }, 10)
4038 }, $scope.goBack = function() {
4039 go.path(prevState || "walletHome")
4040 }, $scope.$on("$destroy", function() {
4041 walletService.lock(fc)
4042 }), $scope.goToStep = function(n) {
4043 function finalStep() {
4044 ongoingProcess.set("validatingWords", !0), confirm(function(err) {
4045 ongoingProcess.set("validatingWords", !1), err && backupError(err), $timeout(function() {
4046 $scope.step = 4
4047 }, 1)
4048 })
4049 }
4050 1 == n && $scope.initFlow(), 2 == n && ($scope.step = 2), 3 == n && ($scope.mnemonicHasPassphrase ? $scope.step = 3 : finalStep()), 4 == n && finalStep()
4051 }, $scope.addButton = function(index, item) {
4052 var newWord = {
4053 word: item.word,
4054 prevIndex: index
4055 };
4056 $scope.customWords.push(newWord), $scope.shuffledMnemonicWords[index].selected = !0, $scope.shouldContinue()
4057 }, $scope.removeButton = function(index, item) {
4058 $scope.loading || ($scope.customWords.splice(index, 1), $scope.shuffledMnemonicWords[item.prevIndex].selected = !1, $scope.shouldContinue())
4059 }, $scope.shouldContinue = function() {
4060 $scope.customWords.length == $scope.shuffledMnemonicWords.length ? $scope.selectComplete = !0 : $scope.selectComplete = !1
4061 }
4062 }), angular.module("copayApp.controllers").controller("buyAmazonController", function($rootScope, $scope, $ionicModal, $log, $timeout, $state, lodash, profileService, bwcError, gettext, configService, walletService, fingerprintService, amazonService, ongoingProcess) {
4063 var client, self = this,
4064 handleEncryptedWallet = function(client, cb) {
4065 if (!walletService.isEncrypted(client)) return cb();
4066 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
4067 return cb(err ? err : walletService.unlock(client, password))
4068 })
4069 };
4070 this.init = function() {
4071 var network = amazonService.getEnvironment();
4072 self.allWallets = profileService.getWallets(network, 1), (client = profileService.focusedClient) && (lodash.isEmpty(self.allWallets) || client.credentials.network == network && $timeout(function() {
4073 self.selectedWalletId = client.credentials.walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
4074 }, 100))
4075 }, $scope.openWalletsModal = function(wallets) {
4076 self.error = null, $scope.type = "GIFT", $scope.wallets = wallets, $scope.self = self, $ionicModal.fromTemplateUrl("views/modals/wallets.html", {
4077 scope: $scope,
4078 animation: "slide-in-up"
4079 }).then(function(modal) {
4080 $scope.walletsModal = modal, $scope.walletsModal.show()
4081 }), $scope.$on("walletSelected", function(ev, walletId) {
4082 $timeout(function() {
4083 client = profileService.getClient(walletId), self.selectedWalletId = walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
4084 }, 100), $scope.walletsModal.hide()
4085 })
4086 }, this.createTx = function() {
4087 self.error = null, self.errorInfo = null;
4088 var dataSrc = {
4089 currency: "USD",
4090 amount: $scope.fiat,
4091 uuid: self.selectedWalletId
4092 },
4093 outputs = [],
4094 config = configService.getSync(),
4095 configWallet = config.wallet,
4096 walletSettings = configWallet.settings;
4097 ongoingProcess.set("Processing Transaction...", !0), $timeout(function() {
4098 amazonService.createBitPayInvoice(dataSrc, function(err, dataInvoice) {
4099 if (err) return ongoingProcess.set("Processing Transaction...", !1), self.error = bwcError.msg(err), void $timeout(function() {
4100 $scope.$digest()
4101 });
4102 amazonService.getBitPayInvoice(dataInvoice.invoiceId, function(err, invoice) {
4103 if (err) return ongoingProcess.set("Processing Transaction...", !1), self.error = bwcError.msg(err), void $timeout(function() {
4104 $scope.$digest()
4105 });
4106 $log.debug("Fetch PayPro Request...", invoice.paymentUrls.BIP73), client.fetchPayPro({
4107 payProUrl: invoice.paymentUrls.BIP73
4108 }, function(err, paypro) {
4109 if (err) {
4110 ongoingProcess.set("Processing Transaction...", !1), $log.warn("Could not fetch payment request:", err);
4111 var msg = err.toString();
4112 return msg.match("HTTP") && (msg = gettext("Could not fetch payment information")), self.error = msg, void $timeout(function() {
4113 $scope.$digest()
4114 })
4115 }
4116 if (!paypro.verified) return ongoingProcess.set("Processing Transaction...", !1), $log.warn("Failed to verify payment protocol signatures"), self.error = gettext("Payment Protocol Invalid"), void $timeout(function() {
4117 $scope.$digest()
4118 });
4119 var address, comment, amount, url;
4120 address = paypro.toAddress, amount = paypro.amount, url = paypro.url, comment = "Amazon.com Gift Card", outputs.push({
4121 toAddress: address,
4122 amount: amount,
4123 message: comment
4124 });
4125 var txp = {
4126 toAddress: address,
4127 amount: amount,
4128 outputs: outputs,
4129 message: comment,
4130 payProUrl: url,
4131 excludeUnconfirmedUtxos: !configWallet.spendUnconfirmed,
4132 feeLevel: walletSettings.feeLevel || "normal"
4133 };
4134 walletService.createTx(client, txp, function(err, createdTxp) {
4135 if (ongoingProcess.set("Processing Transaction...", !1), err) return self.error = bwcError.msg(err), void $timeout(function() {
4136 $scope.$digest()
4137 });
4138 $scope.$emit("Local/NeedsConfirmation", createdTxp, function(accept) {
4139 accept && self.confirmTx(createdTxp, function(err, tx) {
4140 if (err) return ongoingProcess.set("Processing Transaction...", !1), self.error = bwcError.msg(err), void $timeout(function() {
4141 $scope.$digest()
4142 });
4143 ongoingProcess.set("Processing Transaction...", !0), dataSrc.accessKey = dataInvoice.accessKey, dataSrc.invoiceId = invoice.id, dataSrc.invoiceUrl = invoice.url, dataSrc.invoiceTime = invoice.invoiceTime, self.debounceCreate(0, dataSrc)
4144 })
4145 })
4146 })
4147 })
4148 })
4149 })
4150 }, 100)
4151 }, self.debounceCreate = lodash.throttle(function(count, dataSrc) {
4152 self.debounceCreateGiftCard(count, dataSrc)
4153 }, 8e3, {
4154 leading: !0
4155 }), self.debounceCreateGiftCard = function(count, dataSrc) {
4156 amazonService.createGiftCard(dataSrc, function(err, giftCard) {
4157 if ($log.debug("creating gift card " + count), err && (giftCard = {}, giftCard.status = "FAILURE", ongoingProcess.set("Processing Transaction...", !1), self.error = bwcError.msg(err), self.errorInfo = dataSrc, $timeout(function() {
4158 $scope.$digest()
4159 })), "PENDING" == giftCard.status && count < 3) return $log.debug("pending gift card not available yet"), void self.debounceCreate(count + 1, dataSrc, dataSrc);
4160 var now = 1e3 * moment().unix(),
4161 newData = giftCard;
4162 newData.invoiceId = dataSrc.invoiceId, newData.accessKey = dataSrc.accessKey, newData.invoiceUrl = dataSrc.invoiceUrl, newData.amount = dataSrc.amount, newData.date = dataSrc.invoiceTime || now, newData.uuid = dataSrc.uuid, "expired" == newData.status && amazonService.savePendingGiftCard(newData, {
4163 remove: !0
4164 }, function(err) {}), amazonService.savePendingGiftCard(newData, null, function(err) {
4165 ongoingProcess.set("Processing Transaction...", !1), $log.debug("Saving new gift card with status: " + newData.status), self.giftCard = newData, "PENDING" == newData.status && $state.transitionTo("amazon"), $timeout(function() {
4166 $scope.$digest()
4167 })
4168 })
4169 })
4170 }, this.confirmTx = function(txp, cb) {
4171 fingerprintService.check(client, function(err) {
4172 if (err) return $log.debug(err), cb(err);
4173 handleEncryptedWallet(client, function(err) {
4174 if (err) return $log.debug(err), bwcError.cb(err, null, cb);
4175 ongoingProcess.set("Processing Transaction...", !0), walletService.publishTx(client, txp, function(err, publishedTxp) {
4176 if (err) return $log.debug(err), bwcError.cb(err, null, cb);
4177 walletService.signTx(client, publishedTxp, function(err, signedTxp) {
4178 if (walletService.lock(client), err) return $log.debug(err), walletService.removeTx(client, signedTxp, function(err) {
4179 err && $log.debug(err)
4180 }), bwcError.cb(err, null, cb);
4181 walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) {
4182 if (err) return $log.debug(err), walletService.removeTx(client, broadcastedTxp, function(err) {
4183 err && $log.debug(err)
4184 }), bwcError.cb(err, null, cb);
4185 $timeout(function() {
4186 return cb(null, broadcastedTxp)
4187 }, 5e3)
4188 })
4189 })
4190 })
4191 })
4192 })
4193 }
4194 }), angular.module("copayApp.controllers").controller("buyCoinbaseController", function($scope, $log, $ionicModal, $timeout, lodash, profileService, coinbaseService, addressService, ongoingProcess) {
4195 var self = this;
4196 this.init = function(testnet) {
4197 self.allWallets = profileService.getWallets(testnet ? "testnet" : "livenet");
4198 var client = profileService.focusedClient;
4199 client && $timeout(function() {
4200 self.selectedWalletId = client.credentials.walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
4201 }, 100)
4202 }, this.getPaymentMethods = function(token) {
4203 coinbaseService.getPaymentMethods(token, function(err, p) {
4204 if (err) return void(self.error = err);
4205 self.paymentMethods = [], lodash.each(p.data, function(pm) {
4206 pm.allow_buy && self.paymentMethods.push(pm), pm.allow_buy && pm.primary_buy && ($scope.selectedPaymentMethod = pm)
4207 })
4208 })
4209 }, this.getPrice = function(token) {
4210 coinbaseService.buyPrice(token, "USD", function(err, b) {
4211 err || (self.buyPrice = b.data || null)
4212 })
4213 }, $scope.openWalletsModal = function(wallets) {
4214 self.error = null, $scope.type = "BUY", $scope.wallets = wallets, $scope.noColor = !0, $scope.self = self, $ionicModal.fromTemplateUrl("views/modals/wallets.html", {
4215 scope: $scope,
4216 animation: "slide-in-up"
4217 }).then(function(modal) {
4218 $scope.walletsModal = modal, $scope.walletsModal.show()
4219 }), $scope.$on("walletSelected", function(ev, walletId) {
4220 $timeout(function() {
4221 var client = profileService.getClient(walletId);
4222 self.selectedWalletId = walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
4223 }, 100), $scope.walletsModal.hide()
4224 })
4225 }, this.buyRequest = function(token, account) {
4226 self.error = null;
4227 var accountId = account.id,
4228 amount = $scope.amount ? $scope.amount : $scope.fiat,
4229 currency = $scope.amount ? "BTC" : "USD";
4230 if (amount) {
4231 var dataSrc = {
4232 amount: amount,
4233 currency: currency,
4234 payment_method: $scope.selectedPaymentMethod.id || null
4235 };
4236 ongoingProcess.set("Sending request...", !0), coinbaseService.buyRequest(token, accountId, dataSrc, function(err, data) {
4237 if (ongoingProcess.set("Sending request...", !1), err) return void(self.error = err);
4238 self.buyInfo = data.data
4239 })
4240 }
4241 }, this.confirmBuy = function(token, account, buy) {
4242 self.error = null;
4243 var accountId = account.id,
4244 buyId = buy.id;
4245 ongoingProcess.set("Buying Bitcoin...", !0), coinbaseService.buyCommit(token, accountId, buyId, function(err, b) {
4246 if (ongoingProcess.set("Buying Bitcoin...", !1), err) return void(self.error = err);
4247 var tx = b.data.transaction;
4248 tx && (ongoingProcess.set("Fetching transaction...", !0), coinbaseService.getTransaction(token, accountId, tx.id, function(err, updatedTx) {
4249 ongoingProcess.set("Fetching transaction...", !1), err && $log.debug(err), addressService.getAddress(self.selectedWalletId, !1, function(err, addr) {
4250 if (err) return void(self.error = {
4251 errors: [{
4252 message: "Could not create address"
4253 }]
4254 });
4255 updatedTx.data.toAddr = addr, coinbaseService.savePendingTransaction(updatedTx.data, {}, function(err) {
4256 err && $log.debug(err), "completed" == updatedTx.data.status ? self.sendToCopay(token, account, updatedTx.data) : (self.success = updatedTx.data, $timeout(function() {
4257 $scope.$emit("Local/CoinbaseTx")
4258 }, 1e3))
4259 })
4260 })
4261 }))
4262 })
4263 }, this.sendToCopay = function(token, account, tx) {
4264 self.error = null;
4265 var accountId = account.id;
4266 ongoingProcess.set("Sending funds to Copay...", !0);
4267 var data = {
4268 to: tx.toAddr,
4269 amount: tx.amount.amount,
4270 currency: tx.amount.currency,
4271 description: "Copay Wallet: " + self.selectedWalletName
4272 };
4273 coinbaseService.sendTo(token, accountId, data, function(err, res) {
4274 if (ongoingProcess.set("Sending funds to Copay...", !1), err) self.error = err;
4275 else {
4276 if (self.receiveInfo = res.data, !res.data.id) return;
4277 coinbaseService.getTransaction(token, accountId, res.data.id, function(err, sendTx) {
4278 coinbaseService.savePendingTransaction(tx, {
4279 remove: !0
4280 }, function(err) {
4281 coinbaseService.savePendingTransaction(sendTx.data, {}, function(err) {
4282 $timeout(function() {
4283 $scope.$emit("Local/CoinbaseTx")
4284 }, 1e3)
4285 })
4286 })
4287 })
4288 }
4289 })
4290 }
4291 }), angular.module("copayApp.controllers").controller("buyGlideraController", function($scope, $timeout, $ionicModal, profileService, addressService, glideraService, bwcError, lodash, ongoingProcess) {
4292 var self = this;
4293 this.show2faCodeInput = null, this.error = null, this.success = null, this.init = function(testnet) {
4294 self.allWallets = profileService.getWallets(testnet ? "testnet" : "livenet");
4295 var client = profileService.focusedClient;
4296 client && $timeout(function() {
4297 self.selectedWalletId = client.credentials.walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
4298 }, 100)
4299 }, $scope.openWalletsModal = function(wallets) {
4300 self.error = null, $scope.type = "BUY", $scope.wallets = wallets, $scope.noColor = !0, $scope.self = self, $ionicModal.fromTemplateUrl("views/modals/wallets.html", {
4301 scope: $scope,
4302 animation: "slide-in-up"
4303 }).then(function(modal) {
4304 $scope.walletsModal = modal, $scope.walletsModal.show()
4305 }), $scope.$on("walletSelected", function(ev, walletId) {
4306 $timeout(function() {
4307 var client = profileService.getClient(walletId);
4308 self.selectedWalletId = walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
4309 }, 100), $scope.walletsModal.hide()
4310 })
4311 }, this.getBuyPrice = function(token, price) {
4312 var self = this;
4313 if (this.error = null, !price || price && !price.qty && !price.fiat) return void(this.buyPrice = null);
4314 this.gettingBuyPrice = !0, glideraService.buyPrice(token, price, function(err, buyPrice) {
4315 if (self.gettingBuyPrice = !1, err) return void(self.error = "Could not get exchange information. Please, try again.");
4316 self.buyPrice = buyPrice
4317 })
4318 }, this.get2faCode = function(token) {
4319 var self = this;
4320 self.error = null, ongoingProcess.set("Sending 2FA code...", !0), $timeout(function() {
4321 glideraService.get2faCode(token, function(err, sent) {
4322 if (ongoingProcess.set("Sending 2FA code...", !1), err) return void(self.error = "Could not send confirmation code to your phone");
4323 self.show2faCodeInput = sent
4324 })
4325 }, 100)
4326 }, this.sendRequest = function(token, permissions, twoFaCode) {
4327 var self = this;
4328 self.error = null, ongoingProcess.set("Buying Bitcoin...", !0), $timeout(function() {
4329 addressService.getAddress(self.selectedWalletId, !1, function(err, walletAddr) {
4330 if (err) return ongoingProcess.set("Buying Bitcoin...", !1), void(self.error = bwcError.cb(err, "Could not create address"));
4331 var data = {
4332 destinationAddress: walletAddr,
4333 qty: self.buyPrice.qty,
4334 priceUuid: self.buyPrice.priceUuid,
4335 useCurrentPrice: !1,
4336 ip: null
4337 };
4338 glideraService.buy(token, twoFaCode, data, function(err, data) {
4339 if (ongoingProcess.set("Buying Bitcoin...", !1), err) return void(self.error = err);
4340 self.success = data, $scope.$emit("Local/GlideraTx")
4341 })
4342 })
4343 }, 100)
4344 }
4345 }), angular.module("copayApp.controllers").controller("coinbaseController", function($rootScope, $scope, $timeout, $ionicModal, profileService, configService, storageService, coinbaseService, lodash, platformInfo, ongoingProcess) {
4346 var isNW = platformInfo.isNW;
4347 platformInfo.isCordova && StatusBar.isVisible && StatusBar.backgroundColorByHexString("#4B6178"), this.openAuthenticateWindow = function() {
4348 var oauthUrl = this.getAuthenticateUrl();
4349 if (isNW) {
4350 var self = this,
4351 gui = require("nw.gui"),
4352 win = gui.Window.open(oauthUrl, {
4353 focus: !0,
4354 position: "center"
4355 });
4356 win.on("loaded", function() {
4357 var title = win.title; - 1 == title.indexOf("Coinbase") && ($scope.code = title, self.submitOauthCode(title), win.close())
4358 })
4359 } else $rootScope.openExternalLink(oauthUrl, "_system")
4360 }, this.getAuthenticateUrl = function() {
4361 return coinbaseService.getOauthCodeUrl()
4362 }, this.submitOauthCode = function(code) {
4363 var self = this,
4364 coinbaseTestnet = configService.getSync().coinbase.testnet,
4365 network = coinbaseTestnet ? "testnet" : "livenet";
4366 ongoingProcess.set("connectingCoinbase", !0), this.error = null, $timeout(function() {
4367 coinbaseService.getToken(code, function(err, data) {
4368 ongoingProcess.set("connectingCoinbase", !1), err ? (self.error = err, $timeout(function() {
4369 $scope.$apply()
4370 }, 100)) : data && data.access_token && data.refresh_token && storageService.setCoinbaseToken(network, data.access_token, function() {
4371 storageService.setCoinbaseRefreshToken(network, data.refresh_token, function() {
4372 $scope.$emit("Local/CoinbaseUpdated", data.access_token), $timeout(function() {
4373 $scope.$apply()
4374 }, 100)
4375 })
4376 })
4377 })
4378 }, 100)
4379 }, this.openTxModal = function(tx) {
4380 $scope.tx = tx, $ionicModal.fromTemplateUrl("views/modals/coinbase-tx-details.html", {
4381 scope: $scope,
4382 animation: "slide-in-up"
4383 }).then(function(modal) {
4384 $scope.coinbaseTxDetailsModal = modal, $scope.coinbaseTxDetailsModal.show()
4385 })
4386 }
4387 }), angular.module("copayApp.controllers").controller("coinbaseUriController", function($scope, $stateParams, $timeout, profileService, configService, coinbaseService, storageService, go, ongoingProcess) {
4388 this.submitOauthCode = function(code) {
4389 var self = this,
4390 coinbaseTestnet = configService.getSync().coinbase.testnet,
4391 network = coinbaseTestnet ? "testnet" : "livenet";
4392 ongoingProcess.set("connectingCoinbase", !0), this.error = null, $timeout(function() {
4393 coinbaseService.getToken(code, function(err, data) {
4394 ongoingProcess.set("connectingCoinbase", !1), err ? (self.error = err, $timeout(function() {
4395 $scope.$apply()
4396 }, 100)) : data && data.access_token && data.refresh_token && storageService.setCoinbaseToken(network, data.access_token, function() {
4397 storageService.setCoinbaseRefreshToken(network, data.refresh_token, function() {
4398 $scope.$emit("Local/CoinbaseUpdated", data.access_token), $timeout(function() {
4399 go.path("coinbase"), $scope.$apply()
4400 }, 100)
4401 })
4402 })
4403 })
4404 }, 100)
4405 }, this.checkCode = function() {
4406 if ($stateParams.url) {
4407 var match = $stateParams.url.match(/code=(.+)&/);
4408 if (match && match[1]) return this.code = match[1], this.submitOauthCode(this.code)
4409 }
4410 $log.error("Bad state: " + JSON.stringify($stateParams))
4411 }
4412 }), angular.module("copayApp.controllers").controller("copayersController", function($scope, $rootScope, $timeout, $log, $ionicModal, profileService, go, notification, platformInfo, gettext, gettextCatalog) {
4413 var self = this,
4414 isCordova = platformInfo.isCordova,
4415 delete_msg = (platformInfo.isWP, platformInfo.isAndroid, gettextCatalog.getString("Are you sure you want to delete this wallet?")),
4416 accept_msg = gettextCatalog.getString("Accept"),
4417 cancel_msg = gettextCatalog.getString("Cancel"),
4418 confirm_msg = gettextCatalog.getString("Confirm");
4419 self.init = function() {
4420 if (profileService.focusedClient.isComplete()) return $log.debug("Wallet Complete...redirecting"), void go.walletHome()
4421 };
4422 var _modalDeleteWallet = function() {
4423 $scope.title = delete_msg, $scope.accept_msg = accept_msg, $scope.cancel_msg = cancel_msg, $scope.confirm_msg = confirm_msg, $scope.okAction = doDeleteWallet, $scope.loading = !1, $ionicModal.fromTemplateUrl("views/modals/confirmation.html", {
4424 scope: $scope,
4425 animation: "slide-in-up"
4426 }).then(function(modal) {
4427 $scope.confirmationModal = modal, $scope.confirmationModal.show()
4428 })
4429 },
4430 doDeleteWallet = function() {
4431 var fc = profileService.focusedClient,
4432 walletName = fc.credentials.walletName;
4433 profileService.deleteWalletClient(fc, function(err) {
4434 err ? (self.error = err.message || err, $timeout(function() {
4435 $scope.$digest()
4436 })) : (go.walletHome(), $timeout(function() {
4437 notification.success(gettextCatalog.getString("Success"), gettextCatalog.getString('The wallet "{{walletName}}" was deleted', {
4438 walletName: walletName
4439 }))
4440 }))
4441 })
4442 };
4443 self.deleteWallet = function() {
4444 profileService.focusedClient;
4445 isCordova ? navigator.notification.confirm(delete_msg, function(buttonIndex) {
4446 1 == buttonIndex && doDeleteWallet()
4447 }, confirm_msg, [accept_msg, cancel_msg]) : _modalDeleteWallet()
4448 }, self.copySecret = function(secret) {
4449 isCordova && (window.cordova.plugins.clipboard.copy(secret), window.plugins.toast.showShortCenter(gettextCatalog.getString("Copied to clipboard")))
4450 }, self.shareSecret = function(secret) {
4451 if (isCordova) {
4452 var message = gettextCatalog.getString("Join my Copay wallet. Here is the invitation code: {{secret}} You can download Copay for your phone or desktop at https://copay.io", {
4453 secret: secret
4454 });
4455 window.plugins.socialsharing.share(message, gettextCatalog.getString("Invitation to share a Copay Wallet"), null, null)
4456 }
4457 }
4458 }), angular.module("copayApp.controllers").controller("createController", function($scope, $rootScope, $timeout, $log, lodash, go, profileService, configService, gettext, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess) {
4459 var isCordova = (platformInfo.isChromeApp, platformInfo.isCordova),
4460 self = (platformInfo.isDevel, this),
4461 defaults = configService.getDefaults();
4462 this.isWindowsPhoneApp = platformInfo.isWP && isCordova, $scope.account = 1;
4463 var COPAYER_PAIR_LIMITS = {
4464 1: 1,
4465 2: 2,
4466 3: 3,
4467 4: 4,
4468 5: 4,
4469 6: 4,
4470 7: 3,
4471 8: 3,
4472 9: 2,
4473 10: 2,
4474 11: 1,
4475 12: 1
4476 },
4477 defaults = configService.getDefaults();
4478 $scope.bwsurl = defaults.bws.url, $scope.derivationPath = derivationPathHelper.default, this.getNumber = function(num) {
4479 return new Array(num)
4480 };
4481 var updateRCSelect = function(n) {
4482 $scope.totalCopayers = n;
4483 var maxReq = COPAYER_PAIR_LIMITS[n];
4484 self.RCValues = lodash.range(1, maxReq + 1), $scope.requiredCopayers = Math.min(parseInt(n / 2 + 1), maxReq)
4485 },
4486 updateSeedSourceSelect = function(n) {
4487 self.seedOptions = [{
4488 id: "new",
4489 label: gettext("Random")
4490 }, {
4491 id: "set",
4492 label: gettext("Specify Recovery Phrase...")
4493 }], $scope.seedSource = self.seedOptions[0]
4494 };
4495 this.TCValues = lodash.range(2, defaults.limits.totalCopayers + 1), $scope.totalCopayers = defaults.wallet.totalCopayers, this.setTotalCopayers = function(tc) {
4496 updateRCSelect(tc), updateSeedSourceSelect(), self.seedSourceId = $scope.seedSource.id
4497 }, this.setSeedSource = function(src) {
4498 self.seedSourceId = $scope.seedSource.id, $timeout(function() {
4499 $rootScope.$apply()
4500 })
4501 }, this.create = function(form) {
4502 if (form && form.$invalid) return void(this.error = gettext("Please enter the required fields"));
4503 var opts = {
4504 m: $scope.requiredCopayers,
4505 n: $scope.totalCopayers,
4506 name: $scope.walletName,
4507 myName: $scope.totalCopayers > 1 ? $scope.myName : null,
4508 networkName: $scope.testnetEnabled ? "testnet" : "livenet",
4509 bwsurl: $scope.bwsurl,
4510 singleAddress: !0,
4511 walletPrivKey: $scope._walletPrivKey
4512 },
4513 setSeed = "set" == self.seedSourceId;
4514 if (setSeed) {
4515 var words = $scope.privateKey || ""; - 1 == words.indexOf(" ") && 1 == words.indexOf("prv") && words.length > 108 ? opts.extendedPrivateKey = words : opts.mnemonic = words, opts.passphrase = $scope.passphrase;
4516 var pathData = derivationPathHelper.parse($scope.derivationPath);
4517 if (!pathData) return void(this.error = gettext("Invalid derivation path"));
4518 opts.account = pathData.account, opts.networkName = pathData.networkName, opts.derivationStrategy = pathData.derivationStrategy
4519 } else opts.passphrase = $scope.createPassphrase;
4520 if (setSeed && !opts.mnemonic && !opts.extendedPrivateKey) return void(this.error = gettext("Please enter the wallet recovery phrase"));
4521 if ("ledger" == self.seedSourceId || "trezor" == self.seedSourceId) {
4522 var account = $scope.account;
4523 if (!account || account < 1) return void(this.error = gettext("Invalid account number"));
4524 "trezor" == self.seedSourceId && (account -= 1), opts.account = account, ongoingProcess.set("connecting" + self.seedSourceId, !0);
4525 ("ledger" == self.seedSourceId ? ledger : trezor).getInfoForNewWallet(opts.n > 1, account, function(err, lopts) {
4526 if (ongoingProcess.set("connecting" + self.seedSourceId, !1), err) return self.error = err, void $scope.$apply();
4527 opts = lodash.assign(lopts, opts), self._create(opts)
4528 })
4529 } else self._create(opts)
4530 }, this._create = function(opts) {
4531 ongoingProcess.set("creatingWallet", !0), $timeout(function() {
4532 profileService.createWallet(opts, function(err) {
4533 if (ongoingProcess.set("creatingWallet", !1), err) return $log.warn(err), self.error = err, void $timeout(function() {
4534 $rootScope.$apply()
4535 });
4536 "set" == self.seedSourceId && $timeout(function() {
4537 $rootScope.$emit("Local/BackupDone")
4538 }, 1), go.walletHome()
4539 })
4540 }, 100)
4541 }, this.formFocus = function(what) {
4542 this.isWindowsPhoneApp && (what && "my-name" == what ? (this.hideWalletName = !0, this.hideTabs = !0) : what && "wallet-name" == what ? this.hideTabs = !0 : (this.hideWalletName = !1, this.hideTabs = !1), $timeout(function() {
4543 $rootScope.$digest()
4544 }, 1))
4545 }, $scope.$on("$destroy", function() {
4546 $rootScope.hideWalletNavigation = !1
4547 }), updateSeedSourceSelect(), self.setSeedSource()
4548 }), angular.module("copayApp.controllers").controller("DevLoginController", function($scope, $rootScope, $routeParams, identityService) {
4549 var mail = $routeParams.mail,
4550 password = $routeParams.password,
4551 form = {};
4552 form.email = {}, form.password = {}, form.email.$modelValue = mail, form.password.$modelValue = password, identityService.open($scope, form)
4553 }), angular.module("copayApp.controllers").controller("disclaimerController", function($scope, $rootScope, $timeout, $log, $ionicSideMenuDelegate, profileService, applicationService, gettextCatalog, uxLanguage, go, storageService, gettext, platformInfo, ongoingProcess) {
4554 var self = this;
4555 self.tries = 0;
4556 platformInfo.isCordova;
4557 ongoingProcess.set("creatingWallet", !0);
4558 var create = function(opts) {
4559 opts = opts || {}, $log.debug("Creating profile"), profileService.create(opts, function(err) {
4560 if (err) return $log.warn(err), $scope.error = err, $scope.$apply(), $timeout(function() {
4561 return $log.warn("Retrying to create profile......"), 3 == self.tries ? (self.tries, create({
4562 noWallet: !0
4563 })) : (self.tries += 1, create())
4564 }, 3e3);
4565 $scope.error = "", ongoingProcess.set("creatingWallet", !1)
4566 })
4567 };
4568 this.init = function(opts) {
4569 $ionicSideMenuDelegate.canDragContent(!1), self.lang = uxLanguage.currentLanguage, storageService.getProfile(function(err, profile) {
4570 profile ? ($log.info("There is already a profile"), ongoingProcess.set("creatingWallet", !1), profileService.bindProfile(profile, function(err) {
4571 err && err.message && err.message.match("NONAGREEDDISCLAIMER") || ($log.debug("Disclaimer already accepted at #disclaimer. Redirect to Wallet Home."), $ionicSideMenuDelegate.canDragContent(!0), go.walletHome())
4572 })) : create(opts)
4573 })
4574 }, this.accept = function() {
4575 profileService.setDisclaimerAccepted(function(err) {
4576 err ? $log.error(err) : ($ionicSideMenuDelegate.canDragContent(!0), $rootScope.$emit("disclaimerAccepted"), go.walletHome())
4577 })
4578 }
4579 }), angular.module("copayApp.controllers").controller("exportController", function($rootScope, $scope, $timeout, $log, lodash, backupService, walletService, fingerprintService, configService, storageService, profileService, platformInfo, notification, go, gettext, gettextCatalog) {
4580 function handleEncryptedWallet(client, cb) {
4581 if (!walletService.isEncrypted(client)) return $scope.credentialsEncrypted = !1, cb();
4582 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
4583 return cb(err ? err : walletService.unlock(client, password))
4584 })
4585 }
4586
4587 function encodeWalletInfo() {
4588 var info, c = fc.credentials,
4589 derivationPath = fc.credentials.getBaseAddressDerivationPath(),
4590 encodingType = {
4591 mnemonic: 1,
4592 xpriv: 2,
4593 xpub: 3
4594 };
4595 return $scope.supported = "BIP44" == c.derivationStrategy && c.canSign(), $scope.supported ? (info = c.mnemonic ? {
4596 type: encodingType.mnemonic,
4597 data: c.mnemonic
4598 } : {
4599 type: encodingType.xpriv,
4600 data: c.xPrivKey
4601 }, info.type + "|" + info.data + "|" + c.network.toLowerCase() + "|" + derivationPath + "|" + c.mnemonicHasPassphrase) : null
4602 }
4603 var prevState, fc = (platformInfo.isWP, platformInfo.isAndroid, profileService.focusedClient);
4604 $scope.isEncrypted = fc.isPrivKeyEncrypted(), $scope.isCordova = platformInfo.isCordova, $scope.isSafari = platformInfo.isSafari, $scope.error = null, $scope.init = function(state) {
4605 $scope.supported = !0, $scope.exportQR = !1, $scope.noSignEnabled = !1, $scope.showAdvanced = !1, prevState = state || "walletHome", fingerprintService.check(fc, function(err) {
4606 if (err) return void go.path(prevState);
4607 handleEncryptedWallet(fc, function(err) {
4608 if (err) return void go.path(prevState);
4609 $scope.exportWalletInfo = encodeWalletInfo(), $timeout(function() {
4610 $scope.$apply()
4611 }, 1)
4612 })
4613 })
4614 }, $scope.$on("$destroy", function() {
4615 walletService.lock(fc)
4616 }), $scope.downloadWalletBackup = function() {
4617 $scope.getAddressbook(function(err, localAddressBook) {
4618 if (err) return void($scope.error = !0);
4619 var opts = {
4620 noSign: $scope.noSignEnabled,
4621 addressBook: localAddressBook
4622 };
4623 backupService.walletDownload($scope.password, opts, function(err) {
4624 if (err) return void($scope.error = !0);
4625 notification.success(gettext("Success"), gettext("Encrypted export file saved")), go.walletHome()
4626 })
4627 })
4628 }, $scope.getAddressbook = function(cb) {
4629 storageService.getAddressbook(fc.credentials.network, function(err, addressBook) {
4630 if (err) return cb(err);
4631 var localAddressBook = [];
4632 try {
4633 localAddressBook = JSON.parse(addressBook)
4634 } catch (ex) {
4635 $log.warn(ex)
4636 }
4637 return cb(null, localAddressBook)
4638 })
4639 }, $scope.getBackup = function(cb) {
4640 $scope.getAddressbook(function(err, localAddressBook) {
4641 if (err) return $scope.error = !0, cb(null);
4642 var opts = {
4643 noSign: $scope.noSignEnabled,
4644 addressBook: localAddressBook
4645 },
4646 ew = backupService.walletExport($scope.password, opts);
4647 return $scope.error = !ew, cb(ew)
4648 })
4649 }, $scope.viewWalletBackup = function() {
4650 $timeout(function() {
4651 $scope.getBackup(function(backup) {
4652 var ew = backup;
4653 ew && ($scope.backupWalletPlainText = ew)
4654 })
4655 }, 100)
4656 }, $scope.copyWalletBackup = function() {
4657 $scope.getBackup(function(backup) {
4658 var ew = backup;
4659 ew && (window.cordova.plugins.clipboard.copy(ew), window.plugins.toast.showShortCenter(gettextCatalog.getString("Copied to clipboard")))
4660 })
4661 }, $scope.sendWalletBackup = function() {
4662 var fc = profileService.focusedClient;
4663 window.plugins.toast.showShortCenter(gettextCatalog.getString("Preparing backup..."));
4664 var name = fc.credentials.walletName || fc.credentials.walletId;
4665 fc.alias && (name = fc.alias + " [" + name + "]"), $scope.getBackup(function(backup) {
4666 var ew = backup;
4667 if (ew) {
4668 $scope.noSignEnabled && (name += "(No Private Key)");
4669 var subject = "Copay Wallet Backup: " + name,
4670 body = "Here is the encrypted backup of the wallet " + name + ": \n\n" + ew + "\n\n To import this backup, copy all text between {...}, including the symbols {}";
4671 window.plugins.socialsharing.shareViaEmail(body, subject, null, null, null, null, function() {}, function() {})
4672 }
4673 })
4674 }
4675 }), angular.module("copayApp.controllers").controller("glideraController", function($rootScope, $scope, $timeout, $ionicModal, profileService, configService, storageService, glideraService, lodash, ongoingProcess, platformInfo) {
4676 platformInfo.isCordova && StatusBar.isVisible && StatusBar.backgroundColorByHexString("#4B6178"), this.getAuthenticateUrl = function() {
4677 return glideraService.getOauthCodeUrl()
4678 }, this.submitOauthCode = function(code) {
4679 var self = this,
4680 glideraTestnet = configService.getSync().glidera.testnet,
4681 network = glideraTestnet ? "testnet" : "livenet";
4682 ongoingProcess.set("connectingGlidera", !0), this.error = null, $timeout(function() {
4683 glideraService.getToken(code, function(err, data) {
4684 ongoingProcess.set("connectingGlidera", !1), err ? (self.error = err, $timeout(function() {
4685 $scope.$apply()
4686 }, 100)) : data && data.access_token && storageService.setGlideraToken(network, data.access_token, function() {
4687 $scope.$emit("Local/GlideraUpdated", data.access_token), $timeout(function() {
4688 $scope.$apply()
4689 }, 100)
4690 })
4691 })
4692 }, 100)
4693 }, this.openTxModal = function(token, tx) {
4694 var self = this;
4695 $scope.self = self, $scope.tx = tx, glideraService.getTransaction(token, tx.transactionUuid, function(error, tx) {
4696 $scope.tx = tx
4697 }), $ionicModal.fromTemplateUrl("views/modals/glidera-tx-details.html", {
4698 scope: $scope,
4699 backdropClickToClose: !1,
4700 hardwareBackButtonClose: !1,
4701 animation: "slide-in-up"
4702 }).then(function(modal) {
4703 $scope.glideraTxDetailsModal = modal, $scope.glideraTxDetailsModal.show()
4704 })
4705 }
4706 }), angular.module("copayApp.controllers").controller("glideraUriController", function($scope, $log, $stateParams, $timeout, profileService, configService, glideraService, storageService, go, ongoingProcess) {
4707 this.submitOauthCode = function(code) {
4708 $log.debug("Glidera Oauth Code:" + code);
4709 var self = this,
4710 glideraTestnet = configService.getSync().glidera.testnet,
4711 network = glideraTestnet ? "testnet" : "livenet";
4712 ongoingProcess.set("connectingGlidera", !0), this.error = null, $timeout(function() {
4713 glideraService.getToken(code, function(err, data) {
4714 ongoingProcess.set("connectingGlidera", !1), err ? (self.error = err, $timeout(function() {
4715 $scope.$apply()
4716 }, 100)) : data && data.access_token && storageService.setGlideraToken(network, data.access_token, function() {
4717 $scope.$emit("Local/GlideraUpdated", data.access_token), $timeout(function() {
4718 go.path("glidera"), $scope.$apply()
4719 }, 100)
4720 })
4721 })
4722 }, 100)
4723 }, this.checkCode = function() {
4724 if ($stateParams.url) {
4725 var match = $stateParams.url.match(/code=(.+)/);
4726 if (match && match[1]) return this.code = match[1], this.submitOauthCode(this.code)
4727 }
4728 $log.error("Bad state: " + JSON.stringify($stateParams))
4729 }
4730 }), angular.module("copayApp.controllers").controller("importController", function($scope, $rootScope, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, ledger, trezor, derivationPathHelper, platformInfo, bwcService, ongoingProcess) {
4731 var isChromeApp = platformInfo.isChromeApp,
4732 isDevel = platformInfo.isDevel,
4733 reader = new FileReader,
4734 defaults = configService.getDefaults(),
4735 errors = bwcService.getErrors();
4736 $scope.bwsurl = defaults.bws.url, $scope.derivationPath = derivationPathHelper.default, $scope.account = 1, $scope.importErr = !1;
4737 $scope.processWalletInfo = function(code) {
4738 if (code) {
4739 $scope.importErr = !1, $scope.error = null;
4740 var parsedCode = code.split("|");
4741 if (5 != parsedCode.length) return void($scope.error = gettext("Incorrect code format"));
4742 var info = {
4743 type: parsedCode[0],
4744 data: parsedCode[1],
4745 network: parsedCode[2],
4746 derivationPath: parsedCode[3],
4747 hasPassphrase: "true" == parsedCode[4]
4748 };
4749 1 == info.type && info.hasPassphrase && ($scope.error = gettext("Password required. Make sure to enter your password in advanced options")), $scope.derivationPath = info.derivationPath, $scope.testnetEnabled = "testnet" == info.network, $timeout(function() {
4750 $scope.words = info.data, $rootScope.$apply()
4751 }, 1)
4752 }
4753 }, $scope.setType = function(type) {
4754 $scope.type = type, $scope.error = null, $timeout(function() {
4755 $rootScope.$apply()
4756 }, 1)
4757 };
4758 var _importBlob = function(str, opts) {
4759 var str2, err;
4760 try {
4761 str2 = sjcl.decrypt($scope.password, str)
4762 } catch (e) {
4763 err = gettext("Could not decrypt file, check your password"), $log.warn(e)
4764 }
4765 if (err) return $scope.error = err, void $timeout(function() {
4766 $rootScope.$apply()
4767 });
4768 ongoingProcess.set("importingWallet", !0), opts.compressed = null, opts.password = null, $timeout(function() {
4769 profileService.importWallet(str2, opts, function(err, walletId) {
4770 ongoingProcess.set("importingWallet", !1), err ? $scope.error = err : ($rootScope.$emit("Local/WalletImported", walletId), notification.success(gettext("Success"), gettext("Your wallet has been imported correctly")), go.walletHome())
4771 })
4772 }, 100)
4773 },
4774 _importExtendedPrivateKey = function(xPrivKey, opts) {
4775 ongoingProcess.set("importingWallet", !0), $timeout(function() {
4776 profileService.importExtendedPrivateKey(xPrivKey, opts, function(err, walletId) {
4777 if (ongoingProcess.set("importingWallet", !1), err) return err instanceof errors.NOT_AUTHORIZED ? $scope.importErr = !0 : $scope.error = err, $timeout(function() {
4778 $scope.$apply()
4779 });
4780 $rootScope.$emit("Local/WalletImported", walletId), notification.success(gettext("Success"), gettext("Your wallet has been imported correctly")), go.walletHome()
4781 })
4782 }, 100)
4783 },
4784 _importMnemonic = function(words, opts) {
4785 ongoingProcess.set("importingWallet", !0), $timeout(function() {
4786 profileService.importMnemonic(words, opts, function(err, walletId) {
4787 if (ongoingProcess.set("importingWallet", !1), err) return err instanceof errors.NOT_AUTHORIZED ? $scope.importErr = !0 : $scope.error = err, $timeout(function() {
4788 $scope.$apply()
4789 });
4790 $rootScope.$emit("Local/WalletImported", walletId), notification.success(gettext("Success"), gettext("Your wallet has been imported correctly")), go.walletHome()
4791 })
4792 }, 100)
4793 };
4794 $scope.setDerivationPath = function() {
4795 $scope.testnetEnabled ? $scope.derivationPath = derivationPathHelper.defaultTestnet : $scope.derivationPath = derivationPathHelper.default
4796 }, $scope.getFile = function() {
4797 reader.onloadend = function(evt) {
4798 if (evt.target.readyState == FileReader.DONE) {
4799 var opts = {};
4800 opts.bwsurl = $scope.bwsurl, _importBlob(evt.target.result, opts)
4801 }
4802 }
4803 }, $scope.importBlob = function(form) {
4804 if (form.$invalid) return $scope.error = gettext("There is an error in the form"), void $timeout(function() {
4805 $scope.$apply()
4806 });
4807 var backupFile = $scope.file,
4808 backupText = form.backupText.$modelValue;
4809 form.password.$modelValue;
4810 if (!backupFile && !backupText) return $scope.error = gettext("Please, select your backup file"), void $timeout(function() {
4811 $scope.$apply()
4812 });
4813 if (backupFile) reader.readAsBinaryString(backupFile);
4814 else {
4815 var opts = {};
4816 opts.bwsurl = $scope.bwsurl, _importBlob(backupText, opts)
4817 }
4818 }, $scope.importMnemonic = function(form) {
4819 if (form.$invalid) return $scope.error = gettext("There is an error in the form"), void $timeout(function() {
4820 $scope.$apply()
4821 });
4822 var opts = {};
4823 $scope.bwsurl && (opts.bwsurl = $scope.bwsurl);
4824 var pathData = derivationPathHelper.parse($scope.derivationPath);
4825 if (!pathData) return void($scope.error = gettext("Invalid derivation path"));
4826 opts.account = pathData.account, opts.networkName = pathData.networkName, opts.derivationStrategy = pathData.derivationStrategy;
4827 var words = form.words.$modelValue || null;
4828 if ($scope.error = null, words) {
4829 if (0 == words.indexOf("xprv") || 0 == words.indexOf("tprv")) return _importExtendedPrivateKey(words, opts);
4830 if (0 == words.indexOf("xpub") || 0 == words.indexOf("tpuv")) return _importExtendedPublicKey(words, opts);
4831 var wordList = words.split(/[\u3000\s]+/);
4832 wordList.length % 3 != 0 && ($scope.error = gettext("Wrong number of recovery words:") + wordList.length)
4833 } else $scope.error = gettext("Please enter the recovery phrase");
4834 if ($scope.error) return void $timeout(function() {
4835 $scope.$apply()
4836 });
4837 form.passphrase.$modelValue;
4838 opts.passphrase = form.passphrase.$modelValue || null, _importMnemonic(words, opts)
4839 }, $scope.importTrezor = function(account, isMultisig) {
4840 trezor.getInfoForNewWallet(isMultisig, account, function(err, lopts) {
4841 if (ongoingProcess.clear(), err) return $scope.error = err, void $scope.$apply();
4842 lopts.externalSource = "trezor", lopts.bwsurl = $scope.bwsurl, ongoingProcess.set("importingWallet", !0), $log.debug("Import opts", lopts), profileService.importExtendedPublicKey(lopts, function(err, walletId) {
4843 if (ongoingProcess.set("importingWallet", !1), err) return $scope.error = err, $timeout(function() {
4844 $scope.$apply()
4845 });
4846 $rootScope.$emit("Local/WalletImported", walletId), notification.success(gettext("Success"), gettext("Your wallet has been imported correctly")), go.walletHome()
4847 })
4848 }, 100)
4849 }, $scope.importHW = function(form) {
4850 if (form.$invalid || $scope.account < 0) return $scope.error = gettext("There is an error in the form"), void $timeout(function() {
4851 $scope.$apply()
4852 });
4853 $scope.error = "", $scope.importErr = !1;
4854 var account = +$scope.account;
4855 if ("trezor" == $scope.seedSourceId) {
4856 if (account < 1) return void($scope.error = gettext("Invalid account number"));
4857 account -= 1
4858 }
4859 switch ($scope.seedSourceId) {
4860 case "ledger":
4861 ongoingProcess.set("connectingledger", !0), $scope.importLedger(account);
4862 break;
4863 case "trezor":
4864 ongoingProcess.set("connectingtrezor", !0), $scope.importTrezor(account, $scope.isMultisig);
4865 break;
4866 default:
4867 throw "Error: bad source id"
4868 }
4869 }, $scope.setSeedSource = function() {
4870 $scope.seedSource && ($scope.seedSourceId = $scope.seedSource.id, $timeout(function() {
4871 $rootScope.$apply()
4872 }))
4873 }, $scope.importLedger = function(account) {
4874 ledger.getInfoForNewWallet(!0, account, function(err, lopts) {
4875 if (ongoingProcess.clear(), err) return $scope.error = err, void $scope.$apply();
4876 lopts.externalSource = "ledger", lopts.bwsurl = $scope.bwsurl, ongoingProcess.set("importingWallet", !0), $log.debug("Import opts", lopts), profileService.importExtendedPublicKey(lopts, function(err, walletId) {
4877 if (ongoingProcess.set("importingWallet", !1), err) return $scope.error = err, $timeout(function() {
4878 $scope.$apply()
4879 });
4880 $rootScope.$emit("Local/WalletImported", walletId), notification.success(gettext("Success"), gettext("Your wallet has been imported correctly")), go.walletHome()
4881 })
4882 }, 100)
4883 },
4884 function() {
4885 $scope.seedOptions = [], isChromeApp && $scope.seedOptions.push({
4886 id: "ledger",
4887 label: "Ledger Hardware Wallet"
4888 }), (isChromeApp || isDevel) && ($scope.seedOptions.push({
4889 id: "trezor",
4890 label: "Trezor Hardware Wallet"
4891 }), $scope.seedSource = $scope.seedOptions[0])
4892 }(), $scope.setSeedSource("new")
4893 }), angular.module("copayApp.controllers").controller("indexController", function($rootScope, $scope, $log, $filter, $timeout, $ionicScrollDelegate, $ionicPopup, $ionicSideMenuDelegate, $httpBackend, latestReleaseService, feeService, bwcService, pushNotificationsService, lodash, go, profileService, configService, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, addonManager, bwcError, txFormatService, uxLanguage, glideraService, coinbaseService, platformInfo, addressbookService, openURLService, ongoingProcess, orion) {
4894 var self = this,
4895 errors = bwcService.getErrors(),
4896 historyUpdateInProgress = {},
4897 isChromeApp = platformInfo.isChromeApp,
4898 isCordova = platformInfo.isCordova,
4899 isNW = platformInfo.isNW,
4900 ret = {};
4901 ret.isCordova = isCordova, ret.isChromeApp = isChromeApp, ret.isSafari = platformInfo.isSafari, ret.isWindowsPhoneApp = platformInfo.isWP,
4902 ret.historyShowLimit = 10, ret.historyShowMoreLimit = 10, ret.isSearching = !1, ret.prevState = "walletHome", ret.physicalScreenWidth = window.innerWidth > 0 ? window.innerWidth : screen.width, ret.assetVersion = window.commitHash, ret.menu = [{
4903 title: gettext("Receive"),
4904 icon: {
4905 false: "icon-receive",
4906 true: "icon-receive-active"
4907 },
4908 link: "receive"
4909 }, {
4910 title: gettext("Activity"),
4911 icon: {
4912 false: "icon-activity",
4913 true: "icon-activity-active"
4914 },
4915 link: "walletHome"
4916 }, {
4917 title: gettext("Send"),
4918 icon: {
4919 false: "icon-send",
4920 true: "icon-send-active"
4921 },
4922 link: "send"
4923 }], ret.addonViews = addonManager.addonViews(), ret.txTemplateUrl = addonManager.txTemplateUrl() || "views/includes/transaction.html", ret.tab = "walletHome";
4924 var vanillaScope = ret;
4925 isNW && latestReleaseService.checkLatestRelease(function(err, newRelease) {
4926 if (err) return void $log.warn(err);
4927 newRelease && ($scope.newRelease = gettext("There is a new version of Copay. Please update"))
4928 }), self.goHome = function() {
4929 go.walletHome()
4930 }, self.showTokens = function() {
4931 go.path("tokens")
4932 }, self.allowRefresher = function() {
4933 0 != $ionicSideMenuDelegate.getOpenRatio() && (self.allowPullToRefresh = !1)
4934 }, self.hideBalance = function() {
4935 storageService.getHideBalanceFlag(self.walletId, function(err, shouldHideBalance) {
4936 self.shouldHideBalance = !err && "true" == shouldHideBalance
4937 })
4938 }, self.onHold = function() {
4939 self.shouldHideBalance = !self.shouldHideBalance, storageService.setHideBalanceFlag(self.walletId, self.shouldHideBalance.toString(), function() {})
4940 }, self.setWalletPreferencesTitle = function() {
4941 return gettext("Wallet Preferences")
4942 }, self.cleanInstance = function() {
4943 $log.debug("Cleaning Index Instance"), lodash.each(self, function(v, k) {
4944 if (!lodash.isFunction(v) && "hasProfile" != k && "tab" != k && "noFocusedWallet" != k && "backgroundColor" != k && "physicalScreenWidth" != k) return "loadingWallet" == k ? void(self.loadingWallet = !0) : lodash.isUndefined(vanillaScope[k]) ? void delete self[k] : void(self[k] = vanillaScope[k])
4945 })
4946 }, self.setFocusedWallet = function() {
4947 var fc = profileService.focusedClient;
4948 fc && (self.cleanInstance(), self.loadingWallet = !0, self.setSpendUnconfirmed(), $timeout(function() {
4949 $rootScope.$apply(), self.hasProfile = !0, self.isSingleAddress = !0, self.noFocusedWallet = !1, self.updating = !1, self.m = fc.credentials.m, self.n = fc.credentials.n, self.network = fc.credentials.network, self.copayerId = fc.credentials.copayerId, self.copayerName = fc.credentials.copayerName, self.requiresMultipleSignatures = fc.credentials.m > 1, self.isShared = fc.credentials.n > 1, self.walletName = fc.credentials.walletName, self.walletId = fc.credentials.walletId, self.isComplete = fc.isComplete(), self.canSign = fc.canSign(), self.isPrivKeyExternal = fc.isPrivKeyExternal(), self.isPrivKeyEncrypted = fc.isPrivKeyEncrypted(), self.externalSource = fc.getPrivKeyExternalSourceName(), self.account = fc.credentials.account, self.incorrectDerivation = !1 === fc.keyDerivationOk, "trezor" == self.externalSource && self.account++, self.txps = [], self.copayers = [], self.updateColor(), self.updateAlias(), self.setAddressbook(), self.initGlidera(), self.initCoinbase(), self.hideBalance(), self.setCustomBWSFlag(), self.isComplete ? go.is("copayers") && ($log.debug("Wallet Complete BEFORE update... redirect to home"), go.walletHome()) : ($log.debug("Wallet not complete BEFORE update... redirecting"), go.path("copayers")), profileService.needsBackup(fc, function(needsBackup) {
4950 self.needsBackup = needsBackup, self.openWallet(function() {
4951 self.isComplete ? go.is("copayers") && ($log.debug("Wallet Complete after update... redirect to home"), go.walletHome()) : ($log.debug("Wallet not complete after update... redirecting"), go.path("copayers"))
4952 })
4953 })
4954 }))
4955 }, self.setCustomBWSFlag = function() {
4956 var defaults = configService.getDefaults(),
4957 config = configService.getSync();
4958 self.usingCustomBWS = config.bwsFor && config.bwsFor[self.walletId] && config.bwsFor[self.walletId] != defaults.bws.url
4959 }, self.setTab = function(tab, reset, tries, switchState) {
4960 if (tries = tries || 0, "object" == typeof tab) return tab.open ? (tab.link && (self.tab = tab.link), void tab.open()) : self.setTab(tab.link, reset, tries, switchState);
4961 if (self.tab !== tab || reset) {
4962 if (!document.getElementById("menu-" + tab) && ++tries < 5) return $timeout(function() {
4963 self.setTab(tab, reset, tries, switchState)
4964 }, 300);
4965 self.tab && go.is("walletHome") || (self.tab = "walletHome");
4966 var changeTab = function() {
4967 if (document.getElementById(self.tab)) {
4968 document.getElementById(self.tab).className = "tab-out tab-view " + self.tab;
4969 var old = document.getElementById("menu-" + self.tab);
4970 old && (old.className = "")
4971 }
4972 if (document.getElementById(tab)) {
4973 document.getElementById(tab).className = "tab-in tab-view " + tab;
4974 var newe = document.getElementById("menu-" + tab);
4975 newe && (newe.className = "active")
4976 }
4977 self.tab = tab, $rootScope.$emit("Local/TabChanged", tab)
4978 };
4979 if (switchState && !go.is("walletHome")) return void go.path("walletHome", function() {
4980 changeTab()
4981 });
4982 changeTab()
4983 }
4984 };
4985 var _walletStatusHash = function(walletStatus) {
4986 return walletStatus ? walletStatus.balance.totalAmount : self.totalBalanceSat
4987 };
4988 self.updateAll = function(opts, initStatusHash, tries) {
4989 $scope.$broadcast("scroll.refreshComplete"), tries = tries || 0, opts = opts || {};
4990 var fc = profileService.focusedClient;
4991 if (fc) {
4992 var walletId = fc.credentials.walletId;
4993 opts.untilItChanges && lodash.isUndefined(initStatusHash) && (initStatusHash = _walletStatusHash(), $log.debug("Updating status until it changes. initStatusHash:" + initStatusHash));
4994 var get = function(cb) {
4995 return opts.walletStatus ? cb(null, opts.walletStatus) : (self.updateError = !1, fc.getStatus({
4996 twoStep: !0
4997 }, function(err, ret) {
4998 return err ? self.updateError = bwcError.msg(err, gettext("Could not update Wallet")) : (self.isSingleAddress = !0, opts.quiet || (self.updating = "running" == ret.wallet.scanStatus)), cb(err, ret)
4999 }))
5000 };
5001 opts.triggerTxUpdate && !opts.untilItChanges && $timeout(function() {
5002 self.debounceUpdateHistory()
5003 }, 1), $timeout(function() {
5004 opts.quiet || (self.updating = !0), $log.debug("Updating Status:", fc.credentials.walletName, tries), get(function(err, walletStatus) {
5005 var currentStatusHash = _walletStatusHash(walletStatus);
5006 return $log.debug("Status update. hash:" + currentStatusHash + " Try:" + tries), !err && opts.untilItChanges && initStatusHash == currentStatusHash && tries < 7 && walletId == profileService.focusedClient.credentials.walletId ? $timeout(function() {
5007 return $log.debug("Retrying update... Try:" + tries), self.updateAll({
5008 walletStatus: null,
5009 untilItChanges: !0,
5010 triggerTxUpdate: opts.triggerTxUpdate
5011 }, initStatusHash, ++tries)
5012 }, 1400 * tries) : walletId == profileService.focusedClient.credentials.walletId ? (self.updating = !1, err ? void self.handleError(err) : ($log.debug("Wallet Status:", walletStatus), self.setPendingTxps(walletStatus.pendingTxps), self.lastUpdate = Date.now(), self.walletName = walletStatus.wallet.name, self.walletSecret = walletStatus.wallet.secret, self.walletStatus = walletStatus.wallet.status, self.walletScanStatus = walletStatus.wallet.scanStatus, self.copayers = walletStatus.wallet.copayers, self.preferences = walletStatus.preferences, self.setBalance(walletStatus.balance), self.otherWallets = lodash.filter(profileService.getWallets(self.network), function(w) {
5013 return w.id != self.walletId
5014 }), $rootScope.$emit("Local/BalanceUpdated", walletStatus.balance), $rootScope.$apply(), opts.triggerTxUpdate && opts.untilItChanges ? $timeout(function() {
5015 self.debounceUpdateHistory()
5016 }, 1) : self.loadingWallet = !1, opts.cb ? opts.cb() : void 0)) : void 0
5017 })
5018 })
5019 }
5020 }, self.setSpendUnconfirmed = function(spendUnconfirmed) {
5021 self.spendUnconfirmed = spendUnconfirmed || configService.getSync().wallet.spendUnconfirmed
5022 }, self.updateBalance = function() {
5023 var fc = profileService.focusedClient;
5024 $timeout(function() {
5025 ongoingProcess.set("updatingBalance", !0), $log.debug("Updating Balance"), fc.getBalance(function(err, balance) {
5026 if (ongoingProcess.set("updatingBalance", !1), err) return void self.handleError(err);
5027 $log.debug("Wallet Balance:", balance), self.setBalance(balance)
5028 })
5029 })
5030 }, self.updatePendingTxps = function() {
5031 var fc = profileService.focusedClient;
5032 $timeout(function() {
5033 self.updating = !0, $log.debug("Updating PendingTxps"), fc.getTxProposals({}, function(err, txps) {
5034 self.updating = !1, err ? self.handleError(err) : ($log.debug("Wallet PendingTxps:", txps), self.setPendingTxps(txps)), $rootScope.$apply()
5035 })
5036 })
5037 };
5038 var _handleError = function(err) {
5039 if ($log.warn("Client ERROR: ", err), err instanceof errors.NOT_AUTHORIZED) self.notAuthorized = !0, go.walletHome();
5040 else if (err instanceof errors.NOT_FOUND) self.showErrorPopup(gettext("Could not access Wallet Service: Not found"));
5041 else {
5042 var msg = "";
5043 $scope.$emit("Local/ClientError", err.error ? err.error : err);
5044 var msg = bwcError.msg(err, gettext("Error at Wallet Service"));
5045 self.showErrorPopup(msg)
5046 }
5047 };
5048 self.handleError = lodash.debounce(_handleError, 1e3), self.openWallet = function(cb) {
5049 var fc = profileService.focusedClient;
5050 $timeout(function() {
5051 $rootScope.$apply(), self.updating = !0, self.updateError = !1, fc.openWallet(function(err, walletStatus) {
5052 if (self.updating = !1, err) return self.updateError = !0, void self.handleError(err);
5053 $log.debug("Wallet Opened"), self.updateAll(lodash.isObject(walletStatus) ? {
5054 walletStatus: walletStatus,
5055 cb: cb
5056 } : {
5057 cb: cb
5058 }), $rootScope.$apply()
5059 })
5060 })
5061 }, self.setPendingTxps = function(txps) {
5062 self.pendingTxProposalsCountForUs = 0;
5063 var now = Math.floor(Date.now() / 1e3);
5064 lodash.each(txps, function(tx) {
5065 tx = txFormatService.processTx(tx), tx.createdOn > now && (tx.createdOn = now);
5066 var action = lodash.find(tx.actions, {
5067 copayerId: self.copayerId
5068 });
5069 action || "pending" != tx.status || (tx.pendingForUs = !0), action && "accept" == action.type ? tx.statusForUs = "accepted" : action && "reject" == action.type ? tx.statusForUs = "rejected" : tx.statusForUs = "pending", tx.deleteLockTime || (tx.canBeRemoved = !0), tx.creatorId != self.copayerId && (self.pendingTxProposalsCountForUs = self.pendingTxProposalsCountForUs + 1), addonManager.formatPendingTxp(tx)
5070 }), self.txps = txps
5071 };
5072 self.processNewTxs = function(txs) {
5073 var now = (configService.getSync().wallet.settings, Math.floor(Date.now() / 1e3)),
5074 txHistoryUnique = {},
5075 ret = [];
5076 return self.hasUnsafeConfirmed = !1, lodash.each(txs, function(tx) {
5077 tx = txFormatService.processTx(tx), tx.time > now && (tx.time = now), tx.confirmations >= 6 ? tx.safeConfirmed = "6+" : (tx.safeConfirmed = !1, self.hasUnsafeConfirmed = !0), tx.note && (delete tx.note.encryptedEditedByName, delete tx.note.encryptedBody), txHistoryUnique[tx.txid] ? $log.debug("Ignoring duplicate TX in history: " + tx.txid) : (ret.push(tx), txHistoryUnique[tx.txid] = !0)
5078 }), ret
5079 }, self.updateAlias = function() {
5080 var config = configService.getSync();
5081 config.aliasFor = config.aliasFor || {}, self.alias = config.aliasFor[self.walletId], profileService.focusedClient.alias = self.alias
5082 }, self.updateColor = function() {
5083 var config = configService.getSync();
5084 config.colorFor = config.colorFor || {}, self.backgroundColor = config.colorFor[self.walletId] || "#4A90E2";
5085 var fc = profileService.focusedClient;
5086 fc.backgroundColor = self.backgroundColor, isCordova && StatusBar.isVisible && StatusBar.backgroundColorByHexString(fc.backgroundColor)
5087 }, self.setBalance = function(balance) {
5088 if (balance) {
5089 var config = configService.getSync().wallet.settings;
5090 self.balanceByAddress = balance.byAddress, self.spendUnconfirmed ? (self.totalBalanceSat = balance.totalAmount, self.lockedBalanceSat = balance.lockedAmount, self.availableBalanceSat = balance.availableAmount, self.totalBytesToSendMax = balance.totalBytesToSendMax, self.pendingAmount = null) : (self.totalBalanceSat = balance.totalConfirmedAmount, self.lockedBalanceSat = balance.lockedConfirmedAmount, self.availableBalanceSat = balance.availableConfirmedAmount, self.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax, self.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount), self.unitToSatoshi = config.unitToSatoshi, self.satToUnit = 1 / self.unitToSatoshi, self.unitName = config.unitName, self.totalBalanceStr = profileService.formatAmount(self.totalBalanceSat) + " " + self.unitName, self.lockedBalanceStr = profileService.formatAmount(self.lockedBalanceSat) + " " + self.unitName, self.availableBalanceStr = profileService.formatAmount(self.availableBalanceSat) + " " + self.unitName, self.pendingAmount ? self.pendingAmountStr = profileService.formatAmount(self.pendingAmount) + " " + self.unitName : self.pendingAmountStr = null, self.alternativeName = config.alternativeName, self.alternativeIsoCode = config.alternativeIsoCode, addressService.isUsed(self.walletId, balance.byAddress, function(err, used) {
5091 used && ($log.debug("Address used. Creating new"), $rootScope.$emit("Local/AddressIsUsed"))
5092 }), rateService.whenAvailable(function() {
5093 var totalBalanceAlternative = rateService.toFiat(self.totalBalanceSat, self.alternativeIsoCode),
5094 lockedBalanceAlternative = rateService.toFiat(self.lockedBalanceSat, self.alternativeIsoCode),
5095 alternativeConversionRate = rateService.toFiat(1e8, self.alternativeIsoCode);
5096 self.totalBalanceAlternative = $filter("formatFiatAmount")(totalBalanceAlternative), self.lockedBalanceAlternative = $filter("formatFiatAmount")(lockedBalanceAlternative), self.alternativeConversionRate = $filter("formatFiatAmount")(alternativeConversionRate), self.alternativeBalanceAvailable = !0, self.isRateAvailable = !0, $rootScope.$apply()
5097 }), rateService.isAvailable() || $rootScope.$apply()
5098 }
5099 }, self.removeAndMarkSoftConfirmedTx = function(txs) {
5100 return lodash.filter(txs, function(tx) {
5101 if (tx.confirmations >= 12) return tx;
5102 tx.recent = !0
5103 })
5104 }, self.getSavedTxs = function(walletId, cb) {
5105 storageService.getTxHistory(walletId, function(err, txs) {
5106 if (err) return cb(err);
5107 var localTxs = [];
5108 if (!txs) return cb(null, localTxs);
5109 try {
5110 localTxs = JSON.parse(txs)
5111 } catch (ex) {
5112 $log.warn(ex)
5113 }
5114 return cb(null, lodash.compact(localTxs))
5115 })
5116 }, self.updateLocalTxHistory = function(client, cb) {
5117 var FIRST_LIMIT = 5,
5118 requestLimit = FIRST_LIMIT,
5119 walletId = client.credentials.walletId,
5120 config = configService.getSync().wallet.settings,
5121 fixTxsUnit = function(txs) {
5122 if (txs && txs[0] && txs[0].amountStr) {
5123 if (txs[0].amountStr.split(" ")[1] != config.unitName) {
5124 var name = " " + config.unitName;
5125 $log.debug("Fixing Tx Cache Unit to:" + name), lodash.each(txs, function(tx) {
5126 tx.amountStr = profileService.formatAmount(tx.amount) + name, tx.feeStr = profileService.formatAmount(tx.fees) + name
5127 })
5128 }
5129 }
5130 };
5131 self.getSavedTxs(walletId, function(err, txsFromLocal) {
5132 function getNewTxs(newTxs, skip, i_cb) {
5133 self.getTxsFromServer(client, skip, endingTxid, requestLimit, function(err, res, shouldContinue) {
5134 if (err) return i_cb(err);
5135 if (newTxs = newTxs.concat(lodash.compact(res)), skip += requestLimit, $log.debug("Syncing TXs. Got:" + newTxs.length + " Skip:" + skip, " EndingTxid:", endingTxid, " Continue:", shouldContinue), !shouldContinue) return newTxs = self.processNewTxs(newTxs), $log.debug("Finished Sync: New / soft confirmed Txs: " + newTxs.length), i_cb(null, newTxs);
5136 if (requestLimit = 50, getNewTxs(newTxs, skip, i_cb), walletId == profileService.focusedClient.credentials.walletId) {
5137 if (self.txProgress = newTxs.length, self.completeHistory < FIRST_LIMIT && 0 == txsFromLocal.length) {
5138 $log.debug("Showing partial history");
5139 var newHistory = self.processNewTxs(newTxs);
5140 newHistory = lodash.compact(newHistory.concat(confirmedTxs)), self.completeHistory = newHistory, self.setCompactTxHistory()
5141 }
5142 $timeout(function() {
5143 $rootScope.$apply()
5144 })
5145 }
5146 })
5147 }
5148 if (err) return cb(err);
5149 fixTxsUnit(txsFromLocal);
5150 var confirmedTxs = self.removeAndMarkSoftConfirmedTx(txsFromLocal),
5151 endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null,
5152 endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null;
5153 walletId == profileService.focusedClient.credentials.walletId && (self.completeHistory = txsFromLocal, self.setCompactTxHistory()), historyUpdateInProgress[walletId] || (historyUpdateInProgress[walletId] = !0, getNewTxs([], 0, function(err, txs) {
5154 if (err) return cb(err);
5155 var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) {
5156 return x.txid
5157 });
5158 ! function(cb2) {
5159 if (!endingTs) return cb2();
5160 $log.debug("Syncing notes from: " + endingTs), client.getTxNotes({
5161 minTs: endingTs
5162 }, function(err, notes) {
5163 return err ? ($log.warn(err), cb2()) : (lodash.each(notes, function(note) {
5164 $log.debug("Note for " + note.txid), lodash.each(newHistory, function(tx) {
5165 tx.txid == note.txid && ($log.debug("...updating note for " + note.txid), tx.note = note)
5166 })
5167 }), cb2())
5168 })
5169 }(function() {
5170 var historyToSave = JSON.stringify(newHistory);
5171 return lodash.each(txs, function(tx) {
5172 tx.recent = !0
5173 }), $log.debug("Tx History synced. Total Txs: " + newHistory.length), walletId == profileService.focusedClient.credentials.walletId && (self.completeHistory = newHistory, self.setCompactTxHistory()), storageService.setTxHistory(historyToSave, walletId, function() {
5174 return $log.debug("Tx History saved."), cb()
5175 })
5176 })
5177 }))
5178 })
5179 }, self.showMore = function() {
5180 $timeout(function() {
5181 self.isSearching ? (self.txHistorySearchResults = self.result.slice(0, self.nextTxHistory), $log.debug("Total txs: ", self.txHistorySearchResults.length + "/" + self.result.length), self.txHistorySearchResults.length >= self.result.length && (self.historyShowMore = !1)) : (self.txHistory = self.completeHistory.slice(0, self.nextTxHistory), $log.debug("Total txs: ", self.txHistory.length + "/" + self.completeHistory.length), self.txHistory.length >= self.completeHistory.length && (self.historyShowMore = !1)), self.nextTxHistory += self.historyShowMoreLimit, $scope.$broadcast("scroll.infiniteScrollComplete")
5182 }, 100)
5183 }, self.startSearch = function() {
5184 self.isSearching = !0, self.txHistorySearchResults = [], self.result = [], self.historyShowMore = !1, self.nextTxHistory = self.historyShowMoreLimit
5185 }, self.cancelSearch = function() {
5186 self.isSearching = !1, self.result = [], self.setCompactTxHistory()
5187 }, self.updateSearchInput = function(search) {
5188 self.search = search, isCordova && window.plugins.toast.hide(), self.throttleSearch(), $ionicScrollDelegate.resize()
5189 }, self.throttleSearch = lodash.throttle(function() {
5190 self.txHistorySearchResults = function(search) {
5191 function computeSearchableString(tx) {
5192 var addrbook = "";
5193 tx.addressTo && self.addressbook && self.addressbook[tx.addressTo] && (addrbook = self.addressbook[tx.addressTo] || "");
5194 var searchableDate = computeSearchableDate(new Date(1e3 * tx.time)),
5195 message = tx.message ? tx.message : "",
5196 comment = tx.note ? tx.note.body : "",
5197 addressTo = tx.addressTo ? tx.addressTo : "";
5198 return (tx.amountStr + message + addressTo + addrbook + searchableDate + comment).toString().toLowerCase()
5199 }
5200
5201 function computeSearchableDate(date) {
5202 var day = ("0" + date.getDate()).slice(-2).toString();
5203 return [("0" + (date.getMonth() + 1)).slice(-2).toString(), day, date.getFullYear()].join("/")
5204 }
5205 return self.result = [], lodash.isEmpty(search) ? (self.historyShowMore = !1, []) : (self.result = lodash.filter(self.completeHistory, function(tx) {
5206 return tx.searcheableString || (tx.searcheableString = computeSearchableString(tx)), lodash.includes(tx.searcheableString, search.toLowerCase())
5207 }), self.result.length > self.historyShowLimit ? self.historyShowMore = !0 : self.historyShowMore = !1, self.result)
5208 }(self.search).slice(0, self.historyShowLimit), isCordova && window.plugins.toast.showShortBottom(gettextCatalog.getString("Matches: " + self.result.length)), $timeout(function() {
5209 $rootScope.$apply()
5210 })
5211 }, 1e3), self.getTxsFromServer = function(client, skip, endingTxid, limit, cb) {
5212 client.getTxHistory({
5213 skip: skip,
5214 limit: limit
5215 }, function(err, txsFromServer) {
5216 if (err) return cb(err);
5217 if (!txsFromServer.length) return cb();
5218 var res = lodash.takeWhile(txsFromServer, function(tx) {
5219 return tx.txid != endingTxid
5220 });
5221 return cb(null, res, res.length == limit)
5222 })
5223 }, self.updateHistory = function() {
5224 var fc = profileService.focusedClient;
5225 if (fc) {
5226 var walletId = fc.credentials.walletId;
5227 fc.isComplete() && ($log.debug("Updating Transaction History"), self.txHistoryError = !1, self.updatingTxHistory = !0, $timeout(function() {
5228 self.updateLocalTxHistory(fc, function(err) {
5229 historyUpdateInProgress[walletId] = self.updatingTxHistory = !1, self.loadingWallet = !1, self.txProgress = 0, err && (self.txHistoryError = !0), $timeout(function() {
5230 self.newTx = !1
5231 }, 1e3), $rootScope.$apply()
5232 })
5233 }))
5234 }
5235 }, self.setCompactTxHistory = function() {
5236 self.isSearching = !1, self.nextTxHistory = self.historyShowMoreLimit, self.txHistory = self.completeHistory ? self.completeHistory.slice(0, self.historyShowLimit) : null, self.historyShowMore = self.completeHistory ? self.completeHistory.length > self.historyShowLimit : null
5237 }, self.debounceUpdateHistory = lodash.debounce(function() {
5238 self.updateHistory()
5239 }, 1e3), self.throttledUpdateHistory = lodash.throttle(function() {
5240 self.updateHistory()
5241 }, 5e3), self.showErrorPopup = function(msg, cb) {
5242 $log.warn("Showing err popup:" + msg),
5243 function(msg, cb) {
5244 $scope.msg = msg, self.errorPopup = $ionicPopup.show({
5245 templateUrl: "views/includes/alert.html",
5246 scope: $scope
5247 }), $scope.close = function() {
5248 return cb()
5249 }
5250 }(msg, function() {
5251 if (self.errorPopup.close(), cb) return cb()
5252 })
5253 }, self.recreate = function(cb) {
5254 var fc = profileService.focusedClient;
5255 ongoingProcess.set("recreating", !0), fc.recreateWallet(function(err) {
5256 if (self.notAuthorized = !1, ongoingProcess.set("recreating", !1), err) return self.handleError(err), void $rootScope.$apply();
5257 profileService.bindWalletClient(fc, {
5258 force: !0
5259 }), self.startScan(self.walletId)
5260 })
5261 }, self.toggleLeftMenu = function() {
5262 profileService.isDisclaimerAccepted(function(val) {
5263 val ? go.toggleLeftMenu() : $log.debug("Disclaimer not accepted, cannot open menu")
5264 })
5265 }, self.retryScan = function() {
5266 var self = this;
5267 self.startScan(self.walletId)
5268 }, self.startScan = function(walletId) {
5269 $log.debug("Scanning wallet " + walletId);
5270 var c = profileService.walletClients[walletId];
5271 c.isComplete() && (self.walletId == walletId && (self.updating = !0), c.startScan({
5272 includeCopayerBranches: !0
5273 }, function(err) {
5274 err && self.walletId == walletId && (self.updating = !1, self.handleError(err), $rootScope.$apply())
5275 }))
5276 }, self.initGlidera = function(accessToken) {
5277 self.glideraEnabled = configService.getSync().glidera.enabled, self.glideraTestnet = configService.getSync().glidera.testnet;
5278 var network = self.glideraTestnet ? "testnet" : "livenet";
5279 if (self.glideraToken = null, self.glideraError = null, self.glideraPermissions = null, self.glideraEmail = null, self.glideraPersonalInfo = null, self.glideraTxs = null, self.glideraStatus = null, self.glideraEnabled) {
5280 glideraService.setCredentials(network);
5281 ! function(cb) {
5282 accessToken ? cb(null, accessToken) : storageService.getGlideraToken(network, cb)
5283 }(function(err, accessToken) {
5284 !err && accessToken && glideraService.getAccessTokenPermissions(accessToken, function(err, p) {
5285 err ? self.glideraError = err : (self.glideraToken = accessToken, self.glideraPermissions = p, self.updateGlidera({
5286 fullUpdate: !0
5287 }))
5288 })
5289 })
5290 }
5291 }, self.updateGlidera = function(opts) {
5292 if (self.glideraToken && self.glideraPermissions) {
5293 var accessToken = self.glideraToken,
5294 permissions = self.glideraPermissions;
5295 opts = opts || {}, glideraService.getStatus(accessToken, function(err, data) {
5296 self.glideraStatus = data
5297 }), glideraService.getLimits(accessToken, function(err, limits) {
5298 self.glideraLimits = limits
5299 }), permissions.transaction_history && glideraService.getTransactions(accessToken, function(err, data) {
5300 self.glideraTxs = data
5301 }), permissions.view_email_address && opts.fullUpdate && glideraService.getEmail(accessToken, function(err, data) {
5302 self.glideraEmail = data.email
5303 }), permissions.personal_info && opts.fullUpdate && glideraService.getPersonalInfo(accessToken, function(err, data) {
5304 self.glideraPersonalInfo = data
5305 })
5306 }
5307 }, self.initCoinbase = function(accessToken) {
5308 self.coinbaseEnabled = configService.getSync().coinbase.enabled, self.coinbaseTestnet = configService.getSync().coinbase.testnet;
5309 var network = self.coinbaseTestnet ? "testnet" : "livenet";
5310 if (self.coinbaseToken = null, self.coinbaseError = null, self.coinbasePermissions = null, self.coinbaseEmail = null, self.coinbasePersonalInfo = null, self.coinbaseTxs = null, self.coinbaseStatus = null, self.coinbaseEnabled) {
5311 coinbaseService.setCredentials(network);
5312 ! function(cb) {
5313 accessToken ? cb(null, accessToken) : storageService.getCoinbaseToken(network, cb)
5314 }(function(err, accessToken) {
5315 !err && accessToken && coinbaseService.getAccounts(accessToken, function(err, a) {
5316 err ? (self.coinbaseError = err, err.errors[0] && "expired_token" == err.errors[0].id && self.refreshCoinbaseToken()) : (self.coinbaseToken = accessToken, lodash.each(a.data, function(account) {
5317 account.primary && "wallet" == account.type && (self.coinbaseAccount = account, self.updateCoinbase())
5318 }))
5319 })
5320 })
5321 }
5322 }, self.updateCoinbase = lodash.debounce(function(opts) {
5323 if (self.coinbaseToken && self.coinbaseAccount) {
5324 var accessToken = self.coinbaseToken,
5325 accountId = self.coinbaseAccount.id;
5326 opts = opts || {}, opts.updateAccount && coinbaseService.getAccount(accessToken, accountId, function(err, a) {
5327 if (err) return self.coinbaseError = err, void(err.errors[0] && "expired_token" == err.errors[0].id && self.refreshCoinbaseToken());
5328 self.coinbaseAccount = a.data
5329 }), coinbaseService.getCurrentUser(accessToken, function(err, u) {
5330 if (err) return self.coinbaseError = err, void(err.errors[0] && "expired_token" == err.errors[0].id && self.refreshCoinbaseToken());
5331 self.coinbaseUser = u.data
5332 }), coinbaseService.getPendingTransactions(function(err, txs) {
5333 self.coinbasePendingTransactions = lodash.isEmpty(txs) ? null : txs, lodash.forEach(txs, function(dataFromStorage, txId) {
5334 "sell" == dataFromStorage.type && "completed" == dataFromStorage.status || "buy" == dataFromStorage.type && "completed" == dataFromStorage.status || "error" == dataFromStorage.status || "send" == dataFromStorage.type && "completed" == dataFromStorage.status || coinbaseService.getTransaction(accessToken, accountId, txId, function(err, tx) {
5335 if (err) return err.errors[0] && "expired_token" == err.errors[0].id ? void self.refreshCoinbaseToken() : void coinbaseService.savePendingTransaction(dataFromStorage, {
5336 status: "error",
5337 error: err
5338 }, function(err) {
5339 err && $log.debug(err)
5340 });
5341 _updateCoinbasePendingTransactions(dataFromStorage, tx.data), self.coinbasePendingTransactions[txId] = dataFromStorage, "send" == tx.data.type && "completed" == tx.data.status && tx.data.from ? coinbaseService.sellPrice(accessToken, dataFromStorage.sell_price_currency, function(err, s) {
5342 if (err) return err.errors[0] && "expired_token" == err.errors[0].id ? void self.refreshCoinbaseToken() : void coinbaseService.savePendingTransaction(dataFromStorage, {
5343 status: "error",
5344 error: err
5345 }, function(err) {
5346 err && $log.debug(err)
5347 });
5348 var newSellPrice = s.data.amount;
5349 if (Math.abs((newSellPrice - dataFromStorage.sell_price_amount) / dataFromStorage.sell_price_amount * 100) < dataFromStorage.price_sensitivity.value) self.sellPending(tx.data);
5350 else {
5351 var error = {
5352 errors: [{
5353 message: "Price falls over the selected percentage"
5354 }]
5355 };
5356 coinbaseService.savePendingTransaction(dataFromStorage, {
5357 status: "error",
5358 error: error
5359 }, function(err) {
5360 err && $log.debug(err)
5361 })
5362 }
5363 }) : "buy" == tx.data.type && "completed" == tx.data.status && tx.data.buy ? self.sendToCopay(dataFromStorage) : coinbaseService.savePendingTransaction(dataFromStorage, {}, function(err) {
5364 err && $log.debug(err)
5365 })
5366 })
5367 })
5368 })
5369 }
5370 }, 1e3);
5371 var _updateCoinbasePendingTransactions = function(obj) {
5372 for (var i = 1; i < arguments.length; i++)
5373 for (var prop in arguments[i]) {
5374 var val = arguments[i][prop];
5375 "object" == typeof val ? _updateCoinbasePendingTransactions(obj[prop], val) : obj[prop] = val || obj[prop]
5376 }
5377 return obj
5378 };
5379 self.refreshCoinbaseToken = function() {
5380 var network = self.coinbaseTestnet ? "testnet" : "livenet";
5381 storageService.getCoinbaseRefreshToken(network, function(err, refreshToken) {
5382 refreshToken && coinbaseService.refreshToken(refreshToken, function(err, data) {
5383 err ? self.coinbaseError = err : data && data.access_token && data.refresh_token && storageService.setCoinbaseToken(network, data.access_token, function() {
5384 storageService.setCoinbaseRefreshToken(network, data.refresh_token, function() {
5385 $timeout(function() {
5386 self.initCoinbase(data.access_token)
5387 }, 100)
5388 })
5389 })
5390 })
5391 })
5392 }, self.sendToCopay = function(tx) {
5393 if (tx) {
5394 var data = {
5395 to: tx.toAddr,
5396 amount: tx.amount.amount,
5397 currency: tx.amount.currency,
5398 description: "To Copay Wallet"
5399 };
5400 coinbaseService.sendTo(self.coinbaseToken, self.coinbaseAccount.id, data, function(err, res) {
5401 if (err) {
5402 if (err.errors[0] && "expired_token" == err.errors[0].id) return void self.refreshCoinbaseToken();
5403 coinbaseService.savePendingTransaction(tx, {
5404 status: "error",
5405 error: err
5406 }, function(err) {
5407 err && $log.debug(err)
5408 })
5409 } else {
5410 if (!res.data.id) return void coinbaseService.savePendingTransaction(tx, {
5411 status: "error",
5412 error: err
5413 }, function(err) {
5414 err && $log.debug(err)
5415 });
5416 coinbaseService.getTransaction(self.coinbaseToken, self.coinbaseAccount.id, res.data.id, function(err, sendTx) {
5417 coinbaseService.savePendingTransaction(tx, {
5418 remove: !0
5419 }, function(err) {
5420 coinbaseService.savePendingTransaction(sendTx.data, {}, function(err) {
5421 $timeout(function() {
5422 self.updateCoinbase({
5423 updateAccount: !0
5424 })
5425 }, 1e3)
5426 })
5427 })
5428 })
5429 }
5430 })
5431 }
5432 }, self.sellPending = function(tx) {
5433 if (tx) {
5434 var data = tx.amount;
5435 data.commit = !0, coinbaseService.sellRequest(self.coinbaseToken, self.coinbaseAccount.id, data, function(err, res) {
5436 if (err) {
5437 if (err.errors[0] && "expired_token" == err.errors[0].id) return void self.refreshCoinbaseToken();
5438 coinbaseService.savePendingTransaction(tx, {
5439 status: "error",
5440 error: err
5441 }, function(err) {
5442 err && $log.debug(err)
5443 })
5444 } else {
5445 if (!res.data.transaction) return void coinbaseService.savePendingTransaction(tx, {
5446 status: "error",
5447 error: err
5448 }, function(err) {
5449 err && $log.debug(err)
5450 });
5451 coinbaseService.savePendingTransaction(tx, {
5452 remove: !0
5453 }, function(err) {
5454 coinbaseService.getTransaction(self.coinbaseToken, self.coinbaseAccount.id, res.data.transaction.id, function(err, updatedTx) {
5455 coinbaseService.savePendingTransaction(updatedTx.data, {}, function(err) {
5456 err && $log.debug(err), $timeout(function() {
5457 self.updateCoinbase({
5458 updateAccount: !0
5459 })
5460 }, 1e3)
5461 })
5462 })
5463 })
5464 }
5465 })
5466 }
5467 }, self.isInFocus = function(walletId) {
5468 var fc = profileService.focusedClient;
5469 return fc && fc.credentials.walletId == walletId
5470 }, self.setAddressbook = function(ab) {
5471 if (ab) return void(self.addressbook = ab);
5472 addressbookService.list(function(err, ab) {
5473 if (err) return void $log.error("Error getting the addressbook");
5474 self.addressbook = ab
5475 })
5476 }, $rootScope.$on("$stateChangeSuccess", function(ev, to, toParams, from, fromParams) {
5477 self.prevState = from.name || "walletHome", self.tab = "walletHome"
5478 }), $rootScope.$on("Local/ValidatingWalletEnded", function(ev, walletId, isOK) {
5479 self.isInFocus(walletId) && (self.incorrectDerivation = !1 === isOK)
5480 }), $rootScope.$on("Local/ClearHistory", function(event) {
5481 $log.debug("The wallet transaction history has been deleted"), self.txHistory = self.completeHistory = self.txHistorySearchResults = [], self.debounceUpdateHistory()
5482 }), $rootScope.$on("Local/AddressbookUpdated", function(event, ab) {
5483 self.setAddressbook(ab)
5484 }), $rootScope.$on("Local/ColorUpdated", function(event) {
5485 self.updateColor(), $timeout(function() {
5486 $rootScope.$apply()
5487 })
5488 }), $rootScope.$on("Local/AliasUpdated", function(event) {
5489 self.updateAlias(), $timeout(function() {
5490 $rootScope.$apply()
5491 })
5492 }), $rootScope.$on("Local/SpendUnconfirmedUpdated", function(event, spendUnconfirmed) {
5493 self.setSpendUnconfirmed(spendUnconfirmed), self.updateAll()
5494 }), $rootScope.$on("Local/GlideraUpdated", function(event, accessToken) {
5495 self.initGlidera(accessToken)
5496 }), $rootScope.$on("Local/CoinbaseUpdated", function(event, accessToken) {
5497 self.initCoinbase(accessToken)
5498 }), $rootScope.$on("Local/GlideraTx", function(event, accessToken, permissions) {
5499 self.updateGlidera()
5500 }), $rootScope.$on("Local/CoinbaseTx", function(event) {
5501 self.updateCoinbase({
5502 updateAccount: !0
5503 })
5504 }), $rootScope.$on("Local/GlideraError", function(event) {
5505 self.debouncedUpdate()
5506 }), $rootScope.$on("Local/UnitSettingUpdated", function(event) {
5507 self.updateAll({
5508 triggerTxUpdate: !0
5509 })
5510 }), $rootScope.$on("Local/WalletCompleted", function(event, walletId) {
5511 self.isInFocus(walletId) && (self.setFocusedWallet(), go.walletHome())
5512 }), self.debouncedUpdate = function() {
5513 var now = Date.now();
5514 (!self.lastUpdate || now - self.lastUpdate > 36e5) && self.updateAll({
5515 quiet: !0,
5516 triggerTxUpdate: !0
5517 })
5518 }, $rootScope.$on("Local/Resume", function(event) {
5519 $log.debug("### Resume event"), profileService.isDisclaimerAccepted(function(v) {
5520 v || ($log.debug("Disclaimer not accepted, resume to home"), go.path("disclaimer"))
5521 }), self.debouncedUpdate()
5522 }), $rootScope.$on("Local/BackupDone", function(event, walletId) {
5523 self.needsBackup = !1, $log.debug("Backup done"), storageService.setBackupFlag(walletId || self.walletId, function(err) {
5524 $log.debug("Backup stored")
5525 })
5526 }), $rootScope.$on("Local/DeviceError", function(event, err) {
5527 self.showErrorPopup(err, function() {
5528 isCordova && navigator && navigator.app && navigator.app.exitApp()
5529 })
5530 }), $rootScope.$on("Local/WalletImported", function(event, walletId) {
5531 self.needsBackup = !1, storageService.setBackupFlag(walletId, function() {
5532 $log.debug("Backup done stored"), addressService.expireAddress(walletId, function(err) {
5533 $timeout(function() {
5534 self.txHistory = self.completeHistory = self.txHistorySearchResults = [], storageService.removeTxHistory(walletId, function() {
5535 self.startScan(walletId)
5536 })
5537 }, 500)
5538 })
5539 })
5540 }), $rootScope.$on("NewIncomingTx", function() {
5541 self.newTx = !0, self.updateAll({
5542 walletStatus: null,
5543 untilItChanges: !0,
5544 triggerTxUpdate: !0
5545 })
5546 }), $rootScope.$on("NewBlock", function() {
5547 self.glideraEnabled && $timeout(function() {
5548 self.updateGlidera()
5549 }), self.coinbaseEnabled && $timeout(function() {
5550 self.updateCoinbase()
5551 }), self.pendingAmount ? self.updateAll({
5552 walletStatus: null,
5553 untilItChanges: null,
5554 triggerTxUpdate: !0
5555 }) : self.hasUnsafeConfirmed && ($log.debug("Wallet has transactions with few confirmations. Updating."), "testnet" == self.network ? self.throttledUpdateHistory() : self.debounceUpdateHistory())
5556 }), $rootScope.$on("BalanceUpdated", function(e, n) {
5557 self.setBalance(n.data)
5558 }), lodash.each(["NewOutgoingTx", "NewOutgoingTxByThirdParty"], function(eventName) {
5559 $rootScope.$on(eventName, function(event) {
5560 self.newTx = !0, self.updateAll({
5561 walletStatus: null,
5562 untilItChanges: !0,
5563 triggerTxUpdate: !0
5564 })
5565 })
5566 }), lodash.each(["NewTxProposal", "TxProposalFinallyRejected", "TxProposalRemoved", "NewOutgoingTxByThirdParty", "Local/GlideraTx"], function(eventName) {
5567 $rootScope.$on(eventName, function(event) {
5568 self.updateAll({
5569 walletStatus: null,
5570 untilItChanges: null,
5571 triggerTxUpdate: !0
5572 })
5573 })
5574 }), $rootScope.$on("Local/TxProposalAction", function(event, untilItChanges) {
5575 self.newTx = untilItChanges, self.updateAll({
5576 walletStatus: null,
5577 untilItChanges: untilItChanges,
5578 triggerTxUpdate: !0
5579 })
5580 }), $rootScope.$on("ScanFinished", function() {
5581 $log.debug("Scan Finished. Updating history"), storageService.removeTxHistory(self.walletId, function() {
5582 self.updateAll({
5583 walletStatus: null,
5584 triggerTxUpdate: !0
5585 })
5586 })
5587 }), lodash.each(["TxProposalRejectedBy", "TxProposalAcceptedBy"], function(eventName) {
5588 $rootScope.$on(eventName, function() {
5589 var f = function() {
5590 if (self.updating) return $timeout(f, 200);
5591 self.updatePendingTxps()
5592 };
5593 f()
5594 })
5595 }), $rootScope.$on("Local/NoWallets", function(event) {
5596 $timeout(function() {
5597 self.hasProfile = !0, self.noFocusedWallet = !0, self.isComplete = null, self.walletName = null, uxLanguage.update()
5598 })
5599 }), $rootScope.$on("Local/NewFocusedWallet", function() {
5600 uxLanguage.update(), self.setFocusedWallet(), self.updateHistory(), storageService.getCleanAndScanAddresses(function(err, walletId) {
5601 walletId && profileService.walletClients[walletId] ? ($log.debug("Clear last address cache and Scan ", walletId), addressService.expireAddress(walletId, function(err) {
5602 self.startScan(walletId)
5603 }), storageService.removeCleanAndScanAddresses(function() {
5604 $rootScope.$emit("Local/NewFocusedWalletReady")
5605 })) : $rootScope.$emit("Local/NewFocusedWalletReady")
5606 })
5607 }), $rootScope.$on("Local/SetTab", function(event, tab, reset) {
5608 self.setTab(tab, reset)
5609 }), $rootScope.$on("disclaimerAccepted", function(event) {
5610 $scope.isDisclaimerAccepted = !0
5611 }), $rootScope.$on("Local/WindowResize", function() {
5612 self.physicalScreenWidth = window.innerWidth > 0 ? window.innerWidth : screen.width
5613 }), $rootScope.$on("Local/NeedsConfirmation", function(event, txp, cb) {
5614 ! function(txp, cb) {
5615 var config = configService.getSync();
5616 $scope.color = config.colorFor[txp.walletId] || "#4A90E2", $scope.tx = txFormatService.processTx(txp), self.confirmationPopup = $ionicPopup.show({
5617 templateUrl: "views/includes/confirm-tx.html",
5618 scope: $scope
5619 }), $scope.processFee = function(amount, fee) {
5620 var walletSettings = configService.getSync().wallet.settings,
5621 feeAlternativeIsoCode = walletSettings.alternativeIsoCode;
5622 $scope.feeLevel = feeService.feeOpts[feeService.getCurrentFeeLevel()], $scope.feeAlternativeStr = parseFloat(rateService.toFiat(fee, feeAlternativeIsoCode).toFixed(2), 10) + " " + feeAlternativeIsoCode, $scope.feeRateStr = (fee / (amount + fee) * 100).toFixed(2) + "%"
5623 }, $scope.cancel = function() {
5624 return cb()
5625 }, $scope.accept = function() {
5626 return cb(!0)
5627 }
5628 }(txp, function(accept) {
5629 return self.confirmationPopup.close(), cb(accept)
5630 })
5631 }), $rootScope.$on("Local/NeedsPassword", function(event, isSetup, cb) {
5632 ! function(isSetup, cb) {
5633 $scope.data = {}, $scope.data.password = null, $scope.isSetup = isSetup, $scope.isVerification = !1, $scope.loading = !1;
5634 var pass = null;
5635 self.passwordPopup = $ionicPopup.show({
5636 templateUrl: "views/includes/password.html",
5637 scope: $scope
5638 }), $scope.cancel = function() {
5639 return cb("No spending password given")
5640 }, $scope.keyPress = function(event) {
5641 $scope.data.password && !$scope.loading && 13 == event.keyCode && $scope.set()
5642 }, $scope.set = function() {
5643 $scope.loading = !0, $scope.error = null, $timeout(function() {
5644 return isSetup && !$scope.isVerification ? ($scope.loading = !1, $scope.isVerification = !0, pass = $scope.data.password, void($scope.data.password = null)) : isSetup && pass != $scope.data.password ? ($scope.loading = !1, $scope.error = gettext("Spending Passwords do not match"), $scope.isVerification = !1, $scope.data.password = null, void(pass = null)) : cb(null, $scope.data.password)
5645 }, 100)
5646 }
5647 }(isSetup, function(err, pass) {
5648 return self.passwordPopup.close(), cb(err, pass)
5649 })
5650 }), $rootScope.$on("Local/EmailUpdated", function(event, email) {
5651 self.preferences.email = email
5652 }), lodash.each(["NewCopayer", "CopayerUpdated"], function(eventName) {
5653 $rootScope.$on(eventName, function() {
5654 self.setFocusedWallet()
5655 })
5656 }), $rootScope.$on("Local/NewEncryptionSetting", function() {
5657 var fc = profileService.focusedClient;
5658 self.isPrivKeyEncrypted = fc.isPrivKeyEncrypted(), $timeout(function() {
5659 $rootScope.$apply()
5660 })
5661 }), lodash.assign(self, vanillaScope), openURLService.init()
5662 }), angular.module("copayApp.controllers").controller("joinController", function($scope, $rootScope, $timeout, go, notification, profileService, configService, storageService, applicationService, gettext, lodash, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess) {
5663 var self = (platformInfo.isChromeApp, platformInfo.isDevel, this),
5664 defaults = configService.getDefaults();
5665 $scope.bwsurl = defaults.bws.url, $scope.derivationPath = derivationPathHelper.default, $scope.account = 1, this.onQrCodeScanned = function(data) {
5666 $scope.secret = data, $scope.joinForm.secret.$setViewValue(data), $scope.joinForm.secret.$render()
5667 };
5668 this.setSeedSource = function() {
5669 self.seedSourceId = $scope.seedSource.id, $timeout(function() {
5670 $rootScope.$apply()
5671 })
5672 }, this.join = function(form) {
5673 if (form && form.$invalid) return void(self.error = gettext("Please enter the required fields"));
5674 var opts = {
5675 secret: form.secret.$modelValue,
5676 myName: form.myName.$modelValue,
5677 bwsurl: $scope.bwsurl
5678 },
5679 setSeed = "set" == self.seedSourceId;
5680 if (setSeed) {
5681 var words = form.privateKey.$modelValue; - 1 == words.indexOf(" ") && 1 == words.indexOf("prv") && words.length > 108 ? opts.extendedPrivateKey = words : opts.mnemonic = words, opts.passphrase = form.passphrase.$modelValue;
5682 var pathData = derivationPathHelper.parse($scope.derivationPath);
5683 if (!pathData) return void(this.error = gettext("Invalid derivation path"));
5684 opts.account = pathData.account, opts.networkName = pathData.networkName, opts.derivationStrategy = pathData.derivationStrategy
5685 } else opts.passphrase = form.createPassphrase.$modelValue;
5686 if (opts.walletPrivKey = $scope._walletPrivKey, setSeed && !opts.mnemonic && !opts.extendedPrivateKey) return void(this.error = gettext("Please enter the wallet recovery phrase"));
5687 if ("ledger" == self.seedSourceId || "trezor" == self.seedSourceId) {
5688 var account = $scope.account;
5689 if (!account || account < 1) return void(this.error = gettext("Invalid account number"));
5690 "trezor" == self.seedSourceId && (account -= 1), opts.account = account, ongoingProcess.set("connecting" + self.seedSourceId, !0);
5691 ("ledger" == self.seedSourceId ? ledger : trezor).getInfoForNewWallet(!0, account, function(err, lopts) {
5692 if (ongoingProcess.set("connecting" + self.seedSourceId, !1), err) return self.error = err, void $scope.$apply();
5693 opts = lodash.assign(lopts, opts), self._join(opts)
5694 })
5695 } else self._join(opts)
5696 }, this._join = function(opts) {
5697 ongoingProcess.set("joiningWallet", !0), $timeout(function() {
5698 profileService.joinWallet(opts, function(err) {
5699 if (ongoingProcess.set("joiningWallet", !1), err) return self.error = err, void $rootScope.$apply();
5700 go.walletHome()
5701 })
5702 }, 100)
5703 },
5704 function() {
5705 self.seedOptions = [{
5706 id: "new",
5707 label: gettext("Random")
5708 }, {
5709 id: "set",
5710 label: gettext("Specify Recovery Phrase...")
5711 }], $scope.seedSource = self.seedOptions[0]
5712 }(), self.setSeedSource()
5713 }), angular.module("copayApp.controllers").controller("addressbookController", function($rootScope, $scope, $timeout, lodash, profileService, addressService, addressbookService, bwcError) {
5714 var self = $scope.self,
5715 fc = profileService.focusedClient;
5716 self.lockAddress = !1, self._address = null, $scope.editAddressbook = !1, $scope.addAddressbookEntry = !1, $scope.selectedAddressbook = {}, $scope.newAddress = address, $scope.walletName = fc.credentials.walletName, $scope.color = fc.backgroundColor, $scope.addressbook = {
5717 address: $scope.newAddress || "",
5718 label: ""
5719 }, $scope.checkClipboard = function() {
5720 $scope.newAddress || getClipboard(function(value) {
5721 $scope.newAddress = value
5722 })
5723 }, $scope.beforeQrCodeScann = function() {
5724 $scope.error = null, $scope.addAddressbookEntry = !0, $scope.editAddressbook = !1
5725 }, $scope.onQrCodeScanned = function(data, addressbookForm) {
5726 $timeout(function() {
5727 var form = addressbookForm;
5728 data && form && (data = data.replace("neblio:", ""), form.address.$setViewValue(data), form.address.$isValid = !0, form.address.$render()), $scope.$digest()
5729 }, 100)
5730 }, $scope.toggleEditAddressbook = function() {
5731 $scope.editAddressbook = !$scope.editAddressbook, $scope.selectedAddressbook = {}, $scope.addAddressbookEntry = !1
5732 }, $scope.selectAddressbook = function(addr) {
5733 self.setForm(addr), $scope.cancel()
5734 }, $scope.toggleSelectAddressbook = function(addr) {
5735 $scope.selectedAddressbook[addr] = !$scope.selectedAddressbook[addr]
5736 }, $scope.toggleAddAddressbookEntry = function() {
5737 $scope.error = null, $scope.addressbook = {
5738 address: "",
5739 label: ""
5740 }, $scope.addAddressbookEntry = !$scope.addAddressbookEntry
5741 }, $scope.contactList = function() {
5742 $scope.error = null, addressbookService.list(function(err, ab) {
5743 if (err) return void($scope.error = err);
5744 $scope.list = ab, $scope.isEmptyList = lodash.isEmpty($scope.list), $timeout(function() {
5745 $scope.$digest()
5746 })
5747 })
5748 }, $scope.setSelectedWalletsOpt = function(val) {
5749 $scope.selectedWalletsOpt = val
5750 }, $scope.add = function(addressbook) {
5751 $scope.error = null, $timeout(function() {
5752 addressbookService.add(addressbook, function(err, ab) {
5753 if (err) return void($scope.error = err);
5754 $rootScope.$emit("Local/AddressbookUpdated", ab), $scope.list = ab, $scope.isEmptyList = lodash.isEmpty($scope.list), $scope.editAddressbook = !0, $scope.toggleEditAddressbook(), $scope.$digest()
5755 })
5756 }, 100)
5757 }, $scope.remove = function(addr) {
5758 $scope.error = null, $timeout(function() {
5759 addressbookService.remove(addr, function(err, ab) {
5760 if (err) return void($scope.error = err);
5761 $rootScope.$emit("Local/AddressbookUpdated", ab), $scope.list = ab, $scope.isEmptyList = lodash.isEmpty($scope.list), $scope.isEmptyList && ($scope.editAddressbook = !1), $scope.$digest()
5762 })
5763 }, 100)
5764 }, $scope.selectWallet = function(walletId, walletName) {
5765 var client = profileService.getClient(walletId);
5766 $scope.errorSelectedWallet = {}, profileService.isReady(client, function(err) {
5767 err ? $scope.errorSelectedWallet[walletId] = bwcError.msg(err) : ($scope.gettingAddress = !0, $scope.selectedWalletName = walletName, addressService.getAddress(walletId, !1, function(err, addr) {
5768 if ($scope.gettingAddress = !1, err) return self.error = err, void $scope.cancelAddress();
5769 self.setForm(addr), $scope.cancel()
5770 })), $timeout(function() {
5771 $scope.$apply()
5772 })
5773 })
5774 }, $scope.cancelAddress = function() {
5775 self.resetForm(), $scope.cancel()
5776 }, $scope.cancel = function() {
5777 $scope.addressbookModal.hide()
5778 }
5779 }), angular.module("copayApp.controllers").controller("amazonCardDetailsController", function($scope, $log, $timeout, bwcError, amazonService, lodash, ongoingProcess) {
5780 $scope.cancelGiftCard = function() {
5781 ongoingProcess.set("Canceling gift card...", !0), amazonService.cancelGiftCard($scope.card, function(err, data) {
5782 if (ongoingProcess.set("Canceling gift card...", !1), err) return void($scope.error = bwcError.msg(err));
5783 $scope.card.cardStatus = data.cardStatus, amazonService.savePendingGiftCard($scope.card, null, function(err) {
5784 $scope.$emit("UpdateAmazonList")
5785 })
5786 })
5787 }, $scope.remove = function() {
5788 amazonService.savePendingGiftCard($scope.card, {
5789 remove: !0
5790 }, function(err) {
5791 $scope.$emit("UpdateAmazonList"), $scope.cancel()
5792 })
5793 }, $scope.refreshGiftCard = function() {
5794 amazonService.getPendingGiftCards(function(err, gcds) {
5795 if (err) return void(self.error = err);
5796 lodash.forEach(gcds, function(dataFromStorage) {
5797 "PENDING" == dataFromStorage.status && dataFromStorage.invoiceId == $scope.card.invoiceId && ($log.debug("creating gift card"), amazonService.createGiftCard(dataFromStorage, function(err, giftCard) {
5798 if (err) return self.error = bwcError.msg(err), void $log.debug(bwcError.msg(err));
5799 if (lodash.isEmpty(giftCard)) $log.debug("pending gift card not available yet");
5800 else {
5801 var newData = {};
5802 lodash.merge(newData, dataFromStorage, giftCard), amazonService.savePendingGiftCard(newData, null, function(err) {
5803 $log.debug("Saving new gift card"), $scope.card = newData, $scope.$emit("UpdateAmazonList"), $timeout(function() {
5804 $scope.$digest()
5805 })
5806 })
5807 }
5808 }))
5809 })
5810 })
5811 }, $scope.cancel = function() {
5812 $scope.amazonCardDetailsModal.hide()
5813 }
5814 }), angular.module("copayApp.controllers").controller("coinbaseConfirmationController", function($scope, $timeout, coinbaseService, applicationService) {
5815 $scope.ok = function() {
5816 coinbaseService.logout($scope.network, function() {
5817 $timeout(function() {
5818 applicationService.restart()
5819 }, 1e3)
5820 }), $scope.cancel()
5821 }, $scope.cancel = function() {
5822 $scope.coinbaseConfirmationModal.hide()
5823 }
5824 }), angular.module("copayApp.controllers").controller("coinbaseTxDetailsController", function($scope, $rootScope, coinbaseService) {
5825 $scope.remove = function() {
5826 coinbaseService.savePendingTransaction($scope.tx, {
5827 remove: !0
5828 }, function(err) {
5829 $rootScope.$emit("Local/CoinbaseTx"), $scope.cancel()
5830 })
5831 }, $scope.cancel = function() {
5832 $scope.coinbaseTxDetailsModal.hide()
5833 }
5834 }), angular.module("copayApp.controllers").controller("confirmationController", function($scope) {
5835 $scope.ok = function() {
5836 $scope.loading = !0, $scope.okAction(), $scope.confirmationModal.hide()
5837 }, $scope.cancel = function() {
5838 $scope.confirmationModal.hide()
5839 }
5840 }), angular.module("copayApp.controllers").controller("customAmountController", function($scope, $timeout, $filter, platformInfo, rateService) {
5841 var self = $scope.self;
5842 $scope.unitName = self.unitName, $scope.alternativeAmount = self.alternativeAmount, $scope.alternativeName = self.alternativeName, $scope.alternativeIsoCode = self.alternativeIsoCode, $scope.isRateAvailable = self.isRateAvailable, $scope.unitToSatoshi = self.unitToSatoshi, $scope.unitDecimals = self.unitDecimals;
5843 var satToUnit = 1 / self.unitToSatoshi;
5844 $scope.showAlternative = !1, $scope.isCordova = platformInfo.isCordova, Object.defineProperty($scope, "_customAlternative", {
5845 get: function() {
5846 return $scope.customAlternative
5847 },
5848 set: function(newValue) {
5849 $scope.customAlternative = newValue, "number" == typeof newValue && $scope.isRateAvailable ? $scope.customAmount = parseFloat((rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed($scope.unitDecimals), 10) : $scope.customAmount = null
5850 },
5851 enumerable: !0,
5852 configurable: !0
5853 }), Object.defineProperty($scope, "_customAmount", {
5854 get: function() {
5855 return $scope.customAmount
5856 },
5857 set: function(newValue) {
5858 $scope.customAmount = newValue, "number" == typeof newValue && $scope.isRateAvailable ? $scope.customAlternative = parseFloat(rateService.toFiat(newValue * $scope.unitToSatoshi, $scope.alternativeIsoCode).toFixed(2), 10) : $scope.customAlternative = null, $scope.alternativeAmount = $scope.customAlternative
5859 },
5860 enumerable: !0,
5861 configurable: !0
5862 }), $scope.submitForm = function(form) {
5863 var amount = form.amount.$modelValue,
5864 amountSat = parseInt((amount * $scope.unitToSatoshi).toFixed(0));
5865 $timeout(function() {
5866 $scope.customizedAmountUnit = amount + " " + $scope.unitName, $scope.customizedAlternativeUnit = $filter("formatFiatAmount")(form.alternative.$modelValue) + " " + $scope.alternativeIsoCode, "bits" == $scope.unitName && (amount = (1e-8 * amountSat).toFixed(8)), $scope.customizedAmountBtc = amount
5867 }, 1)
5868 }, $scope.toggleAlternative = function() {
5869 $scope.showAlternative = !1
5870 }, $scope.shareAddress = function(uri) {
5871 platformInfo.isCordova && window.plugins.socialsharing.share(uri, null, null, null)
5872 }, $scope.cancel = function() {
5873 $scope.customAmountModal.hide()
5874 }
5875 }), angular.module("copayApp.controllers").controller("glideraConfirmationController", function($scope, $timeout, storageService, applicationService) {
5876 $scope.ok = function() {
5877 storageService.removeGlideraToken($scope.network, function() {
5878 $timeout(function() {
5879 applicationService.restart()
5880 }, 100)
5881 }), $scope.cancel()
5882 }, $scope.cancel = function() {
5883 $scope.glideraConfirmationModal.hide()
5884 }
5885 }), angular.module("copayApp.controllers").controller("glideraTxDetailsController", function($scope) {
5886 $scope.cancel = function() {
5887 $scope.glideraTxDetailsModal.hide()
5888 }
5889 }), angular.module("copayApp.controllers").controller("inputAmountController", function($rootScope, $scope, $filter, $timeout, $ionicScrollDelegate, profileService, platformInfo, lodash, configService, go, rateService) {
5890 function checkFontSize() {
5891 $scope.amount && $scope.amount.length >= SMALL_FONT_SIZE_LIMIT ? $scope.smallFont = !0 : $scope.smallFont = !1
5892 }
5893
5894 function isOperator(val) {
5895 return /[\/\-\+\x\*]/.test(val)
5896 }
5897
5898 function isExpression(val) {
5899 return /^\.?\d+(\.?\d+)?([\/\-\+\*x]\d?\.?\d+)+$/.test(val)
5900 }
5901
5902 function processAmount(val) {
5903 if (!val) return void $scope.resetAmount();
5904 var formatedValue = format(val),
5905 result = evaluate(formatedValue);
5906 lodash.isNumber(result) && ($scope.globalResult = isExpression(val) ? "= " + processResult(result) : "", $scope.amountResult = $filter("formatFiatAmount")(toFiat(result)), $scope.alternativeResult = profileService.formatAmount(fromFiat(result) * unitToSatoshi, !0))
5907 }
5908
5909 function processResult(val) {
5910 return $scope.showAlternativeAmount ? $filter("formatFiatAmount")(val) : profileService.formatAmount(val.toFixed(unitDecimals) * unitToSatoshi, !0)
5911 }
5912
5913 function fromFiat(val) {
5914 return parseFloat((rateService.fromFiat(val, $scope.alternativeIsoCode) * satToUnit).toFixed(unitDecimals), 10)
5915 }
5916
5917 function toFiat(val) {
5918 return parseFloat(rateService.toFiat(val * unitToSatoshi, $scope.alternativeIsoCode).toFixed(2), 10)
5919 }
5920
5921 function evaluate(val) {
5922 var result;
5923 try {
5924 result = $scope.$eval(val)
5925 } catch (e) {
5926 return 0
5927 }
5928 return lodash.isFinite(result) ? result : 0
5929 }
5930
5931 function format(val) {
5932 var result = val.toString();
5933 return isOperator(lodash.last(val)) && (result = result.slice(0, -1)), result.replace("x", "*")
5934 }
5935 var unitToSatoshi, satToUnit, unitDecimals, satToBtc, self = $scope.self,
5936 SMALL_FONT_SIZE_LIMIT = 13;
5937 $scope.init = function() {
5938 var config = configService.getSync().wallet.settings;
5939 $scope.unitName = config.unitName, $scope.alternativeIsoCode = config.alternativeIsoCode, $scope.specificAmount = $scope.specificAlternativeAmount = "", $scope.isCordova = platformInfo.isCordova, unitToSatoshi = config.unitToSatoshi, satToUnit = 1 / unitToSatoshi, satToBtc = 1e-8, unitDecimals = config.unitDecimals, processAmount($scope.amount), $timeout(function() {
5940 $ionicScrollDelegate.resize()
5941 }, 100)
5942 }, $scope.shareAddress = function(uri) {
5943 $scope.isCordova && window.plugins.socialsharing.share(uri, null, null, null)
5944 }, $scope.toggleAlternative = function() {
5945 if ($scope.showAlternativeAmount = !1, $scope.amount && isExpression($scope.amount)) {
5946 var amount = evaluate(format($scope.amount));
5947 $scope.globalResult = "= " + processResult(amount)
5948 }
5949 }, $scope.pushDigit = function(digit) {
5950 $scope.amount && $scope.amount.length >= 19 || ($scope.amount = ($scope.amount + digit).replace("..", "."), checkFontSize(), processAmount($scope.amount))
5951 }, $scope.pushOperator = function(operator) {
5952 $scope.amount && 0 != $scope.amount.length && ($scope.amount = function(val) {
5953 return isOperator(lodash.last(val)) ? val.slice(0, -1) + operator : val + operator
5954 }($scope.amount))
5955 }, $scope.removeDigit = function() {
5956 $scope.amount = $scope.amount.slice(0, -1), processAmount($scope.amount), checkFontSize()
5957 }, $scope.resetAmount = function() {
5958 $scope.amount = $scope.alternativeResult = $scope.amountResult = $scope.globalResult = "", checkFontSize()
5959 }, $scope.finish = function() {
5960 var _amount = evaluate(format($scope.amount)),
5961 amount = _amount.toFixed(unitDecimals),
5962 alternativeAmount = _amount;
5963 if (amount % 1 == 0 && (amount = parseInt(amount)), $scope.addr) {
5964 if ($scope.specificAmount = profileService.formatAmount(amount * unitToSatoshi, !0), $scope.specificAlternativeAmount = $filter("formatFiatAmount")(alternativeAmount), "bits" == $scope.unitName) {
5965 amount = (parseInt((amount * unitToSatoshi).toFixed(0)) * satToBtc).toFixed(8)
5966 }
5967 $scope.customizedAmountBtc = amount, $timeout(function() {
5968 $ionicScrollDelegate.resize()
5969 }, 100)
5970 } else self.setAmount(amount, $scope.showAlternativeAmount), $scope.cancel()
5971 }, $scope.cancel = function() {
5972 $scope.inputAmountModal.hide()
5973 }
5974 }), angular.module("copayApp.controllers").controller("payproController", function($scope) {
5975 var self = $scope.self;
5976 $scope.alternative = self.alternativeAmount, $scope.alternativeIsoCode = self.alternativeIsoCode, $scope.isRateAvailable = self.isRateAvailable, $scope.unitTotal = ($scope.paypro.amount * self.satToUnit).toFixed(self.unitDecimals), $scope.unitName = self.unitName, $scope.cancel = function() {
5977 $scope.payproModal.hide()
5978 }
5979 }), angular.module("copayApp.controllers").controller("scannerController", function($scope, $timeout) {
5980 var video, canvas, $video, context, localMediaStream, prevResult, scanTimer, _scan = function(evt) {
5981 if (localMediaStream) {
5982 context.drawImage(video, 0, 0, 300, 225);
5983 try {
5984 qrcode.decode()
5985 } catch (e) {}
5986 }
5987 scanTimer = $timeout(_scan, 800)
5988 },
5989 _scanStop = function() {
5990 if ($timeout.cancel(scanTimer), localMediaStream && localMediaStream.active)
5991 for (var localMediaStreamTrack = localMediaStream.getTracks(), i = 0; i < localMediaStreamTrack.length; i++) localMediaStreamTrack[i].stop();
5992 else try {
5993 localMediaStream.stop()
5994 } catch (e) {}
5995 localMediaStream = null, video.src = ""
5996 };
5997 qrcode.callback = function(data) {
5998 if (prevResult != data) return void(prevResult = data);
5999 _scanStop(), $scope.cancel(), $scope.onScan({
6000 data: data
6001 })
6002 };
6003 var _successCallback = function(stream) {
6004 "srcObject" in video ? video.srcObject = stream : video.src = window.URL.createObjectURL(stream), localMediaStream = stream, video.onloadedmetadata = function(e) {
6005 video.play()
6006 }, $timeout(_scan, 1e3)
6007 },
6008 _videoError = function(err) {
6009 $scope.cancel()
6010 },
6011 setScanner = function() {
6012 navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia, window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL
6013 };
6014 $scope.init = function() {
6015 setScanner(), $timeout(function() {
6016 $scope.beforeScan && $scope.beforeScan(), canvas = document.getElementById("qr-canvas"), context = canvas.getContext("2d"), video = document.getElementById("qrcode-scanner-video"), $video = angular.element(video), canvas.width = 300, canvas.height = 225, context.clearRect(0, 0, 300, 225);
6017 var isSafari = !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/),
6018 iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
6019 isSafari || iOS ? navigator.mediaDevices.getUserMedia({
6020 video: {
6021 facingMode: "environment"
6022 }
6023 }).then(_successCallback).catch(function(err) {
6024 console.log(err.name + ": " + err.message)
6025 }) : navigator.getUserMedia({
6026 video: {
6027 facingMode: "environment",
6028 width: 300,
6029 height: 225
6030 }
6031 }, _successCallback, _videoError)
6032 }, 500)
6033 }, $scope.cancel = function() {
6034 _scanStop(), $scope.scannerModal.hide(), $scope.scannerModal.remove()
6035 }
6036 }), angular.module("copayApp.controllers").controller("searchController", function($scope) {
6037 $scope.self;
6038 $scope.search = "", $scope.cancel = function() {
6039 $scope.searchModal.hide()
6040 }
6041 }), angular.module("copayApp.controllers").controller("txDetailsController", function($rootScope, $log, $scope, $filter, $ionicPopup, gettextCatalog, profileService, configService, lodash) {
6042 var self = $scope.self,
6043 fc = profileService.focusedClient,
6044 config = configService.getSync(),
6045 configWallet = config.wallet,
6046 walletSettings = configWallet.settings;
6047 $scope.alternativeIsoCode = walletSettings.alternativeIsoCode, $scope.color = fc.backgroundColor, $scope.copayerId = fc.credentials.copayerId, $scope.isShared = fc.credentials.n > 1, $scope.btx.amountStr = profileService.formatAmount($scope.btx.amount, !0) + " " + walletSettings.unitName, $scope.btx.feeStr = profileService.formatAmount($scope.btx.fees, !0) + " " + walletSettings.unitName, $scope.showCommentPopup = function() {
6048 $scope.data = {
6049 comment: $scope.btx.note ? $scope.btx.note.body : ""
6050 };
6051 var commentPopup = $ionicPopup.show({
6052 templateUrl: "views/includes/note.html",
6053 scope: $scope
6054 });
6055 $scope.commentPopupClose = function() {
6056 commentPopup.close()
6057 }, $scope.commentPopupSave = function() {
6058 $log.debug("Saving note");
6059 var args = {
6060 txid: $scope.btx.txid
6061 };
6062 lodash.isEmpty($scope.data.comment) || (args.body = $scope.data.comment), fc.editTxNote(args, function(err) {
6063 if (err) return void $log.debug("Could not save tx comment");
6064 $scope.btx.note = null, args.body && ($scope.btx.note = {}, $scope.btx.note.body = $scope.data.comment, $scope.btx.note.editedByName = fc.credentials.copayerName, $scope.btx.note.editedOn = Math.floor(Date.now() / 1e3)), $scope.btx.searcheableString = null, commentPopup.close()
6065 })
6066 }
6067 }, $scope.getAlternativeAmount = function() {
6068 fc.getFiatRate({
6069 code: $scope.alternativeIsoCode,
6070 ts: 1e3 * $scope.btx.time
6071 }, function(err, res) {
6072 if (err) return void $log.debug("Could not get historic rate");
6073 if (res && res.rate) {
6074 var alternativeAmountBtc = (1e-8 * $scope.btx.amount).toFixed(8);
6075 $scope.rateDate = res.fetchedOn, $scope.rateStr = res.rate + " " + $scope.alternativeIsoCode, $scope.alternativeAmountStr = $filter("formatFiatAmount")(alternativeAmountBtc * res.rate) + " " + $scope.alternativeIsoCode, $scope.$apply()
6076 }
6077 })
6078 }, $scope.getShortNetworkName = function() {
6079 return fc.credentials.network.substring(0, 4)
6080 }, $scope.copyToClipboard = function(addr, $event) {
6081 addr && self.copyToClipboard(addr, $event)
6082 }, $scope.cancel = function() {
6083 $scope.txDetailsModal.hide()
6084 }
6085 }), angular.module("copayApp.controllers").controller("txpDetailsController", function($scope, $rootScope, $timeout, $interval, $ionicModal, ongoingProcess, platformInfo, txStatus, $ionicScrollDelegate, txFormatService, fingerprintService, bwcError, gettextCatalog, lodash, profileService, walletService) {
6086 function setError(err, prefix) {
6087 $scope.loading = !1, $scope.error = bwcError.msg(err, prefix), $timeout(function() {
6088 $scope.$digest()
6089 }, 10)
6090 }
6091
6092 function paymentTimeControl(expirationTime) {
6093 function setExpirationTime() {
6094 var now = Math.floor(Date.now() / 1e3);
6095 if (now > expirationTime) return $scope.paymentExpired = !0, void(self.countDown && $interval.cancel(self.countDown));
6096 var totalSecs = expirationTime - now,
6097 m = Math.floor(totalSecs / 60),
6098 s = totalSecs % 60;
6099 $scope.expires = ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2)
6100 }
6101 $scope.paymentExpired = !1, setExpirationTime(), self.countDown = $interval(function() {
6102 setExpirationTime()
6103 }, 1e3)
6104 }
6105
6106 function handleEncryptedWallet(cb) {
6107 if (!walletService.isEncrypted(fc)) return cb();
6108 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
6109 return cb(err ? err : walletService.unlock(fc, password))
6110 })
6111 }
6112 var self = $scope.self,
6113 tx = $scope.tx,
6114 isGlidera = ($scope.copayers, $scope.isGlidera),
6115 fc = (Math.floor(Date.now() / 1e3), profileService.focusedClient);
6116 $scope.loading = null, $scope.copayerId = fc.credentials.copayerId, $scope.isShared = fc.credentials.n > 1, $scope.canSign = fc.canSign() || fc.isPrivKeyExternal(), $scope.color = fc.backgroundColor,
6117 function() {
6118 tx.payProUrl && !platformInfo.isChromeApp && fc.fetchPayPro({
6119 payProUrl: tx.payProUrl
6120 }, function(err, paypro) {
6121 err || (tx.paypro = paypro, paymentTimeControl(tx.paypro.expires), $timeout(function() {
6122 $ionicScrollDelegate.resize()
6123 }, 100))
6124 })
6125 }(), "Glidera transaction" === tx.message && isGlidera && (tx.isGlidera = !0, tx.canBeRemoved && (tx.canBeRemoved = Date.now() / 1e3 - (tx.ts || tx.createdOn) > GLIDERA_LOCK_TIME)), $scope.sign = function(txp) {
6126 $scope.error = null, $scope.loading = !0, $timeout(function() {
6127 fingerprintService.check(fc, function(err) {
6128 if (err) return $scope.error = gettextCatalog.getString("Could not send payment"), $scope.loading = !1, void $timeout(function() {
6129 $scope.$digest()
6130 }, 1);
6131 handleEncryptedWallet(function(err) {
6132 if (err) return setError(err);
6133 ongoingProcess.set("signingTx", !0), walletService.signTx(fc, txp, function(err, signedTxp) {
6134 if (ongoingProcess.set("signingTx", !1), walletService.lock(fc), err) return setError(err);
6135 "accepted" == signedTxp.status ? (ongoingProcess.set("broadcastingTx", !0), walletService.broadcastTx(fc, signedTxp, function(err, broadcastedTxp) {
6136 if (ongoingProcess.set("broadcastingTx", !1), $scope.$emit("UpdateTx"), $scope.close(broadcastedTxp), err) return setError(err)
6137 })) : ($scope.$emit("UpdateTx"), $scope.close(signedTxp))
6138 })
6139 })
6140 })
6141 }, 10)
6142 }, $scope.reject = function(txp) {
6143 $scope.loading = !0, $scope.error = null, $timeout(function() {
6144 ongoingProcess.set("rejectTx", !0), walletService.rejectTx(fc, txp, function(err, txpr) {
6145 if (ongoingProcess.set("rejectTx", !1), err) return $scope.$emit("UpdateTx"), setError(err, gettextCatalog.getString("Could not reject payment"));
6146 $scope.close(txpr)
6147 })
6148 }, 10)
6149 }, $scope.remove = function(txp) {
6150 $scope.loading = !0, $scope.error = null, $timeout(function() {
6151 ongoingProcess.set("removeTx", !0), walletService.removeTx(fc, txp, function(err) {
6152 if (ongoingProcess.set("removeTx", !1), err && (!err.message || !err.message.match(/Unexpected/))) return $scope.$emit("UpdateTx"), setError(err, gettextCatalog.getString("Could not delete payment proposal"));
6153 $scope.close()
6154 })
6155 }, 10)
6156 }, $scope.broadcast = function(txp) {
6157 $scope.loading = !0, $scope.error = null, $timeout(function() {
6158 ongoingProcess.set("broadcastTx", !0), walletService.broadcastTx(fc, txp, function(err, txpb) {
6159 if (ongoingProcess.set("broadcastTx", !1), err) return setError(err, gettextCatalog.getString("Could not broadcast payment"));
6160 $scope.close(txpb)
6161 })
6162 }, 10)
6163 }, $scope.getShortNetworkName = function() {
6164 return fc.credentials.networkName.substring(0, 4)
6165 }, lodash.each(["TxProposalRejectedBy", "TxProposalAcceptedBy", "transactionProposalRemoved", "TxProposalRemoved", "NewOutgoingTx", "UpdateTx"], function(eventName) {
6166 $rootScope.$on(eventName, function() {
6167 fc.getTx($scope.tx.id, function(err, tx) {
6168 if (err) return void(!err.message || "TX_NOT_FOUND" != err.message || "transactionProposalRemoved" != eventName && "TxProposalRemoved" != eventName || ($scope.tx.removed = !0, $scope.tx.canBeRemoved = !1, $scope.tx.pendingForUs = !1, $scope.$apply()));
6169 var action = lodash.find(tx.actions, {
6170 copayerId: fc.credentials.copayerId
6171 });
6172 $scope.tx = txFormatService.processTx(tx), action || "pending" != tx.status || ($scope.tx.pendingForUs = !0), $scope.updateCopayerList(), $scope.$apply()
6173 })
6174 })
6175 }), $scope.updateCopayerList = function() {
6176 lodash.map($scope.copayers, function(cp) {
6177 lodash.each($scope.tx.actions, function(ac) {
6178 cp.id == ac.copayerId && (cp.action = ac.type)
6179 })
6180 })
6181 }, $scope.copyToClipboard = function(addr, $event) {
6182 addr && self.copyToClipboard(addr, $event)
6183 }, $scope.close = function(txp) {
6184 if ($scope.loading = null, txp) {
6185 var type = txStatus.notify(txp);
6186 $scope.openStatusModal(type, txp, function() {
6187 $scope.$emit("Local/TxProposalAction", "broadcasted" == txp.status)
6188 })
6189 } else $timeout(function() {
6190 $scope.$emit("Local/TxProposalAction")
6191 }, 100);
6192 $scope.cancel()
6193 }, $scope.openStatusModal = function(type, txp, cb) {
6194 $scope.type = type, $scope.tx = txFormatService.processTx(txp), $scope.cb = cb;
6195 var txStatusUrl = "views/modals/tx-status.html";
6196 tx.customData && tx.customData.token && (txStatusUrl = "transfer" == tx.customData.token.action ? "views/orion/modals/transfer-status.html" : "views/orion/modals/issue-status.html"), $ionicModal.fromTemplateUrl(txStatusUrl, {
6197 scope: $scope,
6198 animation: "slide-in-up"
6199 }).then(function(modal) {
6200 $scope.txStatusModal = modal, $scope.txStatusModal.show()
6201 })
6202 }, $scope.cancel = function() {
6203 $scope.txpDetailsModal.hide()
6204 }
6205 }), angular.module("copayApp.controllers").controller("txStatusController", function($scope, $timeout) {
6206 $scope.cb && $timeout($scope.cb, 100), $scope.cancel = function() {
6207 $scope.txStatusModal.hide()
6208 }
6209 }), angular.module("copayApp.controllers").controller("walletsController", function($scope, $timeout, bwcError, profileService) {
6210 $scope.selectWallet = function(walletId) {
6211 var client = profileService.getClient(walletId);
6212 $scope.errorSelectedWallet = {}, profileService.isReady(client, function(err) {
6213 if (err) return $scope.errorSelectedWallet[walletId] = bwcError.msg(err), void $timeout(function() {
6214 $scope.$apply()
6215 });
6216 $scope.$emit("walletSelected", walletId)
6217 })
6218 }, $scope.cancel = function() {
6219 $scope.walletsModal.hide()
6220 }
6221 }), angular.module("copayApp.controllers").controller("paperWalletController", function($scope, $timeout, $log, $ionicModal, configService, profileService, go, addressService, txStatus, bitcore, ongoingProcess, orion, lodash, walletService) {
6222 function _scanFunds(cb) {
6223 function getBalance(privateKey, cb) {
6224 fc.getBalanceFromPrivateKey(privateKey, cb)
6225 }
6226
6227 function checkPrivateKey(privateKey) {
6228 try {
6229 new bitcore.PrivateKey(privateKey, "livenet")
6230 } catch (err) {
6231 return !1
6232 }
6233 return !0
6234 }! function(scannedKey, isPkEncrypted, passphrase, cb) {
6235 if (!isPkEncrypted) return cb(null, scannedKey);
6236 fc.decryptBIP38PrivateKey(scannedKey, passphrase, null, cb)
6237 }($scope.scannedKey, $scope.isPkEncrypted, $scope.passphrase, function(err, privateKey) {
6238 if (err) return cb(err);
6239 if (!checkPrivateKey(privateKey)) return cb(new Error("Invalid private key"));
6240 var nebl_balance;
6241 getBalance(privateKey, function(err, balance) {
6242 return err ? cb(err) : (nebl_balance = balance, cb(null, privateKey, balance))
6243 });
6244 var pk = new bitcore.PrivateKey(privateKey),
6245 address = pk.toAddress();
6246 orion.getTokensByAddress(address, function(err, array) {
6247 if (err) return cb(err);
6248 if (tokens = array, tokens.length < 1) return cb(new Error("No NTP1 tokens found at this private key"));
6249 var nebl_needed = 1e4 * (2 * tokens.length + 1);
6250 if (nebl_needed > nebl_balance) return privateKey = "", cb(new Error("Not enough NEBL at private key to sweep all tokens. Available Balance: " + nebl_balance + " satoshis. Needed: " + nebl_needed + " satoshis"));
6251 $log.debug("Tokens for " + address + ": \n" + JSON.stringify(tokens))
6252 })
6253 })
6254 }
6255
6256 function _sweepWallet(cb) {
6257 addressService.getAddress(fc.credentials.walletId, !0, function(err, destinationAddress) {
6258 if (err) return cb(err);
6259 for (var i = 0, len = tokens.length; i < len; i++) {
6260 var token = tokens[i],
6261 amount = tokens[i].token.amount,
6262 pk = new bitcore.PrivateKey($scope.privateKey),
6263 fromAddress = pk.toAddress();
6264 orion.createTransferTx(fromAddress, token, amount, destinationAddress, function(err, result) {
6265 if (err) return cb(err);
6266 var tx = new bitcore.Transaction(result.txHex),
6267 inputs = lodash.map(tx.inputs, function(input) {
6268 return input = input.toObject(), input = orion.txidToUTXO[input.prevTxId + ":" + input.outputIndex], input.outputIndex = input.vout, input
6269 }),
6270 tx2 = new bitcore.Transaction;
6271 tx2.from(inputs);
6272 for (var j = 0, len = tx.outputs.length; j < len; j++) tx2.addOutput(tx.outputs[j]);
6273 tx2.sign(pk), fc.broadcastRawTx({
6274 rawTx: tx2.serialize(),
6275 network: "livenet"
6276 }, function(err, txid) {
6277 return err ? cb(err) : cb(null, destinationAddress, txid)
6278 })
6279 })
6280 }
6281 })
6282 }
6283 var fc = profileService.focusedClient,
6284 tokens = [];
6285 $scope.onQrCodeScanned = function(data) {
6286 $scope.inputData = data, $scope.onData(data)
6287 }, $scope.onData = function(data) {
6288 $scope.error = null, $scope.scannedKey = data,
6289 $scope.isPkEncrypted = "6P" == data.substring(0, 2)
6290 }, $scope.scanFunds = function() {
6291 $scope.privateKey = "", $scope.balanceSat = 0, $scope.error = null, ongoingProcess.set("scanning", !0), $timeout(function() {
6292 _scanFunds(function(err, privateKey, balance) {
6293 if (ongoingProcess.set("scanning", !1), err) $log.error(err), $scope.error = err.message || err.toString();
6294 else {
6295 $scope.privateKey = privateKey, $scope.balanceSat = balance;
6296 configService.getSync().wallet.settings;
6297 $scope.balance = tokens.length
6298 }
6299 $scope.$apply()
6300 })
6301 }, 100)
6302 }, $scope.sweepWallet = function() {
6303 ongoingProcess.set("sweepingWallet", !0), $scope.sending = !0, $scope.error = null, $timeout(function() {
6304 _sweepWallet(function(err, destinationAddress, txid) {
6305 if (ongoingProcess.set("sweepingWallet", !1), err) $scope.error = err.message || err.toString(), $log.error(err);
6306 else {
6307 var type = txStatus.notify(txp);
6308 $scope.openStatusModal(type, txp, function() {
6309 go.walletHome()
6310 })
6311 }
6312 $scope.$apply()
6313 })
6314 }, 100)
6315 }, $scope.openStatusModal = function(type, txp, cb) {
6316 $scope.type = type, $scope.tx = txFormatService.processTx(txp), $scope.color = fc.backgroundColor, $scope.cb = cb, $ionicModal.fromTemplateUrl("views/modals/tx-status.html", {
6317 scope: $scope,
6318 animation: "slide-in-up"
6319 }).then(function(modal) {
6320 $scope.txStatusModal = modal, $scope.txStatusModal.show()
6321 })
6322 }
6323 }), angular.module("copayApp.controllers").controller("paymentUriController", function($rootScope, $scope, $stateParams, $location, $timeout, profileService, configService, lodash, bitcore, go) {
6324 function strip(number) {
6325 return parseFloat(number.toPrecision(12))
6326 }
6327 this.init = function() {
6328 this.bitcoinURI = $stateParams.url;
6329 var URI = bitcore.URI;
6330 URI.isValid(this.bitcoinURI);
6331 if (!URI.isValid(this.bitcoinURI)) return void(this.error = !0);
6332 var uri = new URI(this.bitcoinURI);
6333 if (uri && uri.address) {
6334 var config = configService.getSync().wallet.settings,
6335 unitToSatoshi = config.unitToSatoshi,
6336 satToUnit = 1 / unitToSatoshi,
6337 unitName = config.unitName;
6338 uri.amount && (uri.amount = strip(uri.amount * satToUnit) + " " + unitName), uri.network = uri.address.network.name, this.uri = uri
6339 }
6340 }, this.getWallets = function(network) {
6341 $scope.wallets = [], lodash.forEach(profileService.getWallets(network), function(w) {
6342 var client = profileService.getClient(w.id);
6343 profileService.isReady(client, function(err) {
6344 err || $scope.wallets.push(w)
6345 })
6346 })
6347 }, this.selectWallet = function(wid) {
6348 var self = this;
6349 profileService.setAndStoreFocus(wid, function() {}), go.walletHome(), $timeout(function() {
6350 $rootScope.$emit("paymentUri", self.bitcoinURI)
6351 }, 1e3)
6352 }
6353 }), angular.module("copayApp.controllers").controller("preferencesController", function($scope, $rootScope, $timeout, $log, configService, profileService, fingerprintService, walletService) {
6354 var fc, config = configService.getSync(),
6355 disableFocusListener = $rootScope.$on("Local/NewFocusedWalletReady", function() {
6356 $scope.init()
6357 });
6358 $scope.$on("$destroy", function() {
6359 disableFocusListener()
6360 }), $scope.init = function() {
6361 $scope.externalSource = null, fc = profileService.focusedClient, fc && ($scope.encryptEnabled = walletService.isEncrypted(fc), fc.isPrivKeyExternal && ($scope.externalSource = "ledger" == fc.getPrivKeyExternalSourceName() ? "Ledger" : "Trezor")), $scope.touchidAvailable = fingerprintService.isAvailable(), $scope.touchidEnabled = config.touchIdFor ? config.touchIdFor[fc.credentials.walletId] : null, $scope.deleted = !1, !fc.credentials || fc.credentials.mnemonicEncrypted || fc.credentials.mnemonic || ($scope.deleted = !0)
6362 };
6363 var handleEncryptedWallet = function(cb) {
6364 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
6365 return cb(err ? err : walletService.unlock(fc, password))
6366 })
6367 };
6368 $scope.encryptChange = function() {
6369 if (fc) {
6370 var val = $scope.encryptEnabled,
6371 setPrivateKeyEncryption = function(password, cb) {
6372 $log.debug("Encrypting private key for", fc.credentials.walletName), fc.setPrivateKeyEncryption(password), fc.lock(), profileService.updateCredentials(JSON.parse(fc.export()), function() {
6373 return $log.debug("Wallet encrypted"), cb()
6374 })
6375 },
6376 disablePrivateKeyEncryption = function(cb) {
6377 $log.debug("Disabling private key encryption for", fc.credentials.walletName);
6378 try {
6379 fc.disablePrivateKeyEncryption()
6380 } catch (e) {
6381 return cb(e)
6382 }
6383 profileService.updateCredentials(JSON.parse(fc.export()), function() {
6384 return $log.debug("Wallet encryption disabled"), cb()
6385 })
6386 };
6387 val && !walletService.isEncrypted(fc) ? $rootScope.$emit("Local/NeedsPassword", !0, function(err, password) {
6388 if (err || !password) return void($scope.encryptEnabled = !1);
6389 setPrivateKeyEncryption(password, function() {
6390 $rootScope.$emit("Local/NewEncryptionSetting"), $scope.encryptEnabled = !0
6391 })
6392 }) : !val && walletService.isEncrypted(fc) && handleEncryptedWallet(function(err) {
6393 if (err) return void($scope.encryptEnabled = !0);
6394 disablePrivateKeyEncryption(function(err) {
6395 if ($rootScope.$emit("Local/NewEncryptionSetting"), err) return $scope.encryptEnabled = !0, void $log.error(err);
6396 $scope.encryptEnabled = !1
6397 })
6398 })
6399 }
6400 }, $scope.touchidChange = function() {
6401 var walletId = fc.credentials.walletId,
6402 opts = {
6403 touchIdFor: {}
6404 };
6405 opts.touchIdFor[walletId] = $scope.touchidEnabled, fingerprintService.check(fc, function(err) {
6406 if (err) return $log.debug(err), void $timeout(function() {
6407 $scope.touchidError = !0, $scope.touchidEnabled = !0
6408 }, 100);
6409 configService.set(opts, function(err) {
6410 err && ($log.debug(err), $scope.touchidError = !0, $scope.touchidEnabled = !1)
6411 })
6412 })
6413 }
6414 }), angular.module("copayApp.controllers").controller("preferencesAbout", function() {}), angular.module("copayApp.controllers").controller("preferencesAliasController", function($scope, $timeout, configService, profileService, go) {
6415 var fc = profileService.focusedClient,
6416 walletId = fc.credentials.walletId,
6417 config = configService.getSync();
6418 config.aliasFor = config.aliasFor || {}, $scope.alias = config.aliasFor[walletId] || fc.credentials.walletName, $scope.save = function() {
6419 var opts = {
6420 aliasFor: {}
6421 };
6422 opts.aliasFor[walletId] = $scope.alias, configService.set(opts, function(err) {
6423 if (err) return void $scope.$emit("Local/DeviceError", err);
6424 $scope.$emit("Local/AliasUpdated"), $timeout(function() {
6425 go.path("preferences")
6426 }, 50)
6427 })
6428 }
6429 }), angular.module("copayApp.controllers").controller("preferencesAltCurrencyController", function($scope, $log, $timeout, configService, rateService, lodash, go, profileService, walletService) {
6430 var completeAlternativeList, config = configService.getSync(),
6431 next = 10;
6432 $scope.currentCurrency = config.wallet.settings.alternativeIsoCode, $scope.listComplete = !1, $scope.init = function() {
6433 rateService.whenAvailable(function() {
6434 completeAlternativeList = rateService.listAlternatives(), lodash.remove(completeAlternativeList, function(c) {
6435 return "BTC" == c.isoCode
6436 }), $scope.altCurrencyList = completeAlternativeList.slice(0, next)
6437 })
6438 }, $scope.loadMore = function() {
6439 $timeout(function() {
6440 $scope.altCurrencyList = completeAlternativeList.slice(0, next), next += 10, $scope.listComplete = $scope.altCurrencyList.length >= completeAlternativeList.length, $scope.$broadcast("scroll.infiniteScrollComplete")
6441 }, 100)
6442 }, $scope.save = function(newAltCurrency) {
6443 var opts = {
6444 wallet: {
6445 settings: {
6446 alternativeName: newAltCurrency.name,
6447 alternativeIsoCode: newAltCurrency.isoCode
6448 }
6449 }
6450 };
6451 configService.set(opts, function(err) {
6452 err && $log.warn(err), go.preferencesGlobal(), $scope.$emit("Local/UnitSettingUpdated"), walletService.updateRemotePreferences(profileService.getClients(), {}, function() {
6453 $log.debug("Remote preferences saved")
6454 })
6455 })
6456 }
6457 }), angular.module("copayApp.controllers").controller("preferencesBwsUrlController", function($scope, $log, configService, applicationService, profileService, storageService) {
6458 $scope.error = null, $scope.success = null;
6459 var fc = profileService.focusedClient,
6460 walletId = fc.credentials.walletId,
6461 defaults = configService.getDefaults(),
6462 config = configService.getSync();
6463 $scope.bwsurl = config.bwsFor && config.bwsFor[walletId] || defaults.bws.url, $scope.resetDefaultUrl = function() {
6464 $scope.bwsurl = defaults.bws.url
6465 }, $scope.save = function() {
6466 var bws;
6467 switch ($scope.bwsurl) {
6468 case "prod":
6469 case "production":
6470 bws = "https://ntp1node.nebl.io:3232/bws/api";
6471 break;
6472 case "sta":
6473 case "staging":
6474 bws = "https://ntp1node.nebl.io:3232/bws/api";
6475 break;
6476 case "loc":
6477 case "local":
6478 bws = "https://ntp1node.nebl.io:3232/bws/api"
6479 }
6480 bws && ($log.info("Using BWS URL Alias to " + bws), $scope.bwsurl = bws);
6481 var opts = {
6482 bwsFor: {}
6483 };
6484 opts.bwsFor[walletId] = $scope.bwsurl, configService.set(opts, function(err) {
6485 err && $log.debug(err), storageService.setCleanAndScanAddresses(walletId, function() {
6486 applicationService.restart()
6487 })
6488 })
6489 }
6490 }), angular.module("copayApp.controllers").controller("preferencesCoinbaseController", function($scope, $timeout, $ionicModal, applicationService, coinbaseService) {
6491 this.revokeToken = function(testnet) {
6492 $scope.network = testnet ? "testnet" : "livenet", $ionicModal.fromTemplateUrl("views/modals/coinbase-confirmation.html", {
6493 scope: $scope,
6494 animation: "slide-in-up"
6495 }).then(function(modal) {
6496 $scope.coinbaseConfirmationModal = modal, $scope.coinbaseConfirmationModal.show()
6497 })
6498 }
6499 }), angular.module("copayApp.controllers").controller("preferencesColorController", function($scope, $log, configService, profileService, go) {
6500 $scope.colorList = ["#DD4B39", "#F38F12", "#FAA77F", "#D0B136", "#9EDD72", "#29BB9C", "#019477", "#77DADA", "#0fa2dc", "#484ED3", "#9B59B6", "#E856EF", "#FF599E", "#7A8C9E"];
6501 var fc = profileService.focusedClient,
6502 walletId = fc.credentials.walletId,
6503 config = configService.getSync();
6504 config.colorFor = config.colorFor || {}, $scope.currentColor = config.colorFor[walletId] || "#0fa2dc", $scope.save = function(color) {
6505 var opts = {
6506 colorFor: {}
6507 };
6508 opts.colorFor[walletId] = color, configService.set(opts, function(err) {
6509 go.preferences(), err && $log.warn(err), $scope.$emit("Local/ColorUpdated")
6510 })
6511 }
6512 }), angular.module("copayApp.controllers").controller("preferencesDeleteWalletController", function($scope, $rootScope, $filter, $timeout, $log, $ionicModal, storageService, notification, profileService, platformInfo, go, gettext, gettextCatalog, applicationService, ongoingProcess) {
6513 var isCordova = platformInfo.isCordova;
6514 $scope.isCordova = isCordova, $scope.error = null;
6515 var delete_msg = gettextCatalog.getString("Are you sure you want to delete this wallet?"),
6516 accept_msg = gettextCatalog.getString("Accept"),
6517 cancel_msg = gettextCatalog.getString("Cancel"),
6518 confirm_msg = gettextCatalog.getString("Confirm"),
6519 _modalDeleteWallet = function() {
6520 $scope.title = delete_msg, $scope.accept_msg = accept_msg, $scope.cancel_msg = cancel_msg, $scope.confirm_msg = confirm_msg, $scope.okAction = doDeleteWallet, $scope.loading = !1, $ionicModal.fromTemplateUrl("views/modals/confirmation.html", {
6521 scope: $scope
6522 }).then(function(modal) {
6523 $scope.confirmationModal = modal, $scope.confirmationModal.show()
6524 })
6525 },
6526 doDeleteWallet = function() {
6527 ongoingProcess.set("deletingWallet", !0);
6528 var fc = profileService.focusedClient,
6529 name = fc.credentials.walletName,
6530 walletName = (fc.alias || "") + " [" + name + "]";
6531 profileService.deleteWalletClient(fc, function(err) {
6532 ongoingProcess.set("deletingWallet", !1), err ? $scope.error = err.message || err : (go.walletHome(), notification.success(gettextCatalog.getString("Success"), gettextCatalog.getString('The wallet "{{walletName}}" was deleted', {
6533 walletName: walletName
6534 })))
6535 })
6536 };
6537 $scope.deleteWallet = function() {
6538 isCordova ? navigator.notification.confirm(delete_msg, function(buttonIndex) {
6539 1 == buttonIndex && doDeleteWallet()
6540 }, confirm_msg, [accept_msg, cancel_msg]) : _modalDeleteWallet()
6541 }
6542 }), angular.module("copayApp.controllers").controller("preferencesDeleteWordsController", function($scope, confirmDialog, lodash, notification, profileService, go, gettext) {
6543 var fc = profileService.focusedClient,
6544 msg = gettext("Are you sure you want to delete the recovery phrase?"),
6545 successMsg = gettext("Recovery phrase deleted");
6546 lodash.isEmpty(fc.credentials.mnemonic) && lodash.isEmpty(fc.credentials.mnemonicEncrypted) && ($scope.deleted = !0), $scope.delete = function() {
6547 confirmDialog.show(msg, function(ok) {
6548 ok && (fc.clearMnemonic(), profileService.updateCredentials(JSON.parse(fc.export()), function() {
6549 notification.success(successMsg), go.walletHome()
6550 }))
6551 })
6552 }
6553 }), angular.module("copayApp.controllers").controller("preferencesEmailController", function($rootScope, $scope, go, profileService, walletService) {
6554 $scope.save = function(form) {
6555 $scope.error = null, $scope.saving = !0;
6556 var fc = profileService.focusedClient,
6557 email = $scope.email || "";
6558 walletService.updateRemotePreferences(fc, {
6559 email: email
6560 }, function(err) {
6561 $scope.saving = !1, err || $rootScope.$emit("Local/EmailUpdated", email), go.path("preferences")
6562 })
6563 }
6564 }), angular.module("copayApp.controllers").controller("preferencesFeeController", function($scope, $timeout, configService, feeService) {
6565 $scope.loading = !0, feeService.getFeeLevels(function(levels) {
6566 $scope.loading = !1, $scope.feeOpts = feeService.feeOpts, $scope.currentFeeLevel = feeService.getCurrentFeeLevel(), $scope.feeLevels = levels, $scope.$apply()
6567 }), $scope.save = function(newFee) {
6568 var opts = {
6569 wallet: {
6570 settings: {
6571 feeLevel: newFee.level
6572 }
6573 }
6574 };
6575 configService.set(opts, function(err) {
6576 err && $log.debug(err), $scope.currentFeeLevel = newFee.level, $timeout(function() {
6577 $scope.$apply()
6578 }, 10)
6579 })
6580 }
6581 }), angular.module("copayApp.controllers").controller("preferencesGlideraController", function($scope, $timeout, $ionicModal, profileService, applicationService, glideraService, storageService) {
6582 this.getEmail = function(token) {
6583 var self = this;
6584 glideraService.getEmail(token, function(error, data) {
6585 self.email = data
6586 })
6587 }, this.getPersonalInfo = function(token) {
6588 var self = this;
6589 glideraService.getPersonalInfo(token, function(error, info) {
6590 self.personalInfo = info
6591 })
6592 }, this.getStatus = function(token) {
6593 var self = this;
6594 glideraService.getStatus(token, function(error, data) {
6595 self.status = data
6596 })
6597 }, this.getLimits = function(token) {
6598 var self = this;
6599 glideraService.getLimits(token, function(error, limits) {
6600 self.limits = limits
6601 })
6602 }, this.revokeToken = function(testnet) {
6603 $scope.network = testnet ? "testnet" : "livenet", $scope.loading = !1, $ionicModal.fromTemplateUrl("views/modals/glidera-confirmation.html", {
6604 scope: $scope,
6605 animation: "slide-in-up"
6606 }).then(function(modal) {
6607 $scope.glideraConfirmationModal = modal, $scope.glideraConfirmationModal.show()
6608 })
6609 }
6610 }), angular.module("copayApp.controllers").controller("preferencesGlobalController", function($scope, $rootScope, $log, configService, uxLanguage, platformInfo, pushNotificationsService, profileService, feeService) {
6611 var isCordova = platformInfo.isCordova;
6612 isCordova && StatusBar.isVisible && StatusBar.backgroundColorByHexString("#4B6178"), $scope.init = function() {
6613 var config = configService.getSync();
6614 $scope.unitName = config.wallet.settings.unitName, $scope.currentLanguageName = uxLanguage.getCurrentLanguageName(), $scope.selectedAlternative = {
6615 name: config.wallet.settings.alternativeName,
6616 isoCode: config.wallet.settings.alternativeIsoCode
6617 }, $scope.feeOpts = feeService.feeOpts, $scope.currentFeeLevel = feeService.getCurrentFeeLevel(), $scope.usePushNotifications = isCordova && !platformInfo.isWP, $scope.PNEnabledByUser = !0, $scope.isIOSApp = platformInfo.isIOS && isCordova, $scope.isIOSApp && cordova.plugins.diagnostic.isRemoteNotificationsEnabled(function(isEnabled) {
6618 $scope.PNEnabledByUser = isEnabled, $scope.$digest()
6619 }), $scope.spendUnconfirmed = config.wallet.spendUnconfirmed, $scope.glideraEnabled = config.glidera.enabled, $scope.coinbaseEnabled = config.coinbase.enabled, $scope.pushNotifications = config.pushNotifications.enabled
6620 }, $scope.openSettings = function() {
6621 cordova.plugins.diagnostic.switchToSettings(function() {
6622 $log.debug("switched to settings")
6623 }, function(err) {
6624 $log.debug(err)
6625 })
6626 }, $scope.spendUnconfirmedChange = function() {
6627 var opts = {
6628 wallet: {
6629 spendUnconfirmed: $scope.spendUnconfirmed
6630 }
6631 };
6632 configService.set(opts, function(err) {
6633 $rootScope.$emit("Local/SpendUnconfirmedUpdated", $scope.spendUnconfirmed), err && $log.debug(err)
6634 })
6635 }, $scope.pushNotificationsChange = function() {
6636 var opts = {
6637 pushNotifications: {
6638 enabled: $scope.pushNotifications
6639 }
6640 };
6641 configService.set(opts, function(err) {
6642 opts.pushNotifications.enabled ? pushNotificationsService.enableNotifications(profileService.walletClients) : pushNotificationsService.disableNotifications(profileService.walletClients), err && $log.debug(err)
6643 })
6644 }, $scope.glideraChange = function() {
6645 var opts = {
6646 glidera: {
6647 enabled: $scope.glideraEnabled
6648 }
6649 };
6650 configService.set(opts, function(err) {
6651 $rootScope.$emit("Local/GlideraUpdated"), err && $log.debug(err)
6652 })
6653 }, $scope.coinbaseChange = function() {
6654 var opts = {
6655 coinbase: {
6656 enabled: $scope.coinbaseEnabled
6657 }
6658 };
6659 configService.set(opts, function(err) {
6660 $rootScope.$emit("Local/CoinbaseUpdated"), err && $log.debug(err)
6661 })
6662 }
6663 }), angular.module("copayApp.controllers").controller("preferencesHistory", function($scope, $log, $timeout, storageService, go, profileService, lodash) {
6664 var fc = profileService.focusedClient,
6665 c = fc.credentials;
6666 $scope.csvReady = !1, $scope.csvHistory = function(cb) {
6667 function formatDate(date) {
6668 var dateObj = new Date(date);
6669 return dateObj ? dateObj.toJSON() ? dateObj.toJSON() : "" : ($log.debug("Error formating a date"), "DateError")
6670 }
6671 var allTxs = [];
6672 $log.debug("Generating CSV from History"),
6673 function(cb) {
6674 storageService.getTxHistory(c.walletId, function(err, txs) {
6675 if (err) return cb(err);
6676 var txsFromLocal = [];
6677 try {
6678 txsFromLocal = JSON.parse(txs)
6679 } catch (ex) {
6680 $log.warn(ex)
6681 }
6682 return allTxs.push(txsFromLocal), cb(null, lodash.flatten(allTxs))
6683 })
6684 }(function(err, txs) {
6685 if (!err && txs) {
6686 $log.debug("Wallet Transaction History Length:", txs.length), $scope.satToUnit = 1 / $scope.unitToSatoshi;
6687 var data = txs;
6688 $scope.csvContent = [], $scope.csvFilename = "Copay-" + ($scope.alias || $scope.walletName) + ".csv", $scope.csvHeader = ["Date", "Destination", "Description", "Amount", "Currency", "Txid", "Creator", "Copayers", "Comment"];
6689 var _amount, _note, _copayers, _creator, _comment;
6690 if (data.forEach(function(it, index) {
6691 var amount = it.amount;
6692 if ("moved" == it.action && (amount = 0), _copayers = "", _creator = "", it.actions && it.actions.length > 1) {
6693 for (var i = 0; i < it.actions.length; i++) _copayers += it.actions[i].copayerName + ":" + it.actions[i].type + " - ";
6694 _creator = it.creatorName && "undefined" != it.creatorName ? it.creatorName : ""
6695 }
6696 if (_amount = ("sent" == it.action ? "-" : "") + (1e-8 * amount).toFixed(8), _note = it.message || "", _comment = it.note ? it.note.body : "", "moved" == it.action && (_note += " Moved:" + (1e-8 * it.amount).toFixed(8)), $scope.csvContent.push({
6697 Date: formatDate(1e3 * it.time),
6698 Destination: it.addressTo || "",
6699 Description: _note,
6700 Amount: _amount,
6701 Currency: "NEBL",
6702 Txid: it.txid,
6703 Creator: _creator,
6704 Copayers: _copayers,
6705 Comment: _comment
6706 }), it.fees && ("moved" == it.action || "sent" == it.action)) {
6707 var _fee = (1e-8 * it.fees).toFixed(8);
6708 $scope.csvContent.push({
6709 Date: formatDate(1e3 * it.time),
6710 Destination: "Neblio Network Fees",
6711 Description: "",
6712 Amount: "-" + _fee,
6713 Currency: "NEBL",
6714 Txid: "",
6715 Creator: "",
6716 Copayers: ""
6717 })
6718 }
6719 }), $scope.csvReady = !0, $timeout(function() {
6720 $scope.$apply()
6721 }, 100), cb) return cb()
6722 } else if ($log.warn("Failed to generate CSV:", err), cb) return cb(err)
6723 })
6724 }, $scope.clearTransactionHistory = function() {
6725 storageService.removeTxHistory(c.walletId, function(err) {
6726 if (err) return void $log.error(err);
6727 $scope.$emit("Local/ClearHistory"), $timeout(function() {
6728 go.walletHome()
6729 }, 100)
6730 })
6731 }
6732 }), angular.module("copayApp.controllers").controller("preferencesInformation", function($scope, $log, $timeout, platformInfo, gettextCatalog, lodash, profileService, configService, go) {
6733 var fc = profileService.focusedClient,
6734 c = fc.credentials,
6735 walletId = c.walletId,
6736 config = configService.getSync(),
6737 b = 1,
6738 isCordova = platformInfo.isCordova;
6739 config.colorFor = config.colorFor || {}, $scope.init = function() {
6740 var basePath = c.getBaseAddressDerivationPath();
6741 $scope.walletName = c.walletName, $scope.walletId = c.walletId, $scope.network = c.network, $scope.addressType = c.addressType || "P2SH", $scope.derivationStrategy = c.derivationStrategy || "BIP45", $scope.basePath = basePath, $scope.M = c.m, $scope.N = c.n, $scope.pubKeys = lodash.pluck(c.publicKeyRing, "xPubKey"), $scope.addrs = null, fc.getMainAddresses({
6742 doNotVerify: !0
6743 }, function(err, addrs) {
6744 if (err) return void $log.warn(err);
6745 for (var last10 = [], i = 0, e = addrs.pop(); i++ < 10 && e;) e.path = "xpub" + e.path.substring(1), last10.push(e), e = addrs.pop();
6746 $scope.addrs = last10, $timeout(function() {
6747 $scope.$apply()
6748 })
6749 })
6750 }, $scope.sendAddrs = function() {
6751 function formatDate(ts) {
6752 var dateObj = new Date(1e3 * ts);
6753 return dateObj ? dateObj.toJSON() ? dateObj.toJSON() : "" : ($log.debug("Error formating a date"), "DateError")
6754 }
6755 $timeout(function() {
6756 fc.getMainAddresses({
6757 doNotVerify: !0
6758 }, function(err, addrs) {
6759 if (err) return void $log.warn(err);
6760 var body = 'Copay Wallet "' + $scope.walletName + '" Addresses\n Only Main Addresses are shown.\n\n';
6761 body += "\n", body += addrs.map(function(v) {
6762 return "* " + v.address + " xpub" + v.path.substring(1) + " " + formatDate(v.createdOn)
6763 }).join("\n"), window.plugins.socialsharing.shareViaEmail(body, "Copay Addresses", null, null, null, null, function() {}, function() {}), $timeout(function() {
6764 $scope.$apply()
6765 }, 1e3)
6766 })
6767 }, 100)
6768 }, $scope.saveBlack = function() {
6769 if (5 != b) return b++;
6770 ! function(color) {
6771 var opts = {
6772 colorFor: {}
6773 };
6774 opts.colorFor[walletId] = color, configService.set(opts, function(err) {
6775 go.walletHome(), err && $log.warn(err), $scope.$emit("Local/ColorUpdated")
6776 })
6777 }("#202020")
6778 }, $scope.copyToClipboard = function(data) {
6779 isCordova && (window.cordova.plugins.clipboard.copy(data), window.plugins.toast.showShortCenter(gettextCatalog.getString("Copied to clipboard")))
6780 }
6781 }), angular.module("copayApp.controllers").controller("preferencesLanguageController", function($scope, $log, configService, profileService, uxLanguage, walletService, go) {
6782 $scope.availableLanguages = uxLanguage.getLanguages(), $scope.currentLanguage = uxLanguage.getCurrentLanguage(), $scope.save = function(newLang) {
6783 var opts = {
6784 wallet: {
6785 settings: {
6786 defaultLanguage: newLang
6787 }
6788 }
6789 };
6790 configService.set(opts, function(err) {
6791 err && $log.warn(err), go.preferencesGlobal(), uxLanguage.update(function() {
6792 walletService.updateRemotePreferences(profileService.getClients(), {}, function() {
6793 $log.debug("Remote preferences saved")
6794 })
6795 })
6796 })
6797 }
6798 }), angular.module("copayApp.controllers").controller("preferencesLogs", function(historicLog) {
6799 this.logs = historicLog.get(), this.sendLogs = function() {
6800 var body = "Copay Session Logs\n Be careful, this could contain sensitive private data\n\n";
6801 body += "\n\n", body += this.logs.map(function(v) {
6802 return v.msg
6803 }).join("\n"), window.plugins.socialsharing.shareViaEmail(body, "Copay Logs", null, null, null, null, function() {}, function() {})
6804 }
6805 }), angular.module("copayApp.controllers").controller("preferencesUnitController", function($scope, $log, configService, go, walletService, profileService) {
6806 var config = configService.getSync();
6807 $scope.currentUnit = config.wallet.settings.unitCode, $scope.unitList = [{
6808 name: "uNEBL (1,000,000 uNEBL = 1NEBL)",
6809 shortName: "uNEBL",
6810 value: 100,
6811 decimals: 2,
6812 code: "bit"
6813 }, {
6814 name: "NEBL",
6815 shortName: "NEBL",
6816 value: 1e8,
6817 decimals: 8,
6818 code: "btc"
6819 }], $scope.save = function(newUnit) {
6820 var opts = {
6821 wallet: {
6822 settings: {
6823 unitName: newUnit.shortName,
6824 unitToSatoshi: newUnit.value,
6825 unitDecimals: newUnit.decimals,
6826 unitCode: newUnit.code
6827 }
6828 }
6829 };
6830 configService.set(opts, function(err) {
6831 err && $log.warn(err), go.preferencesGlobal(), $scope.$emit("Local/UnitSettingUpdated"), walletService.updateRemotePreferences(profileService.getClients(), {}, function() {
6832 $log.debug("Remote preferences saved")
6833 })
6834 })
6835 }
6836 }), angular.module("copayApp.controllers").controller("sellCoinbaseController", function($rootScope, $scope, $log, $timeout, $ionicModal, lodash, profileService, coinbaseService, configService, walletService, fingerprintService, ongoingProcess, go) {
6837 var client, self = this;
6838 $scope.priceSensitivity = [{
6839 value: .5,
6840 name: "0.5%"
6841 }, {
6842 value: 1,
6843 name: "1%"
6844 }, {
6845 value: 2,
6846 name: "2%"
6847 }, {
6848 value: 5,
6849 name: "5%"
6850 }, {
6851 value: 10,
6852 name: "10%"
6853 }], $scope.selectedPriceSensitivity = $scope.priceSensitivity[1];
6854 var handleEncryptedWallet = function(client, cb) {
6855 if (!walletService.isEncrypted(client)) return cb();
6856 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
6857 return cb(err ? err : walletService.unlock(client, password))
6858 })
6859 };
6860 this.init = function(testnet) {
6861 self.allWallets = profileService.getWallets(testnet ? "testnet" : "livenet", 1), (client = profileService.focusedClient) && 1 == client.credentials.m && $timeout(function() {
6862 self.selectedWalletId = client.credentials.walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
6863 }, 100)
6864 }, this.getPaymentMethods = function(token) {
6865 coinbaseService.getPaymentMethods(token, function(err, p) {
6866 if (err) return void(self.error = err);
6867 self.paymentMethods = [], lodash.each(p.data, function(pm) {
6868 pm.allow_sell && self.paymentMethods.push(pm), pm.allow_sell && pm.primary_sell && ($scope.selectedPaymentMethod = pm)
6869 })
6870 })
6871 }, this.getPrice = function(token) {
6872 coinbaseService.sellPrice(token, "USD", function(err, s) {
6873 err || (self.sellPrice = s.data || null)
6874 })
6875 }, $scope.openWalletsModal = function(wallets) {
6876 self.error = null, $scope.type = "SELL", $scope.wallets = wallets, $scope.noColor = !0, $scope.self = self, $ionicModal.fromTemplateUrl("views/modals/wallets.html", {
6877 scope: $scope,
6878 animation: "slide-in-up"
6879 }).then(function(modal) {
6880 $scope.walletsModal = modal, $scope.walletsModal.show()
6881 }), $scope.$on("walletSelected", function(ev, walletId) {
6882 $timeout(function() {
6883 client = profileService.getClient(walletId), self.selectedWalletId = walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
6884 }, 100), $scope.walletsModal.hide()
6885 })
6886 }, this.depositFunds = function(token, account) {
6887 if (self.error = null, $scope.amount) this.createTx(token, account, $scope.amount);
6888 else if ($scope.fiat) {
6889 var btcValue = ($scope.fiat / self.sellPrice.amount).toFixed(8);
6890 this.createTx(token, account, btcValue)
6891 }
6892 }, this.sellRequest = function(token, account, ctx) {
6893 if (self.error = null, ctx.amount) {
6894 var accountId = account.id,
6895 data = ctx.amount;
6896 data.payment_method = $scope.selectedPaymentMethod.id || null, ongoingProcess.set("Sending request...", !0), coinbaseService.sellRequest(token, accountId, data, function(err, sell) {
6897 if (ongoingProcess.set("Sending request...", !1), err) return void(self.error = err);
6898 self.sellInfo = sell.data
6899 })
6900 }
6901 }, this.confirmSell = function(token, account, sell) {
6902 self.error = null;
6903 var accountId = account.id,
6904 sellId = sell.id;
6905 ongoingProcess.set("Selling Bitcoin...", !0), coinbaseService.sellCommit(token, accountId, sellId, function(err, data) {
6906 if (ongoingProcess.set("Selling Bitcoin...", !1), err) return void(self.error = err);
6907 self.success = data.data, $scope.$emit("Local/CoinbaseTx")
6908 })
6909 }, this.createTx = function(token, account, amount) {
6910 if (self.error = null, !client) return void(self.error = "No wallet selected");
6911 var accountId = account.id,
6912 dataSrc = {
6913 name: "Received from Copay: " + self.selectedWalletName
6914 },
6915 outputs = [],
6916 config = configService.getSync(),
6917 configWallet = config.wallet,
6918 walletSettings = configWallet.settings;
6919 ongoingProcess.set("Creating Transaction...", !0), $timeout(function() {
6920 coinbaseService.createAddress(token, accountId, dataSrc, function(err, data) {
6921 if (err) return ongoingProcess.set("Creating Transaction...", !1), void(self.error = err);
6922 var address, comment;
6923 address = data.data.address, amount = parseInt((1e8 * amount).toFixed(0)), comment = "Send funds to Coinbase Account: " + account.name, outputs.push({
6924 toAddress: address,
6925 amount: amount,
6926 message: comment
6927 });
6928 var txp = {
6929 toAddress: address,
6930 amount: amount,
6931 outputs: outputs,
6932 message: comment,
6933 payProUrl: null,
6934 excludeUnconfirmedUtxos: !configWallet.spendUnconfirmed,
6935 feeLevel: walletSettings.feeLevel || "normal"
6936 };
6937 walletService.createTx(client, txp, function(err, createdTxp) {
6938 if (err) return $log.debug(err), ongoingProcess.set("Creating Transaction...", !1), self.error = {
6939 errors: [{
6940 message: "Could not create transaction: " + err.message
6941 }]
6942 }, void $scope.$apply();
6943 ongoingProcess.set("Creating Transaction...", !1), $scope.$emit("Local/NeedsConfirmation", createdTxp, function(accept) {
6944 accept ? self.confirmTx(createdTxp, function(err, tx) {
6945 if (ongoingProcess.clear(), err) return void(self.error = {
6946 errors: [{
6947 message: "Could not create transaction: " + err.message
6948 }]
6949 });
6950 ongoingProcess.set("Checking Transaction...", !1), coinbaseService.getTransactions(token, accountId, function(err, ctxs) {
6951 if (err) return void $log.debug(err);
6952 lodash.each(ctxs.data, function(ctx) {
6953 if ("send" == ctx.type && ctx.from) return ongoingProcess.clear(), "completed" == ctx.status ? self.sellRequest(token, account, ctx) : (ctx.price_sensitivity = $scope.selectedPriceSensitivity, ctx.sell_price_amount = self.sellPrice ? self.sellPrice.amount : "", ctx.sell_price_currency = self.sellPrice ? self.sellPrice.currency : "USD", ctx.description = "Copay Wallet: " + client.credentials.walletName, coinbaseService.savePendingTransaction(ctx, null, function(err) {
6954 err && $log.debug(err), self.sendInfo = ctx, $timeout(function() {
6955 $scope.$emit("Local/CoinbaseTx")
6956 }, 1e3)
6957 })), !1
6958 })
6959 })
6960 }) : go.path("coinbase")
6961 })
6962 })
6963 })
6964 }, 100)
6965 }, this.confirmTx = function(txp, cb) {
6966 fingerprintService.check(client, function(err) {
6967 if (err) return $log.debug(err), cb(err);
6968 handleEncryptedWallet(client, function(err) {
6969 if (err) return $log.debug(err), cb(err);
6970 ongoingProcess.set("Sending Bitcoin to Coinbase...", !0), walletService.publishTx(client, txp, function(err, publishedTxp) {
6971 if (err) return ongoingProcess.set("Sending Bitcoin to Coinbase...", !1), $log.debug(err), cb({
6972 errors: [{
6973 message: "Transaction could not be published: " + err.message
6974 }]
6975 });
6976 walletService.signTx(client, publishedTxp, function(err, signedTxp) {
6977 if (walletService.lock(client), err) return ongoingProcess.set("Sending Bitcoin to Coinbase...", !1), $log.debug(err), walletService.removeTx(client, signedTxp, function(err) {
6978 err && $log.debug(err)
6979 }), cb({
6980 errors: [{
6981 message: "The payment was created but could not be completed: " + err.message
6982 }]
6983 });
6984 walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) {
6985 if (err) return ongoingProcess.set("Sending Bitcoin to Coinbase...", !1), $log.debug(err), walletService.removeTx(client, broadcastedTxp, function(err) {
6986 err && $log.debug(err)
6987 }), cb({
6988 errors: [{
6989 message: "The payment was created but could not be broadcasted: " + err.message
6990 }]
6991 });
6992 $timeout(function() {
6993 return cb(null, broadcastedTxp)
6994 }, 5e3)
6995 })
6996 })
6997 })
6998 })
6999 })
7000 }
7001 }), angular.module("copayApp.controllers").controller("sellGlideraController", function($rootScope, $scope, $timeout, $ionicModal, $log, configService, profileService, addressService, feeService, glideraService, bwcError, lodash, walletService, fingerprintService, ongoingProcess, go) {
7002 var self = this,
7003 config = configService.getSync();
7004 this.data = {}, this.show2faCodeInput = null, this.success = null, this.error = null;
7005 var client, handleEncryptedWallet = function(client, cb) {
7006 if (!walletService.isEncrypted(client)) return cb();
7007 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
7008 return cb(err ? err : walletService.unlock(client, password))
7009 })
7010 };
7011 this.init = function(testnet) {
7012 self.allWallets = profileService.getWallets(testnet ? "testnet" : "livenet", 1), (client = profileService.focusedClient) && 1 == client.credentials.m && $timeout(function() {
7013 self.selectedWalletId = client.credentials.walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
7014 }, 100)
7015 }, $scope.openWalletsModal = function(wallets) {
7016 self.error = null, $scope.type = "SELL", $scope.wallets = wallets, $scope.noColor = !0, $scope.self = self, $ionicModal.fromTemplateUrl("views/modals/wallets.html", {
7017 scope: $scope,
7018 animation: "slide-in-up"
7019 }).then(function(modal) {
7020 $scope.walletsModal = modal, $scope.walletsModal.show()
7021 }), $scope.$on("walletSelected", function(ev, walletId) {
7022 $timeout(function() {
7023 client = profileService.getClient(walletId), self.selectedWalletId = walletId, self.selectedWalletName = client.credentials.walletName, $scope.$apply()
7024 }, 100), $scope.walletsModal.hide()
7025 })
7026 }, this.getSellPrice = function(token, price) {
7027 var self = this;
7028 if (self.error = null, !price || price && !price.qty && !price.fiat) return void(self.sellPrice = null);
7029 self.gettingSellPrice = !0, glideraService.sellPrice(token, price, function(err, sellPrice) {
7030 if (self.gettingSellPrice = !1, err) return void(self.error = "Could not get exchange information. Please, try again.");
7031 self.sellPrice = sellPrice
7032 })
7033 }, this.get2faCode = function(token) {
7034 var self = this;
7035 ongoingProcess.set("Sending 2FA code...", !0), $timeout(function() {
7036 glideraService.get2faCode(token, function(err, sent) {
7037 ongoingProcess.set("Sending 2FA code...", !1), err ? self.error = "Could not send confirmation code to your phone" : self.show2faCodeInput = sent
7038 })
7039 }, 100)
7040 }, this.createTx = function(token, permissions, twoFaCode) {
7041 var self = this;
7042 self.error = null;
7043 var outputs = [],
7044 configWallet = config.wallet,
7045 walletSettings = configWallet.settings;
7046 if (!client) return void(self.error = "No wallet selected");
7047 ongoingProcess.set("creatingTx", !0), addressService.getAddress(client.credentials.walletId, null, function(err, refundAddress) {
7048 if (!refundAddress) return ongoingProcess.clear(), void(self.error = bwcError.msg(err, "Could not create address"));
7049 glideraService.getSellAddress(token, function(error, sellAddress) {
7050 if (!sellAddress) return ongoingProcess.clear(), void(self.error = "Could not get the destination bitcoin address");
7051 var amount = parseInt((1e8 * self.sellPrice.qty).toFixed(0));
7052 outputs.push({
7053 toAddress: sellAddress,
7054 amount: amount,
7055 message: "Glidera transaction"
7056 });
7057 var txp = {
7058 toAddress: sellAddress,
7059 amount: amount,
7060 outputs: outputs,
7061 message: "Glidera transaction",
7062 payProUrl: null,
7063 excludeUnconfirmedUtxos: !configWallet.spendUnconfirmed,
7064 feeLevel: walletSettings.feeLevel || "normal",
7065 customData: {
7066 glideraToken: token
7067 }
7068 };
7069 walletService.createTx(client, txp, function(err, createdTxp) {
7070 if (ongoingProcess.clear(), err) return void(self.error = err.message || bwcError.msg(err));
7071 $scope.$emit("Local/NeedsConfirmation", createdTxp, function(accept) {
7072 accept ? fingerprintService.check(client, function(err) {
7073 if (err) return void(self.error = err.message || bwcError.msg(err));
7074 handleEncryptedWallet(client, function(err) {
7075 if (err) return void(self.error = err.message || bwcError.msg(err));
7076 ongoingProcess.set("signingTx", !0), walletService.publishTx(client, createdTxp, function(err, publishedTxp) {
7077 err && (ongoingProcess.clear(), self.error = err.message || bwcError.msg(err)), walletService.signTx(client, publishedTxp, function(err, signedTxp) {
7078 if (walletService.lock(client), walletService.removeTx(client, signedTxp, function(err) {
7079 err && $log.debug(err)
7080 }), ongoingProcess.clear(), err) return void(self.error = err.message || bwcError.msg(err));
7081 var rawTx = signedTxp.raw,
7082 data = {
7083 refundAddress: refundAddress,
7084 signedTransaction: rawTx,
7085 priceUuid: self.sellPrice.priceUuid,
7086 useCurrentPrice: !self.sellPrice.priceUuid,
7087 ip: null
7088 };
7089 ongoingProcess.set("Seling Bitcoin", !0), glideraService.sell(token, twoFaCode, data, function(err, data) {
7090 if (ongoingProcess.clear(), err) return self.error = err.message || bwcError.msg(err), void $timeout(function() {
7091 $scope.$emit("Local/GlideraError")
7092 }, 100);
7093 self.success = data, $scope.$emit("Local/GlideraTx")
7094 })
7095 })
7096 })
7097 })
7098 }) : go.path("glidera")
7099 })
7100 })
7101 })
7102 })
7103 }
7104 }), angular.module("copayApp.controllers").controller("sidebarController", function($rootScope, $timeout, $ionicScrollDelegate, lodash, profileService, configService, go, platformInfo) {
7105 var self = this;
7106 self.isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova, self.walletSelection = !1, $rootScope.$on("Local/WalletListUpdated", function(event) {
7107 self.walletSelection = !1, self.setWallets()
7108 }), $rootScope.$on("Local/ColorUpdated", function(event) {
7109 self.setWallets()
7110 }), $rootScope.$on("Local/AliasUpdated", function(event) {
7111 self.setWallets()
7112 }), self.signout = function() {
7113 profileService.signout()
7114 }, self.switchWallet = function(selectedWalletId, currentWalletId) {
7115 var client = profileService.focusedClient;
7116 selectedWalletId == currentWalletId && client.isComplete() || (self.walletSelection = !1, profileService.setAndStoreFocus(selectedWalletId, function() {}), $ionicScrollDelegate.scrollTop())
7117 }, self.toggleWalletSelection = function() {
7118 self.walletSelection = !self.walletSelection, self.walletSelection && self.setWallets()
7119 }, self.setWallets = function() {
7120 if (profileService.profile) {
7121 var config = configService.getSync();
7122 config.colorFor = config.colorFor || {}, config.aliasFor = config.aliasFor || {};
7123 var credentials = lodash.filter(profileService.profile.credentials, "walletName"),
7124 ret = lodash.map(credentials, function(c) {
7125 return {
7126 m: c.m,
7127 n: c.n,
7128 name: config.aliasFor[c.walletId] || c.walletName,
7129 id: c.walletId,
7130 color: config.colorFor[c.walletId] || "#4A90E2"
7131 }
7132 });
7133 self.wallets = lodash.sortBy(ret, "name")
7134 }
7135 }, self.setWallets()
7136 }), angular.module("copayApp.controllers").controller("termOfUseController", function($scope, uxLanguage) {
7137 $scope.lang = uxLanguage.currentLanguage
7138 }), angular.module("copayApp.controllers").controller("topbarController", function(go) {
7139 this.goHome = function() {
7140 go.walletHome()
7141 }, this.goPreferences = function() {
7142 go.preferences()
7143 }
7144 }), angular.module("copayApp.controllers").controller("uriController", function($stateParams, $log, openURLService) {
7145 $log.info("DEEP LINK from Browser:" + $stateParams.url), openURLService.handleURL({
7146 url: $stateParams.url
7147 })
7148 }), angular.module("copayApp.controllers").controller("versionController", function() {
7149 this.version = window.version, this.commitHash = window.commitHash
7150 }), angular.module("copayApp.controllers").controller("walletHomeController", function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, notification, txStatus, profileService, lodash, configService, rateService, storageService, bitcore, gettext, gettextCatalog, platformInfo, addressService, ledger, bwcError, confirmDialog, txFormatService, addressbookService, go, feeService, walletService, fingerprintService, nodeWebkit, ongoingProcess) {
7151 function _paymentTimeControl(expirationTime) {
7152 function setExpirationTime() {
7153 var now = Math.floor(Date.now() / 1e3);
7154 if (now > expirationTime) return void setExpiredValues();
7155 var totalSecs = expirationTime - now,
7156 m = Math.floor(totalSecs / 60),
7157 s = totalSecs % 60;
7158 self.remainingTimeStr = ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2)
7159 }
7160
7161 function setExpiredValues() {
7162 self.paymentExpired = !0, self.remainingTimeStr = null, self._paypro = null, self.error = gettext("Cannot sign: The payment request has expired"), self.countDown && $interval.cancel(self.countDown)
7163 }
7164 self.paymentExpired = !1, setExpirationTime(), self.countDown = $interval(function() {
7165 setExpirationTime()
7166 }, 1e3)
7167 }
7168 var isCordova = platformInfo.isCordova,
7169 isChromeApp = (platformInfo.isWP, platformInfo.isAndroid, platformInfo.isChromeApp),
7170 self = this;
7171 $rootScope.shouldHideMenuBar = !1, $rootScope.wpInputFocused = !1;
7172 var config = configService.getSync(),
7173 configWallet = config.wallet,
7174 walletSettings = configWallet.settings,
7175 ret = {};
7176 ret.unitToSatoshi = walletSettings.unitToSatoshi, ret.satToUnit = 1 / ret.unitToSatoshi, ret.unitName = walletSettings.unitName, ret.alternativeIsoCode = walletSettings.alternativeIsoCode, ret.alternativeName = walletSettings.alternativeName, ret.alternativeAmount = 0, ret.unitDecimals = walletSettings.unitDecimals, ret.isCordova = isCordova, ret.addresses = [], ret.isMobile = platformInfo.isMobile, ret.isWindowsPhoneApp = platformInfo.isWP, ret.countDown = null, ret.sendMaxInfo = {}, ret.showAlternative = !1, ret.fromInputAmount = null;
7177 var vanillaScope = ret,
7178 disableScannerListener = $rootScope.$on("dataScanned", function(event, data) {
7179 if (data) {
7180 self.setForm(data), $rootScope.$emit("Local/SetTab", "send");
7181 $scope.sendForm.address.$invalid && !ongoingProcess.get("fetchingPayPro") && (self.resetForm(), self.error = gettext("Could not recognize a valid Bitcoin QR Code"))
7182 }
7183 }),
7184 disablePaymentUriListener = $rootScope.$on("paymentUri", function(event, uri) {
7185 $rootScope.$emit("Local/SetTab", "send"), $timeout(function() {
7186 self.setForm(uri)
7187 }, 100)
7188 }),
7189 disableAddrListener = $rootScope.$on("Local/AddressIsUsed", function() {
7190 self.setAddress(!0)
7191 }),
7192 disableFocusListener = $rootScope.$on("Local/NewFocusedWalletReady", function() {
7193 self.addr = null, self.resetForm(), $scope.search = "", profileService.focusedClient && profileService.focusedClient.isComplete() && (self.setAddress(), self.setSendFormInputs()), $log.debug("Cleaning WalletHome Instance"), lodash.each(self, function(v, k) {
7194 if (!lodash.isFunction(v)) return lodash.isUndefined(vanillaScope[k]) ? void("isRateAvailable" != k && delete self[k]) : void(self[k] = vanillaScope[k])
7195 })
7196 }),
7197 disableResumeListener = $rootScope.$on("Local/Resume", function() {
7198 self.bindTouchDown()
7199 }),
7200 disableTabListener = $rootScope.$on("Local/TabChanged", function(e, tab) {
7201 switch (tab) {
7202 case "receive":
7203 self.setAddress();
7204 break;
7205 case "send":
7206 self.resetError()
7207 }
7208 });
7209 if ($scope.$on("$destroy", function() {
7210 disableAddrListener(), disableScannerListener(), disablePaymentUriListener(), disableTabListener(), disableFocusListener(), disableResumeListener(), $rootScope.shouldHideMenuBar = !1
7211 }), isCordova && StatusBar.isVisible) {
7212 var backgroundColor = profileService.focusedClient ? profileService.focusedClient.backgroundColor : "#4B6178";
7213 StatusBar.backgroundColorByHexString(backgroundColor)
7214 }
7215 this.onQrCodeScanned = function(data) {
7216 data && go.send(), $rootScope.$emit("dataScanned", data)
7217 }, rateService.whenAvailable(function() {
7218 self.isRateAvailable = !0, $rootScope.$digest()
7219 });
7220 var getClipboard = function(cb) {
7221 if (!isCordova || platformInfo.isWP) return cb();
7222 window.cordova.plugins.clipboard.paste(function(value) {
7223 var fc = profileService.focusedClient,
7224 Address = bitcore.Address,
7225 networkName = fc.credentials.network;
7226 if (Address.isValid(value, networkName) && !$scope.newAddress) return cb(value)
7227 })
7228 },
7229 handleEncryptedWallet = function(client, cb) {
7230 if (!walletService.isEncrypted(client)) return cb();
7231 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
7232 return cb(err ? err : walletService.unlock(client, password))
7233 })
7234 };
7235 gettextCatalog.getString("Accept"), gettextCatalog.getString("Cancel"), gettextCatalog.getString("Confirm");
7236 this.openAddressbookModal = function(wallets, address) {
7237 $scope.wallets = wallets, $scope.address = address, $scope.self = self, $ionicModal.fromTemplateUrl("views/modals/addressbook.html", {
7238 scope: $scope
7239 }).then(function(modal) {
7240 $scope.addressbookModal = modal, $scope.addressbookModal.show()
7241 })
7242 };
7243 this.openTxpModal = function(tx, copayers, isGlidera) {
7244 $scope.self = self, $scope.tx = tx, $scope.copayers = copayers, $scope.isGlidera = isGlidera, $scope.error = null, $scope.loading = null, $scope.paymentExpired = null, $scope.currentSpendUnconfirmed = configWallet.spendUnconfirmed, $ionicModal.fromTemplateUrl("views/modals/txp-details.html", {
7245 scope: $scope
7246 }).then(function(modal) {
7247 $scope.txpDetailsModal = modal, $scope.txpDetailsModal.show()
7248 })
7249 }, this.setAddress = function(forceNew) {
7250 self.addrError = null;
7251 var client = profileService.focusedClient;
7252 client && client.isComplete() && (!forceNew && self.addr || (self.generatingAddress = !0, $timeout(function() {
7253 addressService.getAddress(client.credentials.walletId, forceNew, function(err, addr) {
7254 self.generatingAddress = !1, err ? self.addrError = err : addr && (self.addr = addr), $scope.$digest()
7255 })
7256 })))
7257 }, this.copyToClipboard = function(addr, $event) {
7258 isCordova ? (window.cordova.plugins.clipboard.copy(addr), window.plugins.toast.showShortCenter(gettextCatalog.getString("Copied to clipboard"))) : platformInfo.isNW && (nodeWebkit.writeToClipboard(addr), function() {
7259 $ionicPopover.fromTemplateUrl("views/includes/copyToClipboard.html", {
7260 scope: $scope
7261 }).then(function(popover) {
7262 $scope.popover = popover, $scope.popover.show($event)
7263 }), $scope.close = function() {
7264 $scope.popover.hide()
7265 }, $timeout(function() {
7266 $scope.popover.hide()
7267 }, 700), $scope.$on("$destroy", function() {
7268 $scope.popover.remove()
7269 })
7270 }())
7271 }, this.shareAddress = function(addr) {
7272 isCordova && window.plugins.socialsharing.share("neblio:" + addr, null, null, null)
7273 }, this.resetError = function() {
7274 this.error = this.success = null
7275 }, this.bindTouchDown = function(tries) {
7276 var self = this;
7277 if (!((tries = tries || 0) > 5)) {
7278 if (!document.getElementById("menu-walletHome")) return $timeout(function() {
7279 self.bindTouchDown(++tries)
7280 }, 500);
7281 $log.debug("Binding touchstart elements..."), ["hamburger", "menu-walletHome", "menu-send", "menu-receive"].forEach(function(id) {
7282 var e = document.getElementById(id);
7283 e && e.addEventListener("touchstart", function() {
7284 try {
7285 event.preventDefault()
7286 } catch (e) {}
7287 angular.element(e).triggerHandler("click")
7288 }, !0)
7289 })
7290 }
7291 }, this.hideMenuBar = lodash.debounce(function(hide) {
7292 $rootScope.shouldHideMenuBar = !!hide, $rootScope.$digest()
7293 }, 100), this.formFocus = function(what) {
7294 isCordova && this.isWindowsPhoneApp && this.hideMenuBar(what);
7295 var self = this;
7296 isCordova && !this.isWindowsPhoneApp && "address" == what && getClipboard(function(value) {
7297 value && (document.getElementById("amount").focus(), $timeout(function() {
7298 window.plugins.toast.showShortCenter(gettextCatalog.getString("Pasted from clipboard")), self.setForm(value)
7299 }, 100))
7300 })
7301 }, this.setSendFormInputs = function() {
7302 var unitToSat = this.unitToSatoshi,
7303 satToUnit = 1 / unitToSat;
7304 Object.defineProperty($scope, "_alternative", {
7305 get: function() {
7306 return $scope.__alternative
7307 },
7308 set: function(newValue) {
7309 $scope.__alternative = newValue, self.isRateAvailable ? $scope._amount = parseFloat((rateService.fromFiat(newValue, self.alternativeIsoCode) * satToUnit).toFixed(self.unitDecimals), 10) : $scope.__amount = null
7310 },
7311 enumerable: !0,
7312 configurable: !0
7313 }), Object.defineProperty($scope, "_amount", {
7314 get: function() {
7315 return $scope.__amount
7316 },
7317 set: function(newValue) {
7318 $scope.__amount = newValue, self.isRateAvailable ? $scope.__alternative = parseFloat(rateService.toFiat(newValue * self.unitToSatoshi, self.alternativeIsoCode).toFixed(2), 10) : $scope.__alternative = null, self.alternativeAmount = $scope.__alternative, self.resetError()
7319 },
7320 enumerable: !0,
7321 configurable: !0
7322 }), Object.defineProperty($scope, "_address", {
7323 get: function() {
7324 return $scope.__address
7325 },
7326 set: function(newValue) {
7327 $scope.__address = self.onAddressChange(newValue), $scope.sendForm && $scope.sendForm.address.$valid && (self.lockAddress = !0)
7328 },
7329 enumerable: !0,
7330 configurable: !0
7331 });
7332 var fc = profileService.focusedClient;
7333 this.hideNote = !fc.credentials.sharedEncryptingKey
7334 }, this.setSendError = function(err) {
7335 var fc = profileService.focusedClient,
7336 prefix = fc.credentials.m > 1 ? gettextCatalog.getString("Could not create payment proposal") : gettextCatalog.getString("Could not send payment");
7337 this.error = bwcError.msg(err, prefix), $timeout(function() {
7338 $scope.$digest()
7339 }, 1)
7340 }, this.setAmount = function(amount, useAlternativeAmount) {
7341 $scope.showAlternative = !1, self.fromInputAmount = !0, self.setForm(null, amount, null)
7342 }, this.submitForm = function() {
7343 if ($scope._amount && $scope._address) {
7344 var client = profileService.focusedClient,
7345 unitToSat = this.unitToSatoshi,
7346 outputs = (configWallet.spendUnconfirmed, []);
7347 this.resetError(), isCordova && this.isWindowsPhoneApp && ($rootScope.shouldHideMenuBar = !0);
7348 var form = $scope.sendForm,
7349 comment = form.comment.$modelValue;
7350 if (comment && !client.credentials.sharedEncryptingKey) {
7351 var msg = "Could not add message to imported wallet without shared encrypting key";
7352 return $log.warn(msg), self.setSendError(gettext(msg))
7353 }
7354 if (form.amount.$modelValue * unitToSat > Number.MAX_SAFE_INTEGER) {
7355 var msg = "Amount too big";
7356 return $log.warn(msg), self.setSendError(gettext(msg))
7357 }
7358 $timeout(function() {
7359 var address, amount, paypro = self._paypro;
7360 address = form.address.$modelValue, amount = parseInt((form.amount.$modelValue * unitToSat).toFixed(0)), outputs.push({
7361 toAddress: address,
7362 amount: amount,
7363 message: comment
7364 });
7365 var txp = {};
7366 lodash.isEmpty(self.sendMaxInfo) ? txp.amount = amount : (txp.sendMax = !0, txp.inputs = self.sendMaxInfo.inputs), txp.toAddress = address, txp.outputs = outputs, txp.message = comment, txp.payProUrl = paypro ? paypro.url : null, txp.excludeUnconfirmedUtxos = !configWallet.spendUnconfirmed, txp.feePerKb = 1e4, ongoingProcess.set("creatingTx", !0), walletService.createTx(client, txp, function(err, createdTxp) {
7367 if (ongoingProcess.set("creatingTx", !1), err) return self.setSendError(err);
7368 client.canSign() || client.isPrivKeyExternal() ? $rootScope.$emit("Local/NeedsConfirmation", createdTxp, function(accept) {
7369 accept ? self.confirmTx(createdTxp) : self.resetForm()
7370 }) : ($log.info("No signing proposal: No private key"), ongoingProcess.set("sendingTx", !0), walletService.publishTx(client, createdTxp, function(err, publishedTxp) {
7371 if (ongoingProcess.set("sendingTx", !1), err) return self.setSendError(err);
7372 self.resetForm(), go.walletHome();
7373 var type = txStatus.notify(createdTxp);
7374 $scope.openStatusModal(type, createdTxp, function() {
7375 return $scope.$emit("Local/TxProposalAction")
7376 })
7377 }))
7378 })
7379 }, 100)
7380 }
7381 }, this.confirmTx = function(txp) {
7382 var client = profileService.focusedClient,
7383 self = this;
7384 fingerprintService.check(client, function(err) {
7385 if (err) return self.setSendError(err);
7386 handleEncryptedWallet(client, function(err) {
7387 if (err) return self.setSendError(err);
7388 ongoingProcess.set("sendingTx", !0), walletService.publishTx(client, txp, function(err, publishedTxp) {
7389 if (ongoingProcess.set("sendingTx", !1), err) return self.setSendError(err);
7390 ongoingProcess.set("signingTx", !0), walletService.signTx(client, publishedTxp, function(err, signedTxp) {
7391 if (ongoingProcess.set("signingTx", !1), walletService.lock(client), err) return $scope.$emit("Local/TxProposalAction"), self.setSendError(err.message ? err.message : gettext("The payment was created but could not be completed. Please try again from home screen"));
7392 if ("accepted" == signedTxp.status) ongoingProcess.set("broadcastingTx", !0), walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) {
7393 if (ongoingProcess.set("broadcastingTx", !1), err) return self.setSendError(err);
7394 self.resetForm(), go.walletHome();
7395 var type = txStatus.notify(broadcastedTxp);
7396 $scope.openStatusModal(type, broadcastedTxp, function() {
7397 $scope.$emit("Local/TxProposalAction", "broadcasted" == broadcastedTxp.status)
7398 })
7399 });
7400 else {
7401 self.resetForm(), go.walletHome();
7402 var type = txStatus.notify(signedTxp);
7403 $scope.openStatusModal(type, signedTxp, function() {
7404 $scope.$emit("Local/TxProposalAction")
7405 })
7406 }
7407 })
7408 })
7409 })
7410 })
7411 }, $scope.openStatusModal = function(type, txp, cb) {
7412 var fc = profileService.focusedClient;
7413 $scope.type = type, $scope.tx = txFormatService.processTx(txp), $scope.color = fc.backgroundColor, $scope.cb = cb, $ionicModal.fromTemplateUrl("views/modals/tx-status.html", {
7414 scope: $scope,
7415 animation: "slide-in-up"
7416 }).then(function(modal) {
7417 $scope.txStatusModal = modal, $scope.txStatusModal.show()
7418 })
7419 }, $scope.openSearchModal = function() {
7420 var fc = profileService.focusedClient;
7421 $scope.color = fc.backgroundColor, $scope.self = self, $ionicModal.fromTemplateUrl("views/modals/search.html", {
7422 scope: $scope,
7423 focusFirstInput: !0
7424 }).then(function(modal) {
7425 $scope.searchModal = modal, $scope.searchModal.show()
7426 })
7427 }, $scope.openCustomInputAmountModal = function(addr) {
7428 var fc = profileService.focusedClient;
7429 $scope.color = fc.backgroundColor, $scope.self = self, $scope.addr = addr, $ionicModal.fromTemplateUrl("views/modals/customAmount.html", {
7430 scope: $scope
7431 }).then(function(modal) {
7432 $scope.customAmountModal = modal, $scope.customAmountModal.show()
7433 })
7434 }, $scope.openAmountModal = function(addr) {
7435 isCordova ? $scope.openInputAmountModal(addr) : $scope.openCustomInputAmountModal(addr)
7436 }, $scope.openInputAmountModal = function(addr) {
7437 var fc = profileService.focusedClient;
7438 $scope.color = fc.backgroundColor, $scope.showAlternativeAmount = null, $scope.showAlternativeAmount ? $scope.amount = $scope.sendForm.alternative.$viewValue || null : $scope.amount = $scope.sendForm.amount.$viewValue || null, $scope.self = self, $scope.addr = addr, $ionicModal.fromTemplateUrl("views/modals/inputAmount.html", {
7439 scope: $scope
7440 }).then(function(modal) {
7441 $scope.inputAmountModal = modal, $scope.inputAmountModal.show()
7442 })
7443 }, this.setForm = function(to, amount, comment) {
7444 var form = $scope.sendForm;
7445 to && (form.address.$setViewValue(to), form.address.$isValid = !0, form.address.$render(), this.lockAddress = !0), amount && (form.amount.$setViewValue("" + amount), form.amount.$isValid = !0, form.amount.$render(), this.fromInputAmount || (this.lockAmount = !0), this.fromInputAmount = !1), comment && (form.comment.$setViewValue(comment), form.comment.$isValid = !0, form.comment.$render())
7446 }, this.resetForm = function() {
7447 this.resetError(), this.sendMaxInfo = {}, this.countDown && $interval.cancel(this.countDown), this._paypro = null, this.lockAddress = !1, this.lockAmount = !1, this._amount = this._address = null;
7448 var form = $scope.sendForm;
7449 form && form.amount && (form.amount.$pristine = !0, form.amount.$setViewValue(""), form.amount.$render(), form.comment.$setViewValue(""), form.comment.$render(), form.$setPristine(), form.address && (form.address.$pristine = !0, form.address.$setViewValue(""), form.address.$render())), $timeout(function() {
7450 $rootScope.$digest()
7451 }, 1)
7452 }, this.openPPModal = function(paypro) {
7453 var fc = profileService.focusedClient;
7454 $scope.color = fc.backgroundColor, $scope.self = self, $scope.paypro = paypro, $ionicModal.fromTemplateUrl("views/modals/paypro.html", {
7455 scope: $scope
7456 }).then(function(modal) {
7457 $scope.payproModal = modal, $scope.payproModal.show()
7458 })
7459 }, this.setFromPayPro = function(uri, cb) {
7460 cb || (cb = function() {});
7461 var fc = profileService.focusedClient;
7462 if (isChromeApp) return this.error = gettext("Payment Protocol not supported on Chrome App"), cb(!0);
7463 var satToUnit = 1 / this.unitToSatoshi,
7464 self = this;
7465 ongoingProcess.set("fetchingPayPro", !0), $log.debug("Fetch PayPro Request...", uri), $timeout(function() {
7466 fc.fetchPayPro({
7467 payProUrl: uri
7468 }, function(err, paypro) {
7469 if (ongoingProcess.set("fetchingPayPro", !1), err) {
7470 $log.warn("Could not fetch payment request:", err), self.resetForm();
7471 var msg = err.toString();
7472 return msg.match("HTTP") && (msg = gettext("Could not fetch payment information")), self.error = msg, $timeout(function() {
7473 $rootScope.$digest()
7474 }, 1), cb(!0)
7475 }
7476 return paypro.verified ? (self._paypro = paypro, self.setForm(paypro.toAddress, (paypro.amount * satToUnit).toFixed(self.unitDecimals), paypro.memo), _paymentTimeControl(paypro.expires), cb()) : (self.resetForm(), $log.warn("Failed to verify payment protocol signatures"), self.error = gettext("Payment Protocol Invalid"), $timeout(function() {
7477 $rootScope.$digest()
7478 }, 1), cb(!0))
7479 })
7480 }, 1)
7481 }, this.setFromUri = function(uri) {
7482 var self = this,
7483 satToUnit = 1 / this.unitToSatoshi;
7484 if (/^neblio:\?r=[\w+]/.exec(uri)) uri = decodeURIComponent(uri.replace("neblio:?r=", "")), this.setFromPayPro(uri, function(err) {
7485 if (err) return err
7486 });
7487 else {
7488 if (uri = function(uri) {
7489 var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i,
7490 match = regex.exec(uri);
7491 if (!match || 0 === match.length) return uri;
7492 var value = match[0].replace(",", ".");
7493 return uri.replace(regex, value)
7494 }(uri), !bitcore.URI.isValid(uri)) return uri;
7495 var parsed = new bitcore.URI(uri),
7496 addr = parsed.address ? parsed.address.toString() : "",
7497 message = parsed.message,
7498 amount = parsed.amount ? (parsed.amount.toFixed(0) * satToUnit).toFixed(this.unitDecimals) : 0;
7499 if (!parsed.r) return this.setForm(addr, amount, message), addr;
7500 this.setFromPayPro(parsed.r, function(err) {
7501 if (err && addr && amount) return self.setForm(addr, amount, message), addr
7502 })
7503 }
7504 }, this.onAddressChange = function(value) {
7505 return this.resetError(), value ? this._paypro ? value : 0 === value.indexOf("neblio:") ? this.setFromUri(value) : /^https?:\/\//.test(value) ? this.setFromPayPro(value) : value : ""
7506 }, this.getUnitName = function() {
7507 return this.unitName
7508 }, this.getAlternativeIsoCode = function() {
7509 return this.alternativeIsoCode
7510 }, this.openTxModal = function(btx) {
7511 var self = this;
7512 console.log(btx), $scope.btx = lodash.cloneDeep(btx), $scope.self = self, $ionicModal.fromTemplateUrl("views/modals/tx-details.html", {
7513 scope: $scope,
7514 hideDelay: 500
7515 }).then(function(modal) {
7516 $scope.txDetailsModal = modal, $scope.txDetailsModal.show()
7517 })
7518 }, this.hasAction = function(actions, action) {
7519 return actions.hasOwnProperty("create")
7520 }, this.sendMax = function(availableBalanceSat) {
7521 if (0 == availableBalanceSat) return void(this.error = gettext("Cannot create transaction. Insufficient funds"));
7522 var self = this,
7523 fc = profileService.focusedClient;
7524 this.error = null, ongoingProcess.set("calculatingFee", !0), $timeout(function() {
7525 ongoingProcess.set("calculatingFee", !1);
7526 var opts = {};
7527 opts.feePerKb = 1e4, opts.returnInputs = !0;
7528 var config = configService.getSync();
7529 opts.excludeUnconfirmedUtxos = !config.wallet.spendUnconfirmed, ongoingProcess.set("retrivingInputs", !0), fc.getSendMaxInfo(opts, function(err, resp) {
7530 if (ongoingProcess.set("retrivingInputs", !1), err) return self.error = err, void $scope.$apply();
7531 if (0 == resp.amount) return self.error = gettext("Not enough funds for fee"), void $scope.$apply();
7532 var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees", {
7533 fee: profileService.formatAmount(resp.fee) + " " + self.unitName
7534 }),
7535 warningMsg = function() {
7536 var warningMsg = [];
7537 return resp.utxosBelowFee > 0 && warningMsg.push(gettextCatalog.getString("Note: a total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", {
7538 amountBelowFeeStr: profileService.formatAmount(resp.amountBelowFee) + " " + self.unitName
7539 })), resp.utxosAboveMaxSize > 0 && warningMsg.push(gettextCatalog.getString("Note: a total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded", {
7540 amountAboveMaxSizeStr: profileService.formatAmount(resp.amountAboveMaxSize) + " " + self.unitName
7541 })), warningMsg.join("\n")
7542 }();
7543 lodash.isEmpty(warningMsg) || (msg += ". \n" + warningMsg), confirmDialog.show(msg, function(confirmed) {
7544 if (confirmed) {
7545 self.sendMaxInfo = resp;
7546 var amount = parseFloat((resp.amount * self.satToUnit).toFixed(self.unitDecimals));
7547 self.setForm(null, amount, null)
7548 } else self.resetForm()
7549 })
7550 })
7551 }, 10)
7552 }, lodash.assign(self, vanillaScope), this.bindTouchDown(), profileService.focusedClient && (this.setAddress(), this.setSendFormInputs())
7553 }), angular.module("copayApp").run(["gettextCatalog", function(gettextCatalog) {
7554 gettextCatalog.setStrings("en", {
7555 "This requires some Bitcoins for the miner fee": "This requires some NEBL for the staking fee",
7556 "(possible double spend)": "(possible double spend)",
7557 "(Trusted)": "(Trusted)",
7558 "{{fee}} will be deducted for bitcoin networking fees": "{{fee}} will be deducted for neblio networking fees",
7559 "{{index.m}}-of-{{index.n}}": "{{index.m}}-of-{{index.n}}",
7560 "{{item.m}}-of-{{item.n}}": "{{item.m}}-of-{{item.n}}",
7561 "{{len}} wallets imported. Funds scanning in progress. Hold on to see updated balance": "{{len}} wallets imported. Funds scanning in progress. Hold on to see updated balance",
7562 "* A payment proposal can be deleted if 1) you are the creator, and no other copayer has signed, or 2) 24 hours have passed since the proposal was created.": "* A payment proposal can be deleted if 1) you are the creator, and no other copayer has signed, or 2) 24 hours have passed since the proposal was created.",
7563 "<b>IF YOU LOSE ACCESS TO YOUR COPAY WALLET OR YOUR ENCRYPTED PRIVATE KEYS AND YOU HAVE NOT SEPARATELY STORED A BACKUP OF YOUR WALLET AND CORRESPONDING PASSWORD, YOU ACKNOWLEDGE AND AGREE THAT ANY BITCOIN YOU HAVE ASSOCIATED WITH THAT COPAY WALLET WILL BECOME INACCESSIBLE.</b>": "<b>IF YOU LOSE ACCESS TO YOUR ORION WALLET OR YOUR ENCRYPTED PRIVATE KEYS AND YOU HAVE NOT SEPARATELY STORED A BACKUP OF YOUR WALLET AND CORRESPONDING PASSWORD, YOU ACKNOWLEDGE AND AGREE THAT ANY NEBLIO YOU HAVE ASSOCIATED WITH THAT ORION WALLET WILL BECOME INACCESSIBLE.</b>",
7564 "<b>OR</b> 1 wallet export file and the remaining quorum of wallet seeds (e.g. in a 3-5 wallet: 1 wallet export file + 2 wallet seeds of any of the other copayers).": "<b>OR</b> 1 wallet export file and the remaining quorum of wallet seeds (e.g. in a 3-5 wallet: 1 wallet export file + 2 wallet seeds of any of the other copayers).",
7565 "<b>OR</b> the wallet seed of <b>all</b> copayers in the wallet": "<b>OR</b> the wallet seed of <b>all</b> copayers in the wallet",
7566 "<b>OR</b> the wallet seeds of <b>all</b> copayers in the wallet": "<b>OR</b> the wallet seeds of <b>all</b> copayers in the wallet",
7567 "A multisignature bitcoin wallet": "A multisignature neblio wallet",
7568 "About Copay": "About Orion",
7569 Accept: "Accept",
7570 "Add a Seed Passphrase": "Add a Seed Passphrase",
7571 "Add an optional passphrase to secure the seed": "Add an optional passphrase to secure the seed",
7572 "Add wallet": "Add wallet",
7573 Address: "Address",
7574 "Address Type": "Address Type",
7575 Advanced: "Advanced",
7576 "Advanced Send": "Advanced Send",
7577 Agree: "Agree",
7578 "Alias for <i>{{index.walletName}}</i>": "Alias for <i>{{index.walletName}}</i>",
7579 "All contributions to Copay's translation are welcome. Sign up at crowdin.com and join the Copay project at": "All contributions to Copay's translation are welcome. Sign up at crowdin.com and join the Copay project at",
7580 "All transaction requests are irreversible.": "All transaction requests are irreversible.",
7581 "Already have a wallet?": "Already have a wallet?",
7582 "Alternative Currency": "Alternative Currency",
7583 Amount: "Amount",
7584 "Amount below dust threshold": "Amount below dust threshold",
7585 "Amount in": "Amount in",
7586 "Applying changes": "Applying changes",
7587 "Are you sure you want to delete the backup words?": "Are you sure you want to delete the backup words?",
7588 "Are you sure you want to delete this wallet?": "Are you sure you want to delete this wallet?",
7589 "Available Balance": "Available Balance",
7590 "Average confirmation time: {{fee.nbBlocks * 10}} minutes": "Average confirmation time: {{fee.nbBlocks * 10}} minuta",
7591 Back: "Back",
7592 Backup: "Backup",
7593 "Backup now": "Backup now",
7594 "Backup words deleted": "Backup words deleted",
7595 "Bad wallet invitation": "Bad wallet invitation",
7596 "Balance By Address": "NEBL Balance By Address",
7597 "Before receiving funds, it is highly recommended you backup your wallet keys.": "Before receiving funds, it is highly recommended you backup your wallet keys.",
7598 "Bitcoin address": "Neblio address",
7599 "Bitcoin Network Fee Policy": "Neblio Network Fee Policy",
7600 "Bitcoin transactions may include a fee collected by miners on the network. The higher the fee, the greater the incentive a miner has to include that transaction in a block. Actual fees are determined based on network load and the selected policy.": "Neblio transactions may include a fee collected by miners on the network. The higher the fee, the greater the incentive a miner has to include that transaction in a block. Actual fees are determined based on network load and the selected policy.",
7601 "Bitcoin URI is NOT valid!": "Neblio URI is NOT valid!",
7602 "Broadcast Payment": "Broadcast Payment",
7603 "Broadcasting Payment": "Broadcasting Payment",
7604 "Broadcasting transaction": "Broadcasting transaction",
7605 "Browser unsupported": "Browser unsupported",
7606 Cancel: "Cancel",
7607 CANCEL: "CANCEL",
7608 "Cannot join the same wallet more that once": "Cannot join the same wallet more that once",
7609 "Certified by": "Certified by",
7610 "Changing wallet alias only affects the local wallet name.": "Changing wallet alias only affects the local wallet name.",
7611 "Choose a backup file from your computer": "Choose a backup file from your computer",
7612 "Choose a wallet to send funds": "Choose a wallet to send funds",
7613 Close: "Close",
7614 Color: "Color",
7615 "Commit hash": "Commit hash",
7616 Confirm: "Confirm",
7617 Confirmations: "Confirmations",
7618 "Connecting to {{create.hwWallet}} Wallet...": "Connecting to {{create.hwWallet}} Wallet...",
7619 "Connecting to {{import.hwWallet}} Wallet...": "Connecting to {{import.hwWallet}} Wallet...",
7620 "Connecting to {{join.hwWallet}} Wallet...": "Connecting to {{join.hwWallet}} Wallet...",
7621 "Copayer already in this wallet": "Copayer already in this wallet",
7622 "Copayer already voted on this spend proposal": "Copayer already voted on this spend proposal",
7623 "Copayer data mismatch": "Copayer data mismatch",
7624 Copayers: "Copayers",
7625 "Copied to clipboard": "Copied to clipboard",
7626 "Copy this text as it is to a safe place (notepad or email)": "Copy this text as it is to a safe place (notepad or email)",
7627 "Copy to clipboard": "Copy to clipboard",
7628 "Could not accept payment": "Could not accept payment",
7629 "Could not access Wallet Service: Not found": "Could not access Wallet Service: Not found",
7630 "Could not broadcast payment": "Could not broadcast payment",
7631 "Could not create address": "Could not create address",
7632 "Could not create payment proposal": "Could not create payment proposal",
7633 "Could not create using the specified extended private key": "Could not create using the specified extended private key",
7634 "Could not create using the specified extended public key": "Could not create using the specified extended public key",
7635 "Could not create: Invalid wallet seed": "Could not create: Invalid wallet seed",
7636 "Could not decrypt": "Could not decrypt",
7637 "Could not decrypt file, check your password": "Could not decrypt file, check your password",
7638 "Could not delete payment proposal": "Could not delete payment proposal",
7639 "Could not fetch payment information": "Could not fetch payment information",
7640 "Could not fetch transaction history": "Could not fetch transaction history",
7641 "Could not import": "Could not import",
7642 "Could not import. Check input file and password": "Could not import. Check input file and password",
7643 "Could not join wallet": "Could not join wallet",
7644 "Could not recognize a valid Bitcoin QR Code": "Could not recognize a valid Neblio QR Code",
7645 "Could not reject payment": "Could not reject payment",
7646 "Could not send payment": "Could not send payment",
7647 "Could not update Wallet": "Could not update Wallet",
7648 Create: "Create",
7649 "Create {{requiredCopayers}}-of-{{totalCopayers}} wallet": "Create {{requiredCopayers}}-of-{{totalCopayers}} wallet",
7650 "Create new wallet": "Create new wallet",
7651 "Create, join or import": "Create, join or import",
7652 "Created by": "Created by",
7653 "Creating Profile...": "Creating Profile...",
7654 "Creating transaction": "Creating transaction",
7655 "Creating Wallet...": "Creating Wallet...",
7656 "Current fee rate for this policy: {{fee.feePerKBUnit}}/kiB": "Current fee rate for this policy: {{fee.feePerKBUnit}}/kiB",
7657 Date: "Date",
7658 "Decrypting a paper wallet could take around 5 minutes on this device. please be patient and keep the app open.": "Decrypting a paper wallet could take around 5 minutes on this device. please be patient and keep the app open.",
7659 "Delete it and create a new one": "Delete it and create a new one",
7660 "Delete Payment Proposal": "Delete Payment Proposal",
7661 "Delete wallet": "Delete wallet",
7662 "Delete Wallet": "Delete Wallet",
7663 "DELETE WORDS": "DELETE WORDS",
7664 "Deleting payment": "Deleting payment",
7665 "Derivation Strategy": "Derivation Strategy",
7666 Details: "Details",
7667 Disabled: "Disabled",
7668 "Do not include private key": "Do not include private key",
7669 "Don't see your language on Crowdin? Contact the Owner on Crowdin! We'd love to support your language.": "Don't see your language on Crowdin? Contact the Owner on Crowdin! We'd love to support your language.",
7670 Download: "Download",
7671 "Download CSV file": "Download CSV file",
7672 Economy: "Economy",
7673 Email: "Email",
7674 "Email for wallet notifications": "Email for wallet notifications",
7675 "Email Notifications": "Email Notifications",
7676 "Encrypted export file saved": "Encrypted export file saved",
7677 "Enter the seed words (BIP39)": "Enter the seed words (BIP39)",
7678 "Enter your password": "Enter your password",
7679 "Error at Wallet Service": "Error at Wallet Service",
7680 "Error creating wallet": "Error creating wallet",
7681 "Error importing wallet:": "Error importing wallet:",
7682 Expires: "Expires",
7683 Export: "Export",
7684 "Export options": "Export options",
7685 "Extended Public Keys": "Extended Public Keys",
7686 "External Private Key:": "External Private Key:",
7687 "Failed to export": "Failed to export",
7688 "Failed to import wallets": "Failed to import wallets",
7689 "Family vacation funds": "Family vacation funds",
7690 Fee: "Fee",
7691 "Fee Policy": "Fee Policy",
7692 "Fee policy for this transaction": "Fee policy for this transaction",
7693 "Fetching Payment Information": "Fetching Payment Information",
7694 "File/Text Backup": "File/Text Backup",
7695 French: "French",
7696 "Funds are locked by pending spend proposals": "Funds are locked by pending spend proposals",
7697 "Funds found": "Token Types Found",
7698 "Funds received": "Funds/Change received",
7699 "Funds will be transfered to": "Tokens will be transfered to",
7700 "Generate new address": "Generate new address",
7701 "Generate QR Code": "Generate QR Code",
7702 "Generating .csv file...": "Generating .csv file...",
7703 German: "German",
7704 "GET STARTED": "GET STARTED",
7705 "Getting address for wallet {{selectedWalletName}} ...": "Getting address for wallet {{selectedWalletName}} ...",
7706 "Global settings": "Global settings",
7707 "Go back": "Go back",
7708 Greek: "Greek",
7709 "Hardware wallet": "Hardware wallet",
7710 "Hardware Wallet": "Hardware Wallet",
7711 "Have a Backup from Copay v0.9?": "Have a Backup from Copay v0.9?",
7712 "Hide advanced options": "Hide advanced options",
7713 "Hide Wallet Seed": "Hide Wallet Seed",
7714 History: "History",
7715 Home: "Home",
7716 "I affirm that I have read, understood, and agree with these terms.": "I affirm that I have read, understood, and agree with these terms.",
7717 Import: "Import",
7718 "Import backup": "Import backup",
7719 "Import from Ledger": "Import from Ledger",
7720 "Import from the Cloud?": "Import from the Cloud?",
7721 "Import from TREZOR": "Import from TREZOR",
7722 "Import here": "Import here",
7723 "Import wallet": "Import wallet",
7724 "Importing wallet...": "Importing wallet...",
7725 "Importing...": "Importing...",
7726 "In no event shall the authors of the software, employees and affiliates of Bitpay, copyright holders, or BitPay, Inc. be held liable for any claim, damages or other liability, whether in an action of contract, tort, or otherwise, arising from, out of or in connection with the software.": "In no event shall the authors of the software, employees and affiliates of Bitpay, copyright holders, or BitPay, Inc. be held liable for any claim, damages or other liability, whether in an action of contract, tort, or otherwise, arising from, out of or in connection with the software.",
7727 "Incorrect address network": "Incorrect address network",
7728 "Insufficient funds": "Insufficient funds",
7729 "Insufficient funds for fee": "Insufficient funds for fee",
7730 Invalid: "Invalid",
7731 "Invalid address": "Invalid address",
7732 "Invitation to share a Copay Wallet": "Invitation to share a Copay Wallet",
7733 Italian: "Italian",
7734 Japanese: "Japanese",
7735 John: "John",
7736 Join: "Join",
7737 "Join my Copay wallet. Here is the invitation code: {{secret}} You can download Copay for your phone or desktop at https://copay.io": "Join my Copay wallet. Here is the invitation code: {{secret}} You can download Copay for your phone or desktop at https://copay.io",
7738 "Join shared wallet": "Join shared wallet",
7739 "Joining Wallet...": "Joining Wallet...",
7740 "Key already associated with an existing wallet": "Key already associated with an existing wallet",
7741 Language: "Language",
7742 "Last Wallet Addresses": "Last Wallet Addresses",
7743 "Learn more about Copay backups": "Learn more about Copay backups",
7744 "Learn more about Wallet Migration": "Learn more about Wallet Migration",
7745 "Loading...": "Loading...",
7746 "locked by pending payments": "Locked by pending payments and/or in use to hold NTP1 Tokens",
7747 "Locktime in effect. Please wait to create a new spend proposal": "Locktime in effect. Please wait to create a new spend proposal",
7748 "Locktime in effect. Please wait to remove this spend proposal": "Locktime in effect. Please wait to remove this spend proposal",
7749 "Make a payment to": "Make a payment to",
7750 me: "me",
7751 Me: "Me",
7752 Memo: "Memo",
7753 "Merchant message": "Merchant message",
7754 Message: "Message",
7755 More: "More",
7756 Moved: "Moved",
7757 "Multisignature wallet": "Multisignature wallet",
7758 "My Bitcoin address": "My Neblio address",
7759 Network: "Network",
7760 "Network connection error": "Network connection error",
7761 "New Payment Proposal": "New Payment Proposal",
7762 "No Private key": "No Private key",
7763 "No transactions yet": "No transactions yet",
7764 Normal: "Normal",
7765 "Not authorized": "Not authorized",
7766 "Not valid": "Not valid",
7767 Note: "Note",
7768 "Official English Disclaimer": "Official English Disclaimer",
7769 "Once you have copied your wallet seed down, it is recommended to delete it from this device.": "Once you have copied your wallet seed down, it is recommended to delete it from this device.",
7770 "Only Main (not change) addresses are shown. The addresses on this list were not verified locally at this time.": "Only Main (not change) addresses are shown. The addresses on this list were not verified locally at this time.",
7771 optional: "optional",
7772 "Paper Wallet Private Key": "Neblio Address Private Key",
7773 Participants: "Participants",
7774 Passphrase: "Passphrase",
7775 "Passphrase (if you have one)": "Passphrase (if you have one)",
7776 Password: "Password",
7777 "Password needed": "Password needed",
7778 "Passwords do not match": "Passwords do not match",
7779 "Paste invitation here": "Paste invitation here",
7780 "Paste the backup plain text code": "Paste the backup plain text code",
7781 "Paste your paper wallet private key here": "Paste your Neblio address private key here",
7782 "Pay To": "Pay To",
7783 "Payment Accepted": "Payment Accepted",
7784 "Payment accepted, but not yet broadcasted": "Payment accepted, but not yet broadcasted",
7785 "Payment accepted. It will be broadcasted by Glidera. In case there is a problem, it can be deleted 6 hours after it was created.": "Payment accepted. It will be broadcasted by Glidera. In case there is a problem, it can be deleted 6 hours after it was created.",
7786 "Payment details": "Payment details",
7787 "Payment Proposal": "Payment Proposal",
7788 "Payment Proposal Created": "Payment Proposal Created",
7789 "Payment Proposal Rejected": "Payment Proposal Rejected",
7790 "Payment Proposal Rejected by Copayer": "Payment Proposal Rejected by Copayer",
7791 "Payment Proposal Signed by Copayer": "Payment Proposal Signed by Copayer",
7792 "Payment Proposals": "Payment Proposals",
7793 "Payment Protocol Invalid": "Payment Protocol Invalid",
7794 "Payment Protocol not supported on Chrome App": "Payment Protocol not supported on Chrome App",
7795 "Payment rejected": "Payment rejected",
7796 "Payment Rejected": "Payment Rejected",
7797 "Payment request": "Payment request",
7798 "Payment sent": "Payment sent",
7799 "Payment Sent": "Payment Sent",
7800 "Payment to": "Payment to",
7801 "Pending Confirmation": "Pending Confirmation",
7802 "Permanently delete this wallet. THIS ACTION CANNOT BE REVERSED": "Permanently delete this wallet. THIS ACTION CANNOT BE REVERSED",
7803 "Personal Wallet": "Personal Wallet",
7804 "Please enter the required fields": "Please enter the required fields",
7805 "Please enter the seed words": "Please enter the seed words",
7806 "Please enter the wallet seed": "Please enter the wallet seed",
7807 "Please upgrade Copay to perform this action": "Please upgrade Copay to perform this action",
7808 "Please, select your backup file": "Please, select your backup file",
7809 Portuguese: "Portuguese",
7810 Preferences: "Preferences",
7811 "Preparing backup...": "Preparing backup...",
7812 Priority: "Priority",
7813 "QR Code": "QR Code",
7814 "QR-Scanner": "QR-Scanner",
7815 Receive: "Receive",
7816 Received: "Received",
7817 Recipients: "Recipients",
7818 "Reconnecting to Wallet Service...": "Reconnecting to Wallet Service...",
7819 Recreate: "Recreate",
7820 "Recreating Wallet...": "Recreating Wallet...",
7821 Reject: "Reject",
7822 "Rejecting payment": "Rejecting payment",
7823 "Release Information": "Release Information",
7824 "Repeat password": "Repeat password",
7825 "Request a specific amount": "Request a specific amount",
7826 "Request Password for Spending Funds": "Request Password for Spending Funds",
7827 "Requesting Ledger Wallet to sign": "Requesting Ledger Wallet to sign",
7828 Required: "Required",
7829 "Required number of signatures": "Required number of signatures",
7830 "Retrying...": "Retrying...",
7831 Russian: "Russian",
7832 Save: "Save",
7833 "Saving preferences...": "Saving preferences...",
7834 "Scan addresses for funds": "Scan addresses for funds",
7835 "Scan Finished": "Scan Finished",
7836 "Scan status finished with error": "Scan status finished with error",
7837 "Scan Wallet Funds": "Scan For Tokens",
7838 "Scanning wallet funds...": "Scanning for tokens...",
7839 "Scanning Wallet funds...": "Scanning Wallet funds...",
7840 "See it on the blockchain": "See it on the blockchain",
7841 "Seed passphrase": "Seed passphrase",
7842 "Seed Passphrase": "Seed Passphrase",
7843 "Select a backup file": "Select a backup file",
7844 "Select a wallet": "Select a wallet",
7845 "Self-signed Certificate": "Self-signed Certificate",
7846 Send: "Send",
7847 "Send All": "Send All",
7848 "Send all by email": "Send all by email",
7849 "Send by email": "Send by email",
7850 "Sending funds...": "Sending tokens...",
7851 Sent: "Sent",
7852 Server: "Server",
7853 "Server response could not be verified": "Server response could not be verified",
7854 "Session log": "Session log",
7855 SET: "SET",
7856 "Set up a Export Password": "Set up a Export Password",
7857 "Set up a password": "Set up a password",
7858 "Setting up email notifications could weaken your privacy, if the wallet service provider is compromised. Information available to an attacker would include your wallet addresses and its balance, but no more.": "Setting up email notifications could weaken your privacy, if the wallet service provider is compromised. Information available to an attacker would include your wallet addresses and its balance, but no more.",
7859 settings: "settings",
7860 "Share address": "Share address",
7861 "Share invitation": "Share invitation",
7862 "Share this invitation with your copayers": "Share this invitation with your copayers",
7863 "Share this wallet address to receive payments. To protect your privacy, new addresses are generated automatically once you use them.": "Share this wallet address to receive payments. To protect your privacy, new addresses are generated automatically once you use them.",
7864 "Shared Wallet": "Shared Wallet",
7865 "Show advanced options": "Show advanced options",
7866 "Show Wallet Seed": "Show Wallet Seed",
7867 "Signatures rejected by server": "Signatures rejected by server",
7868 "Signing payment": "Signing payment",
7869 "SKIP BACKUP": "SKIP BACKUP",
7870 Spanish: "Spanish",
7871 "Specify your wallet seed": "Specify your wallet seed",
7872 "Spend proposal is not accepted": "Spend proposal is not accepted",
7873 "Spend proposal not found": "Spend proposal not found",
7874 "Still not done": "Still not done",
7875 Success: "Success",
7876 "Sweep paper wallet": "Sweep Tokens From Private Key",
7877 "Sweep Wallet": "Sweep Tokens",
7878 "Tap to retry": "Tap to retry",
7879 "Terms of Use": "Terms of Use",
7880 Testnet: "Testnet",
7881 "The authors of the software, employees and affiliates of Bitpay, copyright holders, and BitPay, Inc. cannot retrieve your private keys or passwords if you lose or forget them and cannot guarantee transaction confirmation as they do not have control over the Bitcoin network.": "The authors of the software, employees and affiliates of Bitpay, copyright holders, and BitPay, Inc. cannot retrieve your private keys or passwords if you lose or forget them and cannot guarantee transaction confirmation as they do not have control over the Neblio network.",
7882 "The Ledger Chrome application is not installed": "The Ledger Chrome application is not installed",
7883 "The payment was created but could not be completed. Please try again from home screen": "The payment was created but could not be completed. Please try again from home screen",
7884 "The payment was created but could not be signed. Please try again from home screen": "The payment was created but could not be signed. Please try again from home screen",
7885 "The payment was removed by creator": "The payment was removed by creator",
7886 "The payment was signed but could not be broadcasted. Please try again from home screen": "The payment was signed but could not be broadcasted. Please try again from home screen",
7887 "The private key for this wallet is encrypted. Exporting keep the private key encrypted in the export archive.": "The private key for this wallet is encrypted. Exporting keep the private key encrypted in the export archive.",
7888 "The seed could require a passphrase to be imported": "The seed could require a passphrase to be imported",
7889 "The software does not constitute an account where BitPay or other third parties serve as financial intermediaries or custodians of your bitcoin.": "The software does not constitute an account where BitPay or other third parties serve as financial intermediaries or custodians of your neblio.",
7890 "The software you are about to use functions as a free, open source, and multi-signature digital wallet.": "The software you are about to use functions as a free, open source, and multi-signature digital wallet.",
7891 "The spend proposal is not pending": "The spend proposal is not pending",
7892 'The wallet "{{walletName}}" was deleted': 'The wallet "{{walletName}}" was deleted',
7893 "There are no wallets to make this payment": "There are no wallets to make this payment",
7894 "There is an error in the form": "There is an error in the form",
7895 "This transaction has become invalid; possibly due to a double spend attempt.": "This transaction has become invalid; possibly due to a double spend attempt.",
7896 "This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.": "This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.",
7897 Time: "Time",
7898 To: "To",
7899 "To restore this {{index.m}}-{{index.n}} <b>shared</b> wallet you will need": "To restore this {{index.m}}-{{index.n}} <b>shared</b> wallet you will need",
7900 "To the fullest extent permitted by law, this software is provided “as is†and no representations or warranties can be made of any kind, express or implied, including but not limited to the warranties of merchantability, fitness or a particular purpose and noninfringement.": "To the fullest extent permitted by law, this software is provided “as is†and no representations or warranties can be made of any kind, express or implied, including but not limited to the warranties of merchantability, fitness or a particular purpose and noninfringement.",
7901 "too long!": "too long!",
7902 Total: "Total",
7903 "Total Locked Balance": "Total Locked Balance",
7904 "Total number of copayers": "Total number of copayers",
7905 Transaction: "Transaction",
7906 "Transaction already broadcasted": "Transaction already broadcasted",
7907 "Translation Credits": "Translation Credits",
7908 Translators: "Translators",
7909 "Type the Seed Word (usually 12 words)": "Type the Seed Word (usually 12 words)",
7910 "Unable to send transaction proposal": "Unable to send transaction proposal",
7911 Unconfirmed: "Unconfirmed",
7912 Unit: "Unit",
7913 "Unsent transactions": "Unsent transactions",
7914 "Updating Wallet...": "Updating Wallet...",
7915 "Use Ledger hardware wallet": "Use Ledger hardware wallet",
7916 "Use TREZOR hardware wallet": "Use TREZOR hardware wallet",
7917 "Use Unconfirmed Funds": "Use Unconfirmed Funds",
7918 Username: "Username",
7919 Version: "Version",
7920 View: "View",
7921 "Waiting for copayers": "Waiting for copayers",
7922 "Waiting...": "Waiting...",
7923 Wallet: "Wallet",
7924 "Wallet Alias": "Wallet Alias",
7925 "Wallet already exists": "Wallet already exists",
7926 "Wallet Already Imported:": "Wallet Already Imported:",
7927 "Wallet already in Copay:": "Wallet already in Copay:",
7928 "Wallet Configuration (m-n)": "Wallet Configuration (m-n)",
7929 "Wallet Export": "Wallet Export",
7930 "Wallet Id": "Wallet Id",
7931 "Wallet incomplete and broken": "Wallet incomplete and broken",
7932 "Wallet Information": "Wallet Information",
7933 "Wallet Invitation": "Wallet Invitation",
7934 "Wallet Invitation is not valid!": "Wallet Invitation is not valid!",
7935 "Wallet is full": "Wallet is full",
7936 "Wallet is not complete": "Wallet is not complete",
7937 "Wallet name": "Wallet name",
7938 "Wallet Name (at creation)": "Wallet Name (at creation)",
7939 "Wallet Network": "Wallet Network",
7940 "Wallet not found": "Wallet not found",
7941 'Wallet not registed at the Wallet Service. Recreate it from "Create Wallet" using "Advanced Options" to set your seed': 'Wallet not registed at the Wallet Service. Recreate it from "Create Wallet" using "Advanced Options" to set your seed',
7942 "Wallet Seed": "Wallet Seed",
7943 "Wallet Seed could require a passphrase to be imported": "Wallet Seed could require a passphrase to be imported",
7944 "Wallet seed is invalid": "Wallet seed is invalid",
7945 "Wallet seed not available. You can still export it from Advanced > Export.": "Wallet seed not available. You can still export it from Advanced > Export.",
7946 "Wallet service not found": "Wallet service not found",
7947 "WARNING: Backup needed": "WARNING: Backup needed",
7948 "WARNING: Not including the private key allows to check the wallet balance, transaction history, and create spend proposals from the export. However, does not allow to approve (sign) proposals, so <b>funds will not be accessible from the export</b>.": "WARNING: Not including the private key allows to check the wallet balance, transaction history, and create spend proposals from the export. However, does not allow to approve (sign) proposals, so <b>funds will not be accessible from the export</b>.",
7949 "WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase.": "WARNING: Passphrase cannot be recovered. <b>Be sure to write it down</b>. The wallet can not be restored without the passphrase.",
7950 "WARNING: The private key of this wallet is not available. The export allows to check the wallet balance, transaction history, and create spend proposals from the export. However, does not allow to approve (sign) proposals, so <b>funds will not be accessible from the export</b>.": "WARNING: The private key of this wallet is not available. The export allows to check the wallet balance, transaction history, and create spend proposals from the export. However, does not allow to approve (sign) proposals, so <b>funds will not be accessible from the export</b>.",
7951 "WARNING: This seed was created with a passphrase. To recover this wallet both the mnemonic and passphrase are needed.": "WARNING: This seed was created with a passphrase. To recover this wallet both the mnemonic and passphrase are needed.",
7952 "Warning: this transaction has unconfirmed inputs": "Warning: this transaction has unconfirmed inputs",
7953 "WARNING: UNTRUSTED CERTIFICATE": "WARNING: UNTRUSTED CERTIFICATE",
7954 "WARNING: Wallet not registered": "WARNING: Wallet not registered",
7955 "Warning!": "Warning!",
7956 "We reserve the right to modify this disclaimer from time to time.": "We reserve the right to modify this disclaimer from time to time.",
7957 "WELCOME TO COPAY": "WELCOME TO COPAY",
7958 "While the software has undergone beta testing and continues to be improved by feedback from the open-source user and developer community, we cannot guarantee that there will be no bugs in the software.": "While the software has undergone beta testing and continues to be improved by feedback from the open-source user and developer community, we cannot guarantee that there will be no bugs in the software.",
7959 "Write it down and keep them somewhere safe.": "Write it down and keep them somewhere safe.",
7960 "Wrong number of seed words:": "Wrong number of seed words:",
7961 "Wrong password": "Wrong password",
7962 Yes: "Yes",
7963 "You acknowledge that your use of this software is at your own discretion and in compliance with all applicable laws.": "You acknowledge that your use of this software is at your own discretion and in compliance with all applicable laws.",
7964 "You are responsible for safekeeping your passwords, private key pairs, PINs and any other codes you use to access the software.": "You are responsible for safekeeping your passwords, private key pairs, PINs and any other codes you use to access the software.",
7965 "You assume any and all risks associated with the use of the software.": "You assume any and all risks associated with the use of the software.",
7966 "You can safely install your wallet on another device and use it from multiple devices at the same time.": "You can safely install your wallet on another device and use it from multiple devices at the same time.",
7967 "You do not have a wallet": "You do not have a wallet",
7968 "You need the wallet seed to restore this personal wallet.": "You need the wallet seed to restore this personal wallet.",
7969 "Your backup password": "Your backup password",
7970 "Your export password": "Your export password",
7971 "Your nickname": "Your nickname",
7972 "Your password": "Your password",
7973 "Your profile password": "Your profile password",
7974 "Your wallet has been imported correctly": "Your wallet has been imported correctly",
7975 "Your wallet key will be encrypted. Password cannot be recovered. Be sure to write it down": "Your wallet key will be encrypted. Password cannot be recovered. Be sure to write it down",
7976 "Your Wallet Seed": "Your Wallet Seed",
7977 "Your wallet seed and access to the server that coordinated the initial wallet creation. You still need {{index.m}} keys to spend.": "Your wallet seed and access to the server that coordinated the initial wallet creation. You still need {{index.m}} keys to spend."
7978 })
7979 }]), window.version = "2.7.0-orion.2", window.commitHash = "adc3673", angular.element(document).ready(function() {
7980 function handleOpenURL(url) {
7981 "cordova" in window ? (console.log("DEEP LINK:" + url), cordova.fireDocumentEvent("handleopenurl", {
7982 url: url
7983 })) : console.log("ERROR: Cannont handle open URL in non-cordova apps")
7984 }
7985 var startAngular = function() {
7986 angular.bootstrap(document, ["copayApp"])
7987 };
7988 "cordova" in window ? (window.handleOpenURL = handleOpenURL, document.addEventListener("deviceready", function() {
7989 window.open = cordova.InAppBrowser.open, cordova.addStickyDocumentEventHandler("handleopenurl"), startAngular()
7990 }, !1)) : startAngular()
7991 }), window.TREZOR_CHROME_URL = "./bower_components/trezor-connect/chrome/wrapper.html";
7992var module = angular.module("copayAddon.orion", ["ngFileUpload"]);
7993angular.module("copayAddon.orion").config(function($stateProvider) {
7994 $stateProvider.state("tokens", {
7995 url: "/tokens",
7996 walletShouldBeComplete: !0,
7997 needProfile: !0,
7998 views: {
7999 main: {
8000 templateUrl: "views/orion/tokens.html"
8001 }
8002 }
8003 })
8004}).run(function(addonManager, orion, $state) {
8005 addonManager.registerAddon({
8006 formatPendingTxp: function(txp) {
8007 if (txp.customData && txp.customData.token) {
8008 var token = (txp.amountStr, txp.customData.token);
8009 txp.amountStr = token.amount + " unit" + (token.amount > 1 ? "s" : "") + " of " + token.tokenName, txp.showSingle = !0, txp.toAddress = txp.outputs[0].toAddress, txp.address = txp.outputs[0].address
8010 }
8011 },
8012 txTemplateUrl: function() {
8013 return "views/orion/includes/transaction.html"
8014 }
8015 })
8016}), angular.module("copayAddon.orion").config(function($provide) {
8017 $provide.decorator("availableBalanceDirective", function($delegate) {
8018 var directive = $delegate[0];
8019 return directive.controller = function($rootScope, $scope, profileService, configService, lodash) {
8020 var config = configService.getSync().wallet.settings;
8021 $rootScope.$on("Orion/TokensUpdated", function(event, tokens) {
8022 var availableBalanceSat = $scope.index.availableBalanceSat;
8023 $scope.availableBalanceStr = profileService.formatAmount(availableBalanceSat) + " " + config.unitName
8024 })
8025 }, directive.templateUrl = "views/orion/includes/available-balance.html", $delegate
8026 })
8027}), angular.module("copayAddon.orion").config(function($provide) {
8028 $provide.decorator("logoDirective", function($delegate) {
8029 var directive = $delegate[0],
8030 ctrl = directive.controller;
8031 return directive.controller = function($scope) {
8032 if (ctrl.apply(this, arguments), $scope.width) {
8033 var logo_width = 1.5 * $scope.width,
8034 logo_height = logo_width / (220 / 43);
8035 $scope.copay_logo_style = "width: " + $scope.width + "px;", $scope.logo_style = "background-size: " + logo_width + "px " + logo_height + "px;width: " + logo_width + "px; height: " + logo_height + "px;"
8036 } else {
8037 $scope.copay_logo_style = "width: " + 100 / 1.5 + "%%; max-width: 147px", $scope.logo_style = "background-size: 100% auto; width: 100%; max-width: 220px; height: 43px;"
8038 }
8039 }, directive.template = '<div class="nt-logo-holder" ng-class="{ \'negative\' : negative, \'inline\' : width < 50, }"><img ng-src="{{ logo_url }}" alt="Orion" style="{{ copay_logo_style }}"></div>', $delegate
8040 })
8041});
8042var TokenIssueController = function($rootScope, $scope, $timeout, $log, orion, gettext, profileService, lodash, bitcore, txStatus, Upload, ntFeeService, configService, walletService, txFormatService, ongoingProcess, $ionicModal) {
8043 ProcessingTxController.call(this, $rootScope, $scope, $timeout, $log, orion, gettext, profileService, lodash, bitcore, txStatus, walletService, configService, txFormatService, ongoingProcess, $ionicModal);
8044 var self = this;
8045 $scope.issuance = {
8046 userData: []
8047 }, $scope.estimatedCost = "...", ntFeeService.estimateCostOfIssuance(function(err, fee, totalCost) {
8048 if (err) return self._handleError(err);
8049 var config = configService.getSync().wallet.settings;
8050 $scope.totalCost = totalCost, $scope.estimatedCost = profileService.formatAmount(totalCost) + " " + config.unitName, $scope.$digest()
8051 }), $scope.addField = function() {
8052 $scope.issuance.userData.push({
8053 key: "",
8054 value: "",
8055 type: "String"
8056 })
8057 }, $scope.removeField = function(field) {
8058 lodash.pull($scope.issuance.userData, field)
8059 }, $scope.cancel = function() {
8060 self.setOngoingProcess(), ongoingProcess.clear(), $scope.issueTokenModal.hide()
8061 };
8062 var handleEncryptedWallet = function(client, cb) {
8063 if (!walletService.isEncrypted(client)) return cb();
8064 $rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
8065 return cb(err ? err : walletService.unlock(client, password))
8066 })
8067 },
8068 createToken = function(issuance, iconData) {
8069 self.setOngoingProcess(gettext("Creating issuance transaction")), issuance.tokenName = issuance.tokenName.toString().toUpperCase(), orion.createIssueTx(issuance, function(err, result) {
8070 err && self._handleError(err);
8071 var customData = {
8072 token: {
8073 action: "issue",
8074 tokenName: issuance.tokenName,
8075 icon: iconData ? iconData.url : null,
8076 amount: issuance.amount,
8077 fee: $scope.totalCost - 2e4
8078 }
8079 };
8080 orion.getTokenId(issuance.tokenName, function(err, res) {
8081 if (err && self._handleError(err), void 0 !== res.tokenId) return self._setError(new Error("Token Symbol " + issuance.tokenName + " already in use. Click Back and try again."));
8082 self._createAndExecuteProposal(result.txHex, result.issuanceUtxo.address, customData)
8083 })
8084 })
8085 },
8086 createTokenWithIcon = function(issuance, icon) {
8087 Upload.upload({
8088 url: "/upload",
8089 file: icon
8090 }).success(function(iconData, status, headers, config) {
8091 if (!iconData.url || 0 != iconData.url.indexOf("https://ntp1-icons.ams3.digitaloceanspaces.com")) return console.log("Error uploading: " + status + " " + iconData), self._handleError({
8092 error: "Failed to upload icon"
8093 });
8094 console.log("Icon uploaded. URL: " + iconData), issuance.urls = [{
8095 name: "icon",
8096 url: iconData.url,
8097 mimeType: iconData.mimeType
8098 }], createToken(issuance, iconData)
8099 }).error(function(data, status, headers, config) {
8100 console.log("error uploading icon: " + status + " " + data), self._handleError({
8101 error: "Failed to upload icon"
8102 })
8103 })
8104 };
8105 $scope.issueToken = function(form) {
8106 if (form.$invalid) return void(this.error = gettext("Unable to send transaction proposal"));
8107 var fc = profileService.focusedClient;
8108 if (fc.isPrivKeyEncrypted()) return void handleEncryptedWallet(fc, function(err) {
8109 if (err) return self.setSendError(err)
8110 });
8111 this.file ? createTokenWithIcon(this.issuance, this.file) : createToken(this.issuance)
8112 }
8113};
8114TokenIssueController.prototype = Object.create(ProcessingTxController.prototype), angular.module("copayAddon.orion").controller("tokenIssueController", TokenIssueController), ProcessingTxController.prototype.setOngoingProcess = function(name) {
8115 this.$rootScope.$emit("Addon/OngoingProcess", name)
8116}, ProcessingTxController.prototype._setError = function(err) {
8117 var fc = this.profileService.focusedClient;
8118 this.$log.warn(err);
8119 var errMessage = fc.credentials.m > 1 ? this.gettext("Could not create transaction proposal") : this.gettext("Could not perform transaction");
8120 err.message = err.error ? err.error : err.message, errMessage = errMessage + ". " + (err.message ? err.message : this.gettext("Check you connection and try again")), this.$scope.error = errMessage
8121}, ProcessingTxController.prototype._handleError = function(err) {
8122 return this.setOngoingProcess(), this._setError(err)
8123}, ProcessingTxController.prototype._signAndBroadcast = function(txp, cb) {
8124 var self = this,
8125 fc = self.profileService.focusedClient;
8126 self.setOngoingProcess(self.gettext("Signing transaction")), fc.signTxProposal(txp, function(err, signedTx) {
8127 return self.setOngoingProcess(), err ? (err.message = self.gettext("Transaction was created but could not be signed. Please try again from home screen.") + (err.message ? " " + err.message : ""), cb(err)) : "accepted" != signedTx.status ? (self.setOngoingProcess(), cb(null, signedTx)) : (self.setOngoingProcess(self.gettext("Broadcasting transaction")), void fc.broadcastTxProposal(signedTx, function(err, btx, memo) {
8128 return self.setOngoingProcess(), err ? (err.message = self.gettext("Transaction was signed but could not be broadcasted. Please try again from home screen.") + (err.message ? " " + err.message : ""), cb(err)) : cb(null, btx)
8129 }))
8130 })
8131}, ProcessingTxController.prototype._openStatusModal = function(type, txp, cb) {
8132 var self = this,
8133 fc = this.profileService.focusedClient;
8134 self.$scope.type = type, self.$scope.tx = this.txFormatService.processTx(txp), self.$scope.color = fc.backgroundColor, self.$scope.cb = cb;
8135 var txStatusUrl = "views/modals/tx-status.html";
8136 txp.customData && txp.customData.token && (txStatusUrl = "transfer" == txp.customData.token.action ? "views/orion/modals/transfer-status.html" : "views/orion/modals/issue-status.html"), self.$ionicModal.fromTemplateUrl(txStatusUrl, {
8137 scope: self.$scope,
8138 animation: "slide-in-up"
8139 }).then(function(modal) {
8140 self.$scope.txStatusModal = modal, self.$scope.txStatusModal.show()
8141 })
8142}, ProcessingTxController.prototype._handleEncryptedWallet = function(client, cb) {
8143 var self = this;
8144 if (!this.walletService.isEncrypted(client)) return cb();
8145 this.$rootScope.$emit("Local/NeedsPassword", !1, function(err, password) {
8146 return cb(err ? err : self.walletService.unlock(client, password))
8147 })
8148}, ProcessingTxController.prototype._closeModal = function() {
8149 this.$scope.cancel(), this.$rootScope.$emit("Orion/TxComplete")
8150}, ProcessingTxController.prototype._confirmTx = function(txp) {
8151 var client = this.profileService.focusedClient,
8152 self = this;
8153 self._handleEncryptedWallet(client, function(err) {
8154 if (err) return self._setError(err);
8155 self.ongoingProcess.set("sendingTx", !0), self.walletService.publishTx(client, txp, function(err, publishedTxp) {
8156 if (self.ongoingProcess.set("sendingTx", !1), err) return self._setError(err);
8157 self.ongoingProcess.set("signingTx", !0), client.credentials.network.network && (client.credentials.network = client.credentials.network.network), self.walletService.signTx(client, publishedTxp, function(err, signedTxp) {
8158 if (self.ongoingProcess.set("signingTx", !1), self.walletService.lock(client), err) return self.$scope.$emit("Local/TxProposalAction"), self._setError(err.message ? err.message : gettext("The payment was created but could not be completed. Please try again from home screen"));
8159 if ("accepted" == signedTxp.status) self.ongoingProcess.set("broadcastingTx", !0), self.walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) {
8160 if (self.ongoingProcess.set("broadcastingTx", !1), err) return self._setError(err);
8161 self._closeModal();
8162 var type = self.txStatus.notify(broadcastedTxp);
8163 self._openStatusModal(type, broadcastedTxp, function() {
8164 self.$scope.$emit("Local/TxProposalAction", "broadcasted" == broadcastedTxp.status)
8165 })
8166 });
8167 else {
8168 self._closeModal();
8169 var type = self.txStatus.notify(signedTxp);
8170 self._openStatusModal(type, signedTxp, function() {
8171 self.$scope.$emit("Local/TxProposalAction")
8172 })
8173 }
8174 })
8175 })
8176 })
8177}, ProcessingTxController.prototype._createAndExecuteProposal = function(txHex, toAddress, customData) {
8178 var self = this,
8179 client = self.profileService.focusedClient,
8180 tx = new self.bitcore.Transaction(txHex);
8181 self.$log.debug(JSON.stringify(tx.toObject(), null, 2));
8182 var inputs = self._.map(tx.inputs, function(input) {
8183 return input = input.toObject(), input = self.orion.txidToUTXO[input.prevTxId + ":" + input.outputIndex], input.outputIndex = input.vout, input
8184 }),
8185 outputs = self._.chain(tx.outputs).map(function(o) {
8186 return {
8187 script: o.script.toString(),
8188 amount: o.satoshis
8189 }
8190 }).dropRight().value();
8191 outputs[0].toAddress = toAddress, self.setOngoingProcess(self.gettext("Creating tx proposal"));
8192 var txp = {};
8193 txp.type = "external", txp.inputs = inputs, txp.outputs = outputs, txp.validateOutputs = !1, txp.noShuffleOutputs = !0, txp.message = null, txp.payProUrl = null, txp.customData = customData, "issue" == customData.token.action ? (txp.fee = customData.token.fee, self.$log.info("Using Fee: " + txp.fee)) : txp.feePerKb = 1e4, self.walletService.createTx(client, txp, function(err, createdTxp) {
8194 if (self.ongoingProcess.set("creatingTx", !1), err) return self._setError(err);
8195 client.canSign() || client.isPrivKeyExternal() ? self.$rootScope.$emit("Local/NeedsConfirmation", createdTxp, function(accept) {
8196 accept ? self._confirmTx(createdTxp) : self.$scope.cancel()
8197 }) : (self.$log.info("No signing proposal: No private key"), self.ongoingProcess.set("sendingTx", !0), self.walletService.publishTx(client, createdTxp, function(err, publishedTxp) {
8198 if (self.ongoingProcess.set("sendingTx", !1), err) return self._setError(err);
8199 self._closeModal();
8200 var type = self.txStatus.notify(createdTxp);
8201 self._openStatusModal(type, createdTxp, function() {
8202 return self.$scope.$emit("Local/TxProposalAction")
8203 })
8204 }))
8205 })
8206}, angular.module("copayAddon.orion").controller("tokenDetailsController", function($rootScope, $scope, $ionicModal, profileService, insight) {
8207 var fc = profileService.focusedClient;
8208 $scope.color = fc.backgroundColor, insight = insight.get(), insight.getTransaction($scope.token.issuanceTxid, function(err, tx) {
8209 err || ($scope.issuanceTx = tx)
8210 }), $rootScope.$on("Orion/TxComplete", function() {
8211 $scope.cancel()
8212 }),
8213 $scope.openTransferModal = function() {
8214 $ionicModal.fromTemplateUrl("views/orion/modals/send.html", {
8215 scope: $scope
8216 }).then(function(modal) {
8217 $scope.tokenTransferModal = modal, $scope.tokenTransferModal.show()
8218 })
8219 }, $scope.openBlockExplorer = function() {
8220 var networkSuffix = "testnet" == profileService.focusedClient.credentials.network ? "testnet/" : "";
8221 $rootScope.openExternalLink("http://explorer.nebl.io/" + networkSuffix + "tx/" + $scope.token.issuanceTxid)
8222 }, $scope.cancel = function() {
8223 $scope.tokenDetailsModal.hide()
8224 }
8225});
8226var TokenTransferController = function($rootScope, $scope, $timeout, $log, orion, gettext, profileService, lodash, bitcore, txStatus, walletService, configService, txFormatService, ongoingProcess, $ionicModal) {
8227 ProcessingTxController.call(this, $rootScope, $scope, $timeout, $log, orion, gettext, profileService, lodash, bitcore, txStatus, walletService, configService, txFormatService, ongoingProcess, $ionicModal);
8228 var self = this;
8229 $scope.onQrCodeScanned = function(data) {
8230 this.error = "";
8231 var form = this.tokenTransferForm;
8232 data && (form.address.$setViewValue(new bitcore.URI(data).address.toString()), form.address.$isValid = !0, form.address.$render(), $scope.lockAddress = !0), form.address.$invalid && ($scope.resetError(), $scope.lockAddress = !1, $scope._address = null, this.error = gettext("Could not recognize a valid Bitcoin QR Code"))
8233 }, $scope.cancel = function() {
8234 self.setOngoingProcess(), ongoingProcess.clear(), $scope.tokenTransferModal.hide()
8235 }, $scope.transferToken = function(transfer, form) {
8236 $log.debug("Transfering " + transfer._amount + " units(s) of token " + $scope.token.token.tokenId + " to " + transfer._address);
8237 for (var i = 0; i < $scope.token.token.utxo.scriptPubKey.addresses.length; i++)
8238 if ($scope.token.token.utxo.scriptPubKey.addresses[i] == transfer._address) return void(this.error = gettext("Sending and receiving addresses cannot be the same"));
8239 if (form.$invalid) return void(this.error = gettext("Unable to send transaction proposal"));
8240 self.setOngoingProcess(gettext("Creating transfer transaction")), orion.createTransferTx("", $scope.token, transfer._amount, transfer._address, function(err, result) {
8241 err && self._handleError(err);
8242 var customData = {
8243 token: {
8244 action: "transfer",
8245 tokenId: $scope.token.token.tokenId,
8246 tokenName: $scope.token.metadata.tokenName,
8247 icon: $scope.token.icon,
8248 utxo: lodash.pick($scope.token.utxo, ["txid", "index"]),
8249 amount: transfer._amount
8250 }
8251 };
8252 self._createAndExecuteProposal(result.txHex, transfer._address, customData)
8253 })
8254 }
8255};
8256TokenTransferController.prototype = Object.create(ProcessingTxController.prototype), angular.module("copayAddon.orion").controller("tokenTransferController", TokenTransferController), angular.module("copayAddon.orion").controller("tokensController", function($rootScope, $scope, $timeout, $ionicModal, platformInfo, orion) {
8257 var self = this;
8258 this.tokens = orion.tokens, this.error = orion.error;
8259 var disableTokenListener = $rootScope.$on("Orion/TokensUpdated", function(event, tokens) {
8260 self.tokens = tokens
8261 }),
8262 disableErrorListener = $rootScope.$on("Orion/Error", function(event, errorMsg) {
8263 self.error = errorMsg
8264 }),
8265 disableOngoingProcessListener = $rootScope.$on("Addon/OngoingProcess", function(e, name) {
8266 self.setOngoingProcess(name)
8267 });
8268 $scope.$on("$destroy", function() {
8269 disableTokenListener(), disableOngoingProcessListener(), disableErrorListener()
8270 }), this.setOngoingProcess = function(name) {
8271 var self = this;
8272 self.blockUx = !!name, platformInfo.isCordova ? name ? (window.plugins.spinnerDialog.hide(), window.plugins.spinnerDialog.show(null, name + "...", !0)) : window.plugins.spinnerDialog.hide() : (self.onGoingProcess = name, $timeout(function() {
8273 $rootScope.$apply()
8274 }))
8275 }, this.setOngoingProcess(orion.onGoingProcess), this.openTokenModal = function(token) {
8276 $scope.token = token, $ionicModal.fromTemplateUrl("views/orion/modals/token-details.html", {
8277 scope: $scope
8278 }).then(function(modal) {
8279 $scope.tokenDetailsModal = modal, $scope.tokenDetailsModal.show()
8280 })
8281 }, this.openIssueModal = function() {
8282 $ionicModal.fromTemplateUrl("views/orion/modals/issue.html", {
8283 scope: $scope
8284 }).then(function(modal) {
8285 $scope.issueTokenModal = modal, $scope.issueTokenModal.show()
8286 })
8287 }
8288}), angular.module("copayAddon.orion").factory("insight", function($http, profileService) {
8289 function Insight(opts) {
8290 this.network = opts.network || "livenet", this.url = opts.url
8291 }
8292 Insight.prototype.getTransaction = function(txid, cb) {
8293 var url = this.url + "/tx/" + txid;
8294 $http.get(url).success(function(data, status) {
8295 return 200 != status ? cb(data) : cb(null, data)
8296 }).error(function(data, status) {
8297 return cb(data)
8298 })
8299 };
8300 var testnetInsight = new Insight({
8301 network: "testnet",
8302 url: "https://ntp1node.nebl.io:13002/insight-api"
8303 }),
8304 livenetInsight = new Insight({
8305 network: "livenet",
8306 url: "https://ntp1node.nebl.io:3002/insight-api"
8307 });
8308 return {
8309 get: function() {
8310 return "testnet" == profileService.focusedClient.credentials.network ? testnetInsight : livenetInsight
8311 }
8312 }
8313}), angular.module("copayAddon.orion").service("ntFeeService", function(profileService, feeService, $log) {
8314 var root = {},
8315 _getEstimatedSize = function(nbInputs, nbOutputs) {
8316 var credentials = profileService.focusedClient.credentials,
8317 walletM = credentials.m,
8318 inputSize = 72 * walletM + 36 * credentials.n + 44;
8319 nbOutputs += 1;
8320 var size = 26 + inputSize * nbInputs + 34 * nbOutputs;
8321 return parseInt((1.05 * size).toFixed(0))
8322 };
8323 return root.estimateFee = function(nbInputs, nbOutputs, cb) {
8324 feeService.getCurrentFeeValue(function(err, feePerKb) {
8325 err && $log.debug(err);
8326 var size = _getEstimatedSize(nbInputs, nbOutputs);
8327 $log.debug("Estimated size: " + size);
8328 var fee = 1e4 * Math.ceil(2 + size / 1e3);
8329 return fee = fee < 1e4 ? 1e4 : fee, fee = 1e4 * Math.ceil(fee / 1e4), fee = parseInt(fee.toFixed(0)), $log.debug("Estimated fee: " + fee), cb(null, fee)
8330 })
8331 }, root.estimateCostOfIssuance = function(cb) {
8332 root.estimateFee(1, 3, function(err, fee) {
8333 var amount = fee + 1e9;
8334 return $log.debug("Estimated cost of issuance: " + amount), cb(err, fee, amount)
8335 })
8336 }, root.estimateCostOfTransfer = function(transferUnits, totalUnits, cb) {
8337 var hasChange = transferUnits < totalUnits,
8338 nOutputs = hasChange ? 3 : 2;
8339 root.estimateFee(2, nOutputs, function(err, fee) {
8340 var amount = hasChange ? fee + 600 : fee;
8341 return $log.debug("Estimated cost of transfer: " + amount), cb(err, fee, amount)
8342 })
8343 }, root
8344}), angular.module("copayAddon.orion").service("orion", Orion), angular.module("copayAddon.orion").directive("booleanIcon", function() {
8345 return {
8346 restrict: "E",
8347 scope: {
8348 value: "="
8349 },
8350 replace: !0,
8351 template: '<span><i class="fi-check" style="color:green" ng-show="value"></i><i class="fi-x" style="color:red" ng-show="!value"></i></span>'
8352 }
8353});