Merge pull request #559 from MetaMask/shapeshiftTx

Shapeshift tx
feature/default_network_editable
Dan Finlay 8 years ago committed by GitHub
commit 30af7e96cb
  1. 2
      CHANGELOG.md
  2. 35
      app/scripts/lib/config-manager.js
  3. 1
      app/scripts/lib/idStore.js
  4. 5
      app/scripts/metamask-controller.js
  5. 2
      development/states.js
  6. 163
      development/states/account-detail-with-shapeshift-tx.json
  7. 11
      ui/app/account-detail.js
  8. 29
      ui/app/actions.js
  9. 28
      ui/app/app.js
  10. 4
      ui/app/components/buy-button-subview.js
  11. 4
      ui/app/components/eth-balance.js
  12. 13
      ui/app/components/qr-code.js
  13. 2
      ui/app/components/shapeshift-form.js
  14. 200
      ui/app/components/shift-list-item.js
  15. 6
      ui/app/components/transaction-list-item.js
  16. 6
      ui/app/components/transaction-list.js
  17. 4
      ui/app/css/index.css
  18. 14
      ui/app/reducers/app.js

@ -2,6 +2,8 @@
## Current Master
- Added ShapeShift to the transaction history
- Added affiliate key to Shapeshift requests
- Added feature to reflect current conversion rates of current vault balance.
- Modify balance display logic.

@ -332,3 +332,38 @@ 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.response.status === 'complete') {
tx.time = new Date().getTime()
}
}
var shapShiftReq = new XMLHttpRequest()
shapShiftReq.addEventListener('load', requestListner)
shapShiftReq.open('GET', `https://shapeshift.io/txStat/${tx.depositAddress}`, true)
shapShiftReq.send()
}
})
this.setData(data)
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)
}

@ -100,6 +100,7 @@ IdentityStore.prototype.getState = function () {
unconfMsgs: messageManager.unconfirmedMsgs(),
messages: messageManager.getMsgList(),
selectedAddress: configManager.getSelectedAccount(),
shapeShiftTxList: configManager.getShapeShiftTxList(),
currentFiat: configManager.getCurrentFiat(),
conversionRate: configManager.getConversionRate(),
conversionDate: configManager.getConversionDate(),

@ -64,6 +64,8 @@ module.exports = class MetamaskController {
recoverSeed: idStore.recoverSeed.bind(idStore),
// coinbase
buyEth: this.buyEth.bind(this),
// shapeshift
createShapeShiftTx: this.createShapeShiftTx.bind(this),
}
}
@ -317,6 +319,9 @@ module.exports = class MetamaskController {
})
}
createShapeShiftTx (depositAddress, depositType) {
this.configManager.createShapeShiftTx(depositAddress, depositType)
}
}
function noop () {}

File diff suppressed because one or more lines are too long

@ -0,0 +1,163 @@
{
"metamask": {
"currentFiat": "USD",
"conversionRate": 11.06608791,
"conversionDate": 1470421024,
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"shapeShiftTxList":[
{
"depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw",
"depositType": "BTC",
"key": "shapeshift",
"time": 1471564825772,
"response": {
"status": "complete",
"outgoingCoin": "100.00",
"incomingCoin": "1.000",
"transaction": "0x3701e0ac344a12a1fc5417cf251109a7c41f3edab922310202630d9c012414c8"
}
},
{
"depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw",
"depositType": "BTC",
"key": "shapeshift",
"time": 1471566579224,
"response": {
"status": "no_deposits",
"depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw"
}
},
{
"depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw",
"depositType": "BTC",
"key": "shapeshift",
"time": 1471566565378,
"response": {
"status": "received",
"depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw"
}
}
],
"transactions": [ {
"id": 1467921503489592,
"txParams": {
"from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"to": "0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761",
"value": "0x66c899104aa57038000",
"origin": "thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com",
"metamaskId": 1467921503489592,
"metamaskNetworkId": "2"
},
"time": 1467921503489,
"status": "rejected",
"containsDelegateCall": false
},
{
"id": 1467923203344608,
"txParams": {
"from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"to": "0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761",
"value": "0x0",
"origin": "thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com",
"metamaskId": 1467923203344608,
"metamaskNetworkId": "2"
},
"time": 1467923203344,
"status": "confirmed",
"containsDelegateCall": false,
"hash": "0x957bbba51e2732a86c10c5e7e8a484a093795a06f2e2c38ad02da1b20aeca620"
},
{
"id": 1467921503489592,
"txParams": {
"from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"to": "0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761",
"value": "0x66c899104aa57038000",
"origin": "thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com",
"metamaskId": 1467921503489592,
"metamaskNetworkId": "2"
},
"time": 1467921503489,
"status": "confirmed",
"containsDelegateCall": false
}],
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"name": "Wallet 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"mayBeFauceting": false
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"name": "Wallet 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"mayBeFauceting": false
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"name": "Wallet 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"mayBeFauceting": false
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"name": "Wallet 4",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
}
},
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "1",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "mainnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail",
"detailView": null,
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "127.0.0.1:9966",
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

