From 752d16f6c072b0dd54eb245209881a25ff06cb8e Mon Sep 17 00:00:00 2001 From: Frankie Date: Thu, 18 Aug 2016 10:40:35 -0700 Subject: [PATCH] WIP: ShapeShift tx --- app/scripts/lib/config-manager.js | 33 ++++ app/scripts/lib/idStore.js | 1 + app/scripts/metamask-controller.js | 6 + ui/app/account-detail.js | 11 +- ui/app/actions.js | 29 ++++ ui/app/app.js | 28 ++++ ui/app/components/qr-code.js | 13 +- ui/app/components/shift-list-item.js | 177 +++++++++++++++++++++ ui/app/components/transaction-list-item.js | 6 +- ui/app/components/transaction-list.js | 6 +- ui/app/reducers/app.js | 16 +- 11 files changed, 314 insertions(+), 12 deletions(-) create mode 100644 ui/app/components/shift-list-item.js diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index d14d3afc3..8e59d02f3 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -284,3 +284,36 @@ ConfigManager.prototype.getShouldntShowWarning = function () { var data = this.getData() return ('isEthConfirmed' in data) && data.isEthConfirmed } + +ConfigManager.prototype.getShapeShiftTxList = function () { + var data = this.getData() + var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : [] + shapeShiftTxList.forEach((tx) => { + if (tx.response.status !== 'complete'){ + var requestListner = function (request) { + tx.response = JSON.parse(this.responseText) + if (tx.status === 'complete') { + tx.completeTime = new Date().getTime() + } + } + + var shapShiftReq = new XMLHttpRequest() + shapShiftReq.addEventListener('load', requestListner) + shapShiftReq.open('GET', `https://shapeshift.io/txStat/${tx.depositAddress}`, true) + shapShiftReq.send() + } + }) + return shapeShiftTxList +} + +ConfigManager.prototype.createShapeShiftTx = function (depositAddress, depositType) { + var data = this.getData() + + var shapeShiftTx = {depositAddress, depositType, key: 'shapeshift', time: new Date().getTime(), response: {}} + if(!data.shapeShiftTxList) { + data.shapeShiftTxList = [shapeShiftTx] + } else { + data.shapeShiftTxList.push(shapeShiftTx) + } + this.setData(data) +} diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index f63e03ec6..55d5e5e61 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -100,6 +100,7 @@ IdentityStore.prototype.getState = function () { unconfMsgs: messageManager.unconfirmedMsgs(), messages: messageManager.getMsgList(), selectedAddress: configManager.getSelectedAccount(), + shapeShiftTxList: configManager.getShapeShiftTxList(), })) } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2c141a402..d34f594d7 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -59,6 +59,8 @@ module.exports = class MetamaskController { recoverSeed: idStore.recoverSeed.bind(idStore), // coinbase buyEth: this.buyEth.bind(this), + // shapeshift + createShapeShiftTx : this.createShapeShiftTx.bind(this), } } @@ -287,6 +289,10 @@ module.exports = class MetamaskController { }) } + createShapeShiftTx (depositAddress, depositType) { + this.configManager.createShapeShiftTx(depositAddress, depositType) + } + } function noop () {} diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 0053935f5..13188e20c 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -29,6 +29,7 @@ function mapStateToProps (state) { unconfTxs: valuesFor(state.metamask.unconfTxs), unconfMsgs: valuesFor(state.metamask.unconfMsgs), isEthWarningConfirmed: state.metamask.isEthConfirmed, + shapeShiftTxList : state.metamask.shapeShiftTxList, } } @@ -179,12 +180,7 @@ AccountDetailScreen.prototype.render = function () { position: 'absolute', left: '219px', }, - }, props.accountDetail.subview === 'buyForm' ? [h('i.fa.fa-arrow-left', { - style: { - width: '22.641px', - height: '14px', - }, - })] : 'BUY'), + }, 'BUY'), h('button', { onClick: () => props.dispatch(actions.showSendPage()), @@ -233,7 +229,7 @@ AccountDetailScreen.prototype.subview = function () { } AccountDetailScreen.prototype.transactionList = function () { - const { transactions, unconfTxs, unconfMsgs, address, network } = this.props + const { transactions, unconfTxs, unconfMsgs, address, network, shapeShiftTxList } = this.props var txsToRender = transactions // only transactions that are from the current address @@ -248,6 +244,7 @@ AccountDetailScreen.prototype.transactionList = function () { network, unconfTxs, unconfMsgs, + shapeShiftTxList, viewPendingTx: (txId) => { this.props.dispatch(actions.viewPendingTx(txId)) }, diff --git a/ui/app/actions.js b/ui/app/actions.js index 326c811af..48a425d50 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -133,6 +133,8 @@ var actions = { // QR STUFF: SHOW_QR: 'SHOW_QR', getQr: getQr, + reshowQrCode: reshowQrCode, + SHOW_QR_VIEW: 'SHOW_QR_VIEW', } module.exports = actions @@ -698,6 +700,7 @@ function coinShiftRquest (data, marketData) { if (response.error) return dispatch(actions.showWarning(response.error)) var message = ` Deposit your ${response.depositType} to the address bellow:` + _accountManager.createShapeShiftTx(response.deposit, response.depositType) dispatch(actions.getQr(response.deposit, '125x125', [message].concat(marketData))) }) } @@ -719,6 +722,32 @@ function getQr (data, size, message) { }) } } +function reshowQrCode (data, coin) { + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { + if (mktResponse.error) return dispatch(actions.showWarning(mktResponse.error)) + + var message =[ + `Deposit your ${coin} to the address bellow:`, + `Deposit Limit: ${mktResponse.limit}`, + `Deposit Minimum:${mktResponse.minimum}`, + ] + + qrRequest(data, '125x125', (response) => { + dispatch(actions.hideLoadingIndication()) + dispatch({ + type: actions.SHOW_QR_VIEW, + value: { + qr: response, + message: message, + data: data, + }, + }) + }) + }) + } +} function shapeShiftRequest (query, options, cb) { var queryResponse, method diff --git a/ui/app/app.js b/ui/app/app.js index 2d8b46ce8..84ff16ec8 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -29,6 +29,7 @@ const NetworkIndicator = require('./components/network') const Tooltip = require('./components/tooltip') const EthStoreWarning = require('./eth-store-warning') const BuyView = require('./components/buy-button-subview') +const QrView = require('./components/qr-code') module.exports = connect(mapStateToProps)(App) inherits(App, Component) @@ -359,6 +360,33 @@ App.prototype.renderPrimary = function () { return h(CreateVaultScreen, {key: 'createVault'}) case 'buyEth': return h(BuyView, {key: 'buyEthView'}) + case 'qr': + return h('div', { + style: { + position: 'absolute', + height: '100%', + top: '0px', + left: '0px', + }, + }, [ + h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { + onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)), + style: { + marginLeft: '10px', + marginTop: '50px', + }, + }), + h('div', { + style: { + position: 'absolute', + bottom: '115px', + left: '44px', + width: '285px', + }, + }, [ + h(QrView, {key: 'qr'}), + ]), + ]) default: return h(AccountDetailScreen, {key: 'account-detail'}) diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js index 1c744b234..b71eaa483 100644 --- a/ui/app/components/qr-code.js +++ b/ui/app/components/qr-code.js @@ -10,6 +10,7 @@ function mapStateToProps (state) { return { Qr: state.appState.Qr, buyView: state.appState.buyView, + warning: state.appState.warning, } } @@ -23,6 +24,7 @@ QrCodeView.prototype.render = function () { var props = this.props var Qr = props.Qr return h('.main-container.flex-column', { + key: 'qr', style: { justifyContent: 'center', padding: '45px', @@ -30,8 +32,17 @@ QrCodeView.prototype.render = function () { }, }, [ Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('h3', Qr.message), + + this.props.warning ? this.props.warning && h('span.error.flex-center', { + style: { + textAlign: 'center', + width: '229px', + height: '82px', + }, + }, + this.props.warning) : null, + h('#qr-container.flex-column', { - key: 'qr', style: { marginTop: '25px', marginBottom: '15px', diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js new file mode 100644 index 000000000..233920a1f --- /dev/null +++ b/ui/app/components/shift-list-item.js @@ -0,0 +1,177 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const vreme = new (require('vreme')) +const explorerLink = require('../../lib/explorer-link') +const extension = require('../../../app/scripts/lib/extension') +const actions = require('../actions') +const addressSummary = require('../util').addressSummary + +const CopyButton = require('./copyButton') +const EtherBalance = require('./eth-balance') + + +module.exports = connect(mapStateToProps)(ShiftListItem) + +function mapStateToProps (state) { + return {} +} + +inherits(ShiftListItem, Component) +function ShiftListItem () { + Component.call(this) +} + +ShiftListItem.prototype.render = function () { + const props = this.props + return ( + h(`.transaction-list-item.flex-row`, { + style:{ + paddingTop: '20px', + paddingBottom: '20px', + justifyContent: 'space-around', + alignItems: 'center', + }, + }, [ + h('div', { + style: { + width:'0px', + position: 'relative', + bottom: '19px', + }, + }, [ + h('img', { + src:'https://info.shapeshift.io/sites/default/files/logo.png', + style: { + height: '35px', + width: '132px', + position: 'absolute', + clip: 'rect(0px,23px,34px,0px)', + }, + }), + ]), + + this.renderInfo(), + this.renderUtilComponents(), + ]) + ) +} + +function formatDate (date) { + return vreme.format(new Date(date), 'March 16 2014 14:30') +} + +ShiftListItem.prototype.renderUtilComponents = function () { + var props = this.props + + switch (props.response.status) { + case 'no_deposits': + return h('.flex-row', [ + h(CopyButton, { value: this.props.depositAddress }), + h('i.fa.fa-qrcode.pointer', { + onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)), + style: { + margin: '5px', + marginLeft: '23px', + marginRight: '12px', + fontSize: '20px', + color: '#F7861C', + }, + }), + ]) + case 'received': + return h('.flex-row') + + case 'complete': + return h('.flex-row', [ + h(CopyButton, { value: this.props.response.transaction }), + h(EtherBalance, { + value: `+${props.response.outgoingCoin}`, + width: '55px', + shorten: true, + style: {fontSize: '15px'}, + }) + ]) + + case 'failed': + return '' + default: + return '' + } + +} + +ShiftListItem.prototype.renderInfo = function () { + var props = this.props + switch (props.response.status) { + case 'no_deposits': + return h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, [ + `${props.depositType} to ETH via ShapeShift` + ]), + h('div', `Status: ${props.response.status.replace('_', ' ')}`), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, formatDate(props.time)), + ]) + case 'received': + return h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, [ + `${props.depositType} to ETH via ShapeShift` + ]), + h('div', `Conversion in progress`), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, formatDate(props.time)), + ]) + case 'complete': + var url = explorerLink(props.response.transaction, parseInt('1')) + + return h('.flex-column.pointer', { + style: {width: '200px', overflow: 'hidden'}, + onClick: () => extension.tabs.create({ url }) + }, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, `ShapeShift`), + h('div', props.completeTime), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, addressSummary(props.response.transaction)), + ]) + + case 'failed': + return h('span.error', ' (Failed)') + default: + return '' + } +} diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 796ba61ae..b03ca11ad 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -10,7 +10,7 @@ const vreme = new (require('vreme')) const extension = require('../../../app/scripts/lib/extension') const TransactionIcon = require('./transaction-list-item-icon') - +const ShiftListItem = require('./shift-list-item') module.exports = TransactionListItem inherits(TransactionListItem, Component) @@ -20,7 +20,9 @@ function TransactionListItem () { TransactionListItem.prototype.render = function () { const { transaction, i, network } = this.props - + if (transaction.key === 'shapeshift') { + if (network === '1') return h(ShiftListItem, transaction) + } var date = formatDate(transaction.time) let isLinkable = false diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js index 886aa7c00..9bf0f6d81 100644 --- a/ui/app/components/transaction-list.js +++ b/ui/app/components/transaction-list.js @@ -14,7 +14,11 @@ function TransactionList () { TransactionList.prototype.render = function () { const { txsToRender, network, unconfMsgs } = this.props - const transactions = txsToRender.concat(unconfMsgs) + var shapeShiftTxList + if (network === '1'){ + shapeShiftTxList = this.props.shapeShiftTxList + } + const transactions = !shapeShiftTxList ? txsToRender.concat(unconfMsgs) : txsToRender.concat(unconfMsgs, shapeShiftTxList) .sort((a, b) => b.time - a.time) return ( diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 95b60f929..63804d478 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -476,7 +476,21 @@ function reduceApp (state, action) { data: action.value.data, }, }) - default: + + case actions.SHOW_QR_VIEW: + return extend(appState, { + currentView: { + name: 'qr', + context: appState.currentView.context, + }, + transForward: true, + Qr: { + message: action.value.message, + image: action.value.qr, + data: action.value.data, + }, + }) + default: return appState } }