diff --git a/app/images/logo-shapeshift.svg b/app/images/logo-shapeshift.svg new file mode 100644 index 0000000000..6cdbce03bc --- /dev/null +++ b/app/images/logo-shapeshift.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/images/shapeshift-dark.svg b/app/images/shapeshift-dark.svg new file mode 100644 index 0000000000..bec6eeed1d --- /dev/null +++ b/app/images/shapeshift-dark.svg @@ -0,0 +1 @@ +shapeshift_logo \ No newline at end of file diff --git a/app/includes/footer.tpl b/app/includes/footer.tpl index a32c017935..6f5c1ed73d 100644 --- a/app/includes/footer.tpl +++ b/app/includes/footer.tpl @@ -50,6 +50,8 @@

Consider using our affiliate links to...

Buy a...

+ +
+
Exchange coins & tokens
+
+
+ +
+
+
+
diff --git a/app/scripts/main.js b/app/scripts/main.js index c35c5a089c..eb7b084ed4 100644 --- a/app/scripts/main.js +++ b/app/scripts/main.js @@ -93,6 +93,7 @@ var walletBalanceCtrl = require('./controllers/walletBalanceCtrl'); var helpersCtrl = require('./controllers/helpersCtrl'); var globalService = require('./services/globalService'); var walletService = require('./services/walletService'); +var shapeShiftService = require('./services/shapeShiftService'); var blockiesDrtv = require('./directives/blockiesDrtv'); var addressFieldDrtv = require('./directives/addressFieldDrtv'); var QRCodeDrtv = require('./directives/QRCodeDrtv'); @@ -118,8 +119,9 @@ app.config(['$translateProvider', function($translateProvider) { app.config(['$animateProvider', function($animateProvider) { $animateProvider.classNameFilter(/^no-animate$/); }]); -app.factory('globalService', ['$http', '$httpParamSerializerJQLike', globalService]); +app.factory('globalService', ['$http', '$httpParamSerializerJQLike', '$rootScope', globalService]); app.factory('walletService', walletService); +app.factory('shapeShiftService', ['$http', shapeShiftService]); app.directive('blockieAddress', blockiesDrtv); app.directive('addressField', ['$compile', addressFieldDrtv]); app.directive('qrCode', QRCodeDrtv); @@ -136,7 +138,7 @@ app.controller('decryptWalletCtrl', ['$scope', '$sce', 'walletService', decryptW app.controller('viewWalletCtrl', ['$scope', 'walletService', viewWalletCtrl]); app.controller('txStatusCtrl', ['$scope', txStatusCtrl]); app.controller('sendTxCtrl', ['$scope', '$sce', 'walletService', '$rootScope', sendTxCtrl]); -app.controller('swapCtrl', ['$scope', '$sce', 'walletService', swapCtrl]); +app.controller('swapCtrl', ['$scope', 'shapeShiftService', swapCtrl]); app.controller('signMsgCtrl', ['$scope', '$sce', 'walletService', signMsgCtrl]); app.controller('contractsCtrl', ['$scope', '$sce', 'walletService', contractsCtrl]); app.controller('ensCtrl', ['$scope', '$sce', 'walletService', ensCtrl]); diff --git a/app/scripts/services/globalService.js b/app/scripts/services/globalService.js index 8a188db8da..a73078f7e4 100644 --- a/app/scripts/services/globalService.js +++ b/app/scripts/services/globalService.js @@ -1,6 +1,6 @@ 'use strict' -var globalService = function($http, $httpParamSerializerJQLike) { - +var globalService = function($http, $httpParamSerializerJQLike, $rootScope) { + $rootScope.seenSwap = !(globalFuncs.localStorage.getItem('seenSwap')); globalFuncs.checkAndRedirectHTTPS() ajaxReq.http = $http ajaxReq.postSerializer = $httpParamSerializerJQLike @@ -40,6 +40,7 @@ var globalService = function($http, $httpParamSerializerJQLike) { id: 4, name: "NAV_Swap", url: "swap", + badge: $rootScope.seenSwap, mew: true, cx: true }, diff --git a/app/scripts/services/shapeShiftService.js b/app/scripts/services/shapeShiftService.js new file mode 100644 index 0000000000..28f4fb9c79 --- /dev/null +++ b/app/scripts/services/shapeShiftService.js @@ -0,0 +1,188 @@ +'use strict'; + +let SOFT_MIN_CAP_ETH = 0.0001; +let SOFT_MIN_CAP_BTC = 0.00001; +let API_KEY = + '0ca1ccd50b708a3f8c02327f0caeeece06d3ddc1b0ac749a987b453ee0f4a29bdb5da2e53bc35e57fb4bb7ae1f43c93bb098c3c4716375fc1001c55d8c94c160'; +let SHAPE_SHIFT_BASE_URL = 'https://shapeshift.io'; + +var shapeShiftService = function($http) { + return { + // Status: No Deposits Received + // { + // status:"no_deposits", + // address:[address] //matches address submitted + // } + // + // Status: Received (we see a new deposit but have not finished processing it) + // { + // status:"received", + // address:[address] //matches address submitted + // } + // + // Status: Complete + // { + // status : "complete", + // address: [address], + // withdraw: [withdrawal address], + // incomingCoin: [amount deposited], + // incomingType: [coin type of deposit], + // outgoingCoin: [amount sent to withdrawal address], + // outgoingType: [coin type of withdrawal], + // transaction: [transaction id of coin sent to withdrawal address] + // } + // + // Status: Failed + // { + // status : "failed", + // error: [Text describing failure] + // } + checkStatus: function(address) { + return $http.get(`${SHAPE_SHIFT_BASE_URL}/txStat/${address}`).then(function(resp) { + return resp.data; + }); + }, + + // pair: [pair], + // withdrawal: [Withdrawal Address], //-- will match address submitted in post + // withdrawalAmount: [Withdrawal Amount], // Amount of the output coin you will receive + // deposit: [Deposit Address (or memo field if input coin is BTS / BITUSD)], + // depositAmount: [Deposit Amount], // Exact amount of input coin to send in + // expiration: [timestamp when this will expire], + // quotedRate: [the exchange rate to be honored] + // apiPubKey: [public API attached to this shift, if one was given] + sendAmount: function(withdrawal, originKind, destinationKind, destinationAmount) { + let pair = originKind.toLowerCase() + '_' + destinationKind.toLowerCase(); + return $http({ + url: `${SHAPE_SHIFT_BASE_URL}/sendamount`, + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: { withdrawal: withdrawal, pair: pair, amount: destinationAmount, apiKey: API_KEY } + }).then(function(resp) { + return resp.data.success; + }); + }, + + getMarketInfo: function() { + return $http.get(`${SHAPE_SHIFT_BASE_URL}/marketinfo`).then(function(resp) { + return resp.data; + }); + }, + + getPairRateFromMarketInfo: function(originKind, destinationKind, marketInfo) { + let pair = originKind.toUpperCase() + '_' + destinationKind.toUpperCase(); + let filteredArray = marketInfo.filter(function(obj) { + return obj.pair === pair; + }); + if (filteredArray.length > 0) { + let pairData = filteredArray[0]; + if (originKind === 'ETH') { + if (pairData.min < SOFT_MIN_CAP_ETH) { + pairData.min = SOFT_MIN_CAP_ETH; + } + } + if (originKind === 'BTC') { + if (pairData.min < SOFT_MIN_CAP_BTC) { + pairData.min = SOFT_MIN_CAP_BTC; + } + } + return pairData; + } else { + return { + min: 0, + limit: 0, + maxLimit: 0, + rate: 0, + pair: pair + } + } + }, + + getTimeRemaining: function(address) { + return $http.get(`${SHAPE_SHIFT_BASE_URL}/timeremaining/${address}`).then(function(resp) { + return resp.data; + }); + }, + + onlyAvailableCoins: function(coinsObj) { + let coinObjCopy = angular.copy(coinsObj); + Object.keys(coinObjCopy).forEach(function(key) { + if (!(coinObjCopy[key].status === 'available')) { + delete coinObjCopy[key]; + } + }); + return coinObjCopy; + }, + + getAvailableCoins: function(whiteListSymbolArray) { + let that = this; + return $http.get(`${SHAPE_SHIFT_BASE_URL}/getcoins`).then(function(resp) { + let availableCoins = that.onlyAvailableCoins(resp.data); + let whiteListedAvailableCoins = that.getWhiteListedCoins( + availableCoins, + whiteListSymbolArray + ); + return that + .attachRatesToCoins(whiteListedAvailableCoins, Object.keys(whiteListedAvailableCoins)) + .then(function(coinDataWithRates) { + return coinDataWithRates; + }); + }); + }, + + getWhiteListedCoins: function(coinsObj, whiteListSymbolArray) { + let filteredObj = {}; + whiteListSymbolArray.forEach(function(each) { + let coin = coinsObj[each]; + if (coin) { + filteredObj[each] = coin; + } + }); + return filteredObj; + }, + + attachRateToCoin: function(coinsObj, coinSymbol, originKind, marketInfo) { + let destinationKind = coinsObj[coinSymbol].symbol; + let pairRate = this.getPairRateFromMarketInfo(originKind, destinationKind, marketInfo); + if (!coinsObj[coinSymbol]['RATES']) { + coinsObj[coinSymbol]['RATES'] = {}; + } + coinsObj[coinSymbol]['RATES'][originKind] = pairRate; + }, + + clean: function(obj) { + for (var propName in obj) { + if (obj[propName] === null || obj[propName] === undefined) { + delete obj[propName]; + } + } + }, + + attachRatesToCoins: function(coinsObj, originKindArray) { + let defaultOrigin = ['BTC', 'ETH']; + if (!originKindArray) { + originKindArray = ['BTC', 'ETH']; + } + let that = this; + return this.getMarketInfo().then(function(marketInfo) { + Object.keys(coinsObj).forEach(function(coinSymbol) { + originKindArray.forEach(function(originKind) { + if (originKind !== coinSymbol) { + that.attachRateToCoin(coinsObj, coinSymbol, originKind, marketInfo); + that.clean(coinsObj[coinSymbol]['RATES'][originKind]); + } + }); + defaultOrigin.forEach(function(defaultOriginKind) { + if (defaultOriginKind !== coinSymbol) { + that.attachRateToCoin(coinsObj, coinSymbol, defaultOriginKind, marketInfo); + } + that.clean(coinsObj[coinSymbol]['RATES'][defaultOriginKind]); + }); + }); + return coinsObj; + }); + } + }; +}; + +module.exports = shapeShiftService; diff --git a/app/styles/bootstrap/dropdowns.less b/app/styles/bootstrap/dropdowns.less index e77e35fe74..4a785ec46e 100644 --- a/app/styles/bootstrap/dropdowns.less +++ b/app/styles/bootstrap/dropdowns.less @@ -23,7 +23,7 @@ // Prevent the focus on the dropdown toggle when closing dropdowns .dropdown-toggle { - margin-top: 0; + margin-top: -2px; font-size: @font-size-base; padding: @space-sm @space; } diff --git a/app/styles/bootstrap/forms.less b/app/styles/bootstrap/forms.less index 650fc7834f..9e6dda35a2 100644 --- a/app/styles/bootstrap/forms.less +++ b/app/styles/bootstrap/forms.less @@ -109,7 +109,7 @@ label + textarea, .form-control { display: block; width: 100%; - height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + height: 40px; // Make inputs at least the height of their button counterpart (base line-height + padding + border) padding: .25rem 1rem; font-size: @font-size-base; line-height: 2; diff --git a/app/styles/etherwallet-nav.less b/app/styles/etherwallet-nav.less index 9bcd4e0999..5155c7ed18 100644 --- a/app/styles/etherwallet-nav.less +++ b/app/styles/etherwallet-nav.less @@ -122,6 +122,22 @@ .nav-item { display: inline-block; font-size: 0; + position: relative; + .badge { + display: block; + position: absolute; + top: 0px; + right: -8px; + font-size: 9px; + letter-spacing: 1px; + font-weight: 400; + color: white; + padding: 0px 4px; + border-radius: 3px; + background-image: linear-gradient(-180deg,#ff1a1a 0%,#e60000 100%); + box-shadow: 0 3px 8px 0 rgba(0,0,0,0.1), inset 0 0 3px 0 rgba(0,0,0,0.1); + pointer-events: none; + } &.NAV_Swap a:before { content:""; display: inline-block; diff --git a/app/styles/etherwallet-new.less b/app/styles/etherwallet-new.less index f3efdd792d..ed60438606 100644 --- a/app/styles/etherwallet-new.less +++ b/app/styles/etherwallet-new.less @@ -290,10 +290,12 @@ a.footer__pill { .swap--usd, .swap--btc, -.swap--hw { +.swap--hw, +.swap--ss { text-align:center; display: flex; padding: 1rem .15rem; + min-height: 105px; .col-sm-7 { display: flex; flex-direction: column; @@ -330,6 +332,20 @@ a.footer__pill { } } +.swap--ss { + .block--text-white(); + background-color: #3a526d; + .swap-flag--bal, + .swap-flag--price { + background-color: #5478a0; + } + .swap__logo { + position: relative; + top: 50%; + transform: translatey(-50%); + } +} + .swap-flag--bal, .swap-flag--price { background-color: @brand-success; diff --git a/app/styles/etherwallet-swap.less b/app/styles/etherwallet-swap.less index ee0b7f8f67..05a5692194 100644 --- a/app/styles/etherwallet-swap.less +++ b/app/styles/etherwallet-swap.less @@ -13,6 +13,31 @@ vertical-align: middle; } + .new-feature-banner { + display: block; + width: fit-content; + margin: auto; + margin-top: 16px; + margin-bottom: -28px; + padding: 0px 16px; + background-color: #3a526d; + border-radius: 3px; + box-shadow: 0 3px 8px 0 rgba(0,0,0,0.1), inset 0 0 3px 0 rgba(0,0,0,0.1); + p { + display: inline-block; + color: white; + vertical-align: middle; + margin-bottom: 0px; + } + img { + display: inline-block; + height: 32px; + padding: 8px; + box-sizing: content-box; + margin-left: 16px; + } + } + .swap-rates { text-align: center; .order-panel { @@ -77,10 +102,37 @@ margin-right: 0; } .dropdown { + vertical-align: middle; + margin-top: -2px; margin-left: 0; } } + .swap-panel-input-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + .spacer { + flex-grow: 1; + @media screen and (max-width: 710px) { + flex-grow: 0; + } + } + .swap-panel-input-container-text, + .swap-panel-input { + margin: 0px; + padding: 0px 16px; + @media screen and (max-width: 710px) { + flex-grow: 1; + width: 100% + } + } + .swap-panel-input-container-text > h1 { + margin: 0px; + line-height: unset; + } + } + .swap-address { text-align: center; padding: @space*3 @space;