@ -30,6 +30,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))
},

@ -135,6 +135,8 @@ var actions = {
// QR STUFF:
SHOW_QR: 'SHOW_QR',
getQr: getQr,
reshowQrCode: reshowQrCode,
SHOW_QR_VIEW: 'SHOW_QR_VIEW',
}
module.exports = actions
@ -717,6 +719,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)))
})
}
@ -738,6 +741,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

@ -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'})

@ -54,7 +54,7 @@ BuyButtonSubview.prototype.render = function () {
justifyContent: 'space-around',
},
}, [
h(currentForm.coinbase ? '.activeForm' : '.inactiveForm', {
h(currentForm.coinbase ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => props.dispatch(actions.coinBaseSubview()),
}, 'Coinbase'),
h('a', {
@ -67,7 +67,7 @@ BuyButtonSubview.prototype.render = function () {
},
}),
]),
h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm', {
h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => props.dispatch(actions.shapeShiftSubview(props.provider.type)),
}, 'Shapeshift'),

@ -21,10 +21,10 @@ EthBalanceComponent.prototype.render = function () {
return (
h('.ether-balance', {
h('.ether-balance.ether-balance-amount', {
style: style,
}, [
h('.ether-balance-amount', {
h('div', {
style: {
display: 'inline',
width: width,

@ -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',

@ -202,6 +202,8 @@ ShapeshiftForm.prototype.shift = function () {
'withdrawal': withdrawal,
'pair': pair,
'returnAddress': returnAddress,
// Public api key
'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6',
}
var message = [
`Deposit Limit: ${props.buyView.formView.marketinfo.limit}`,

@ -0,0 +1,200 @@
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')
const Tooltip = require('./tooltip')
module.exports = connect(mapStateToProps)(ShiftListItem)
function mapStateToProps (state) {
return {}
}
inherits(ShiftListItem, Component)
function ShiftListItem () {
Component.call(this)
}
ShiftListItem.prototype.render = function () {
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(Tooltip, {
title: 'QR Code',
}, [
h('i.fa.fa-qrcode.pointer.pop-hover', {
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',
color: '#01888C',
},
}),
])
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', 'No deposits received'),
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%',
},
}, 'From ShapeShift'),
h('div', formatDate(props.time)),
h('div', {
style: {
fontSize: 'x-small',
color: '#ABA9AA',
width: '100%',
},
}, addressSummary(props.response.transaction)),
])
case 'failed':
return h('span.error', '(Failed)')
default:
return ''
}
}

@ -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

@ -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 (

@ -592,3 +592,7 @@ div.message-container > div:first-child {
font-size: 15px;
color: #4D4D4D;
}
.pop-hover:hover {
transform: scale(1.1);
}

@ -476,6 +476,20 @@ function reduceApp (state, action) {
data: action.value.data,
},
})
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
}

Loading…
Cancel
Save