mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Fetch & display received transactions (#6996)
This commit is contained in:
parent
2f5d7ac8c3
commit
821529622e
222
app/scripts/controllers/incoming-transactions.js
Normal file
222
app/scripts/controllers/incoming-transactions.js
Normal file
@ -0,0 +1,222 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const log = require('loglevel')
|
||||
const BN = require('bn.js')
|
||||
const createId = require('../lib/random-id')
|
||||
const { bnToHex } = require('../lib/util')
|
||||
const {
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEYBY_CODE,
|
||||
KOVAN_CODE,
|
||||
ROPSTEN,
|
||||
RINKEBY,
|
||||
KOVAN,
|
||||
MAINNET,
|
||||
} = require('./network/enums')
|
||||
const networkTypeToIdMap = {
|
||||
[ROPSTEN]: ROPSTEN_CODE,
|
||||
[RINKEBY]: RINKEYBY_CODE,
|
||||
[KOVAN]: KOVAN_CODE,
|
||||
[MAINNET]: MAINNET_CODE,
|
||||
}
|
||||
|
||||
class IncomingTransactionsController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const {
|
||||
blockTracker,
|
||||
networkController,
|
||||
preferencesController,
|
||||
} = opts
|
||||
this.blockTracker = blockTracker
|
||||
this.networkController = networkController
|
||||
this.preferencesController = preferencesController
|
||||
this.getCurrentNetwork = () => networkController.getProviderConfig().type
|
||||
|
||||
const initState = Object.assign({
|
||||
incomingTransactions: {},
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
[ROPSTEN]: null,
|
||||
[RINKEBY]: null,
|
||||
[KOVAN]: null,
|
||||
[MAINNET]: null,
|
||||
},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this.networkController.on('networkDidChange', async (newType) => {
|
||||
const address = this.preferencesController.getSelectedAddress()
|
||||
await this._update({
|
||||
address,
|
||||
networkType: newType,
|
||||
})
|
||||
})
|
||||
this.blockTracker.on('latest', async (newBlockNumberHex) => {
|
||||
const address = this.preferencesController.getSelectedAddress()
|
||||
await this._update({
|
||||
address,
|
||||
newBlockNumberDec: parseInt(newBlockNumberHex, 16),
|
||||
})
|
||||
})
|
||||
this.preferencesController.store.subscribe(async ({ selectedAddress }) => {
|
||||
await this._update({
|
||||
address: selectedAddress,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async _update ({ address, newBlockNumberDec, networkType } = {}) {
|
||||
try {
|
||||
const dataForUpdate = await this._getDataForUpdate({ address, newBlockNumberDec, networkType })
|
||||
await this._updateStateWithNewTxData(dataForUpdate)
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async _getDataForUpdate ({ address, newBlockNumberDec, networkType } = {}) {
|
||||
const {
|
||||
incomingTransactions: currentIncomingTxs,
|
||||
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork,
|
||||
} = this.store.getState()
|
||||
|
||||
const network = networkType || this.getCurrentNetwork()
|
||||
const lastFetchBlockByCurrentNetwork = currentBlocksByNetwork[network]
|
||||
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec
|
||||
if (blockToFetchFrom === undefined) {
|
||||
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16)
|
||||
}
|
||||
|
||||
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(address, blockToFetchFrom, network)
|
||||
|
||||
return {
|
||||
latestIncomingTxBlockNumber,
|
||||
newTxs,
|
||||
currentIncomingTxs,
|
||||
currentBlocksByNetwork,
|
||||
fetchedBlockNumber: blockToFetchFrom,
|
||||
network,
|
||||
}
|
||||
}
|
||||
|
||||
async _updateStateWithNewTxData ({
|
||||
latestIncomingTxBlockNumber,
|
||||
newTxs,
|
||||
currentIncomingTxs,
|
||||
currentBlocksByNetwork,
|
||||
fetchedBlockNumber,
|
||||
network,
|
||||
}) {
|
||||
const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber
|
||||
? parseInt(latestIncomingTxBlockNumber, 10) + 1
|
||||
: fetchedBlockNumber + 1
|
||||
const newIncomingTransactions = {
|
||||
...currentIncomingTxs,
|
||||
}
|
||||
newTxs.forEach(tx => { newIncomingTransactions[tx.hash] = tx })
|
||||
|
||||
this.store.updateState({
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
...currentBlocksByNetwork,
|
||||
[network]: newLatestBlockHashByNetwork,
|
||||
},
|
||||
incomingTransactions: newIncomingTransactions,
|
||||
})
|
||||
}
|
||||
|
||||
async _fetchAll (address, fromBlock, networkType) {
|
||||
try {
|
||||
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, networkType)
|
||||
return this._processTxFetchResponse(fetchedTxResponse)
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async _fetchTxs (address, fromBlock, networkType) {
|
||||
let etherscanSubdomain = 'api'
|
||||
const currentNetworkID = networkTypeToIdMap[networkType]
|
||||
const supportedNetworkTypes = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
||||
|
||||
if (supportedNetworkTypes.indexOf(networkType) === -1) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (networkType !== MAINNET) {
|
||||
etherscanSubdomain = `api-${networkType}`
|
||||
}
|
||||
const apiUrl = `https://${etherscanSubdomain}.etherscan.io`
|
||||
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`
|
||||
|
||||
if (fromBlock) {
|
||||
url += `&startBlock=${parseInt(fromBlock, 10)}`
|
||||
}
|
||||
const response = await fetch(url)
|
||||
const parsedResponse = await response.json()
|
||||
|
||||
return {
|
||||
...parsedResponse,
|
||||
address,
|
||||
currentNetworkID,
|
||||
}
|
||||
}
|
||||
|
||||
_processTxFetchResponse ({ status, result, address, currentNetworkID }) {
|
||||
if (status !== '0' && result.length > 0) {
|
||||
const remoteTxList = {}
|
||||
const remoteTxs = []
|
||||
result.forEach((tx) => {
|
||||
if (!remoteTxList[tx.hash]) {
|
||||
remoteTxs.push(this._normalizeTxFromEtherscan(tx, currentNetworkID))
|
||||
remoteTxList[tx.hash] = 1
|
||||
}
|
||||
})
|
||||
|
||||
const incomingTxs = remoteTxs.filter(tx => tx.txParams.to && tx.txParams.to.toLowerCase() === address.toLowerCase())
|
||||
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1))
|
||||
|
||||
let latestIncomingTxBlockNumber = null
|
||||
incomingTxs.forEach((tx) => {
|
||||
if (
|
||||
tx.blockNumber &&
|
||||
(!latestIncomingTxBlockNumber ||
|
||||
parseInt(latestIncomingTxBlockNumber, 10) < parseInt(tx.blockNumber, 10))
|
||||
) {
|
||||
latestIncomingTxBlockNumber = tx.blockNumber
|
||||
}
|
||||
})
|
||||
return {
|
||||
latestIncomingTxBlockNumber,
|
||||
txs: incomingTxs,
|
||||
}
|
||||
}
|
||||
return {
|
||||
latestIncomingTxBlockNumber: null,
|
||||
txs: [],
|
||||
}
|
||||
}
|
||||
|
||||
_normalizeTxFromEtherscan (txMeta, currentNetworkID) {
|
||||
const time = parseInt(txMeta.timeStamp, 10) * 1000
|
||||
const status = txMeta.isError === '0' ? 'confirmed' : 'failed'
|
||||
return {
|
||||
blockNumber: txMeta.blockNumber,
|
||||
id: createId(),
|
||||
metamaskNetworkId: currentNetworkID,
|
||||
status,
|
||||
time,
|
||||
txParams: {
|
||||
from: txMeta.from,
|
||||
gas: bnToHex(new BN(txMeta.gas)),
|
||||
gasPrice: bnToHex(new BN(txMeta.gasPrice)),
|
||||
nonce: bnToHex(new BN(txMeta.nonce)),
|
||||
to: txMeta.to,
|
||||
value: bnToHex(new BN(txMeta.value)),
|
||||
},
|
||||
hash: txMeta.hash,
|
||||
transactionCategory: 'incoming',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IncomingTransactionsController
|
@ -30,6 +30,7 @@ const InfuraController = require('./controllers/infura')
|
||||
const CachedBalancesController = require('./controllers/cached-balances')
|
||||
const OnboardingController = require('./controllers/onboarding')
|
||||
const RecentBlocksController = require('./controllers/recent-blocks')
|
||||
const IncomingTransactionsController = require('./controllers/incoming-transactions')
|
||||
const MessageManager = require('./lib/message-manager')
|
||||
const PersonalMessageManager = require('./lib/personal-message-manager')
|
||||
const TypedMessageManager = require('./lib/typed-message-manager')
|
||||
@ -137,6 +138,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
networkController: this.networkController,
|
||||
})
|
||||
|
||||
this.incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: this.blockTracker,
|
||||
networkController: this.networkController,
|
||||
preferencesController: this.preferencesController,
|
||||
initState: initState.IncomingTransactionsController,
|
||||
})
|
||||
|
||||
// account tracker watches balances, nonces, and any code at their address.
|
||||
this.accountTracker = new AccountTracker({
|
||||
provider: this.provider,
|
||||
@ -270,6 +278,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
CachedBalancesController: this.cachedBalancesController.store,
|
||||
OnboardingController: this.onboardingController.store,
|
||||
ProviderApprovalController: this.providerApprovalController.store,
|
||||
IncomingTransactionsController: this.incomingTransactionsController.store,
|
||||
})
|
||||
|
||||
this.memStore = new ComposableObservableStore(null, {
|
||||
@ -294,6 +303,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// ProviderApprovalController
|
||||
ProviderApprovalController: this.providerApprovalController.store,
|
||||
ProviderApprovalControllerMemStore: this.providerApprovalController.memStore,
|
||||
IncomingTransactionsController: this.incomingTransactionsController.store,
|
||||
})
|
||||
this.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
}
|
||||
|
@ -63,6 +63,7 @@
|
||||
],
|
||||
"tokens": [],
|
||||
"transactions": {},
|
||||
"incomingTransactions": {},
|
||||
"selectedAddressTxList": [],
|
||||
"unapprovedTxs": {},
|
||||
"unapprovedMsgs": {
|
||||
|
@ -64,6 +64,7 @@
|
||||
],
|
||||
"tokens": [],
|
||||
"transactions": {},
|
||||
"incomingTransactions": {},
|
||||
"selectedAddressTxList": [],
|
||||
"unapprovedMsgs": {},
|
||||
"unapprovedMsgCount": 0,
|
||||
|
@ -28,6 +28,7 @@
|
||||
"conversionRate": 1200.88200327,
|
||||
"conversionDate": 1489013762,
|
||||
"noActiveNotices": true,
|
||||
"incomingTransactions": {},
|
||||
"frequentRpcList": [],
|
||||
"network": "3",
|
||||
"accounts": {
|
||||
|
@ -64,6 +64,7 @@
|
||||
],
|
||||
"tokens": [],
|
||||
"transactions": {},
|
||||
"incomingTransactions": {},
|
||||
"selectedAddressTxList": [
|
||||
{
|
||||
"err": {
|
||||
|
@ -12,6 +12,7 @@
|
||||
}
|
||||
},
|
||||
"cachedBalances": {},
|
||||
"incomingTransactions": {},
|
||||
"unapprovedTxs": {
|
||||
"8393540981007587": {
|
||||
"id": 8393540981007587,
|
||||
|
638
test/unit/app/controllers/incoming-transactions-test.js
Normal file
638
test/unit/app/controllers/incoming-transactions-test.js
Normal file
@ -0,0 +1,638 @@
|
||||
const assert = require('assert')
|
||||
const sinon = require('sinon')
|
||||
const proxyquire = require('proxyquire')
|
||||
const IncomingTransactionsController = proxyquire('../../../../app/scripts/controllers/incoming-transactions', {
|
||||
'../lib/random-id': () => 54321,
|
||||
})
|
||||
|
||||
const {
|
||||
ROPSTEN,
|
||||
RINKEBY,
|
||||
KOVAN,
|
||||
MAINNET,
|
||||
} = require('../../../../app/scripts/controllers/network/enums')
|
||||
|
||||
describe('IncomingTransactionsController', () => {
|
||||
const EMPTY_INIT_STATE = {
|
||||
incomingTransactions: {},
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
[ROPSTEN]: null,
|
||||
[RINKEBY]: null,
|
||||
[KOVAN]: null,
|
||||
[MAINNET]: null,
|
||||
},
|
||||
}
|
||||
|
||||
const NON_EMPTY_INIT_STATE = {
|
||||
incomingTransactions: {
|
||||
'0x123456': { id: 777 },
|
||||
},
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
[ROPSTEN]: 1,
|
||||
[RINKEBY]: 2,
|
||||
[KOVAN]: 3,
|
||||
[MAINNET]: 4,
|
||||
},
|
||||
}
|
||||
|
||||
const NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE = {
|
||||
incomingTransactions: {
|
||||
'0x123456': { id: 777 },
|
||||
},
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
[ROPSTEN]: 1,
|
||||
[RINKEBY]: 2,
|
||||
[KOVAN]: 3,
|
||||
[MAINNET]: 4,
|
||||
FAKE_NETWORK: 1111,
|
||||
},
|
||||
}
|
||||
|
||||
const MOCK_BLOCKTRACKER = {
|
||||
on: sinon.spy(),
|
||||
testProperty: 'fakeBlockTracker',
|
||||
getCurrentBlock: () => '0xa',
|
||||
}
|
||||
|
||||
const MOCK_NETWORK_CONTROLLER = {
|
||||
getProviderConfig: () => ({ type: 'FAKE_NETWORK' }),
|
||||
on: sinon.spy(),
|
||||
}
|
||||
|
||||
const MOCK_PREFERENCES_CONTROLLER = {
|
||||
getSelectedAddress: sinon.stub().returns('0x0101'),
|
||||
store: {
|
||||
subscribe: sinon.spy(),
|
||||
},
|
||||
}
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should set up correct store, listeners and properties in the constructor', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: {},
|
||||
})
|
||||
sinon.spy(incomingTransactionsController, '_update')
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.blockTracker, MOCK_BLOCKTRACKER)
|
||||
assert.deepEqual(incomingTransactionsController.networkController, MOCK_NETWORK_CONTROLLER)
|
||||
assert.equal(incomingTransactionsController.preferencesController, MOCK_PREFERENCES_CONTROLLER)
|
||||
assert.equal(incomingTransactionsController.getCurrentNetwork(), 'FAKE_NETWORK')
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.store.getState(), EMPTY_INIT_STATE)
|
||||
|
||||
assert(incomingTransactionsController.networkController.on.calledOnce)
|
||||
assert.equal(incomingTransactionsController.networkController.on.getCall(0).args[0], 'networkDidChange')
|
||||
const networkControllerListenerCallback = incomingTransactionsController.networkController.on.getCall(0).args[1]
|
||||
assert.equal(incomingTransactionsController._update.callCount, 0)
|
||||
networkControllerListenerCallback('testNetworkType')
|
||||
assert.equal(incomingTransactionsController._update.callCount, 1)
|
||||
assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], {
|
||||
address: '0x0101',
|
||||
networkType: 'testNetworkType',
|
||||
})
|
||||
|
||||
incomingTransactionsController._update.resetHistory()
|
||||
|
||||
assert(incomingTransactionsController.blockTracker.on.calledOnce)
|
||||
assert.equal(incomingTransactionsController.blockTracker.on.getCall(0).args[0], 'latest')
|
||||
const blockTrackerListenerCallback = incomingTransactionsController.blockTracker.on.getCall(0).args[1]
|
||||
assert.equal(incomingTransactionsController._update.callCount, 0)
|
||||
blockTrackerListenerCallback('0xabc')
|
||||
assert.equal(incomingTransactionsController._update.callCount, 1)
|
||||
assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], {
|
||||
address: '0x0101',
|
||||
newBlockNumberDec: 2748,
|
||||
})
|
||||
})
|
||||
|
||||
it('should set the store to a provided initial state', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.store.getState(), NON_EMPTY_INIT_STATE)
|
||||
})
|
||||
})
|
||||
|
||||
describe('_getDataForUpdate', () => {
|
||||
it('should call fetchAll with the correct params when passed a new block number and the current network has no stored block', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
||||
|
||||
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 })
|
||||
|
||||
assert(incomingTransactionsController._fetchAll.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
|
||||
'fakeAddress', 999, 'FAKE_NETWORK',
|
||||
])
|
||||
})
|
||||
|
||||
it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
||||
|
||||
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 })
|
||||
|
||||
assert(incomingTransactionsController._fetchAll.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
|
||||
'fakeAddress', 1111, 'FAKE_NETWORK',
|
||||
])
|
||||
})
|
||||
|
||||
it('should call fetchAll with the correct params when passed a new network type but no block info exists', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
||||
|
||||
await incomingTransactionsController._getDataForUpdate({
|
||||
address: 'fakeAddress',
|
||||
networkType: 'NEW_FAKE_NETWORK',
|
||||
})
|
||||
|
||||
assert(incomingTransactionsController._fetchAll.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
|
||||
'fakeAddress', 10, 'NEW_FAKE_NETWORK',
|
||||
])
|
||||
})
|
||||
|
||||
it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
||||
|
||||
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 })
|
||||
|
||||
assert(incomingTransactionsController._fetchAll.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
|
||||
'fakeAddress', 1111, 'FAKE_NETWORK',
|
||||
])
|
||||
})
|
||||
|
||||
it('should return the expected data', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({
|
||||
latestIncomingTxBlockNumber: 444,
|
||||
txs: [{ id: 555 }],
|
||||
})
|
||||
|
||||
const result = await incomingTransactionsController._getDataForUpdate({
|
||||
address: 'fakeAddress',
|
||||
networkType: 'FAKE_NETWORK',
|
||||
})
|
||||
|
||||
assert.deepEqual(result, {
|
||||
latestIncomingTxBlockNumber: 444,
|
||||
newTxs: [{ id: 555 }],
|
||||
currentIncomingTxs: {
|
||||
'0x123456': { id: 777 },
|
||||
},
|
||||
currentBlocksByNetwork: {
|
||||
[ROPSTEN]: 1,
|
||||
[RINKEBY]: 2,
|
||||
[KOVAN]: 3,
|
||||
[MAINNET]: 4,
|
||||
FAKE_NETWORK: 1111,
|
||||
},
|
||||
fetchedBlockNumber: 1111,
|
||||
network: 'FAKE_NETWORK',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_updateStateWithNewTxData', () => {
|
||||
const MOCK_INPUT_WITHOUT_LASTEST = {
|
||||
newTxs: [{ id: 555, hash: '0xfff' }],
|
||||
currentIncomingTxs: {
|
||||
'0x123456': { id: 777, hash: '0x123456' },
|
||||
},
|
||||
currentBlocksByNetwork: {
|
||||
[ROPSTEN]: 1,
|
||||
[RINKEBY]: 2,
|
||||
[KOVAN]: 3,
|
||||
[MAINNET]: 4,
|
||||
FAKE_NETWORK: 1111,
|
||||
},
|
||||
fetchedBlockNumber: 1111,
|
||||
network: 'FAKE_NETWORK',
|
||||
}
|
||||
|
||||
const MOCK_INPUT_WITH_LASTEST = {
|
||||
...MOCK_INPUT_WITHOUT_LASTEST,
|
||||
latestIncomingTxBlockNumber: 444,
|
||||
}
|
||||
|
||||
it('should update state with correct blockhash and transactions when passed a truthy latestIncomingTxBlockNumber', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
sinon.spy(incomingTransactionsController.store, 'updateState')
|
||||
|
||||
await incomingTransactionsController._updateStateWithNewTxData(MOCK_INPUT_WITH_LASTEST)
|
||||
|
||||
assert(incomingTransactionsController.store.updateState.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], {
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork,
|
||||
'FAKE_NETWORK': 445,
|
||||
},
|
||||
incomingTransactions: {
|
||||
'0x123456': { id: 777, hash: '0x123456' },
|
||||
'0xfff': { id: 555, hash: '0xfff' },
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should update state with correct blockhash and transactions when passed a falsy latestIncomingTxBlockNumber', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
sinon.spy(incomingTransactionsController.store, 'updateState')
|
||||
|
||||
await incomingTransactionsController._updateStateWithNewTxData(MOCK_INPUT_WITHOUT_LASTEST)
|
||||
|
||||
assert(incomingTransactionsController.store.updateState.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], {
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork,
|
||||
'FAKE_NETWORK': 1112,
|
||||
},
|
||||
incomingTransactions: {
|
||||
'0x123456': { id: 777, hash: '0x123456' },
|
||||
'0xfff': { id: 555, hash: '0xfff' },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_fetchTxs', () => {
|
||||
const mockFetch = sinon.stub().returns(Promise.resolve({
|
||||
json: () => Promise.resolve({ someKey: 'someValue' }),
|
||||
}))
|
||||
let tempFetch
|
||||
beforeEach(() => {
|
||||
tempFetch = global.fetch
|
||||
global.fetch = mockFetch
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = tempFetch
|
||||
mockFetch.resetHistory()
|
||||
})
|
||||
|
||||
it('should call fetch with the expected url when passed an address, block number and supported network', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN)
|
||||
|
||||
assert(mockFetch.calledOnce)
|
||||
assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`)
|
||||
})
|
||||
|
||||
it('should call fetch with the expected url when passed an address, block number and MAINNET', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', MAINNET)
|
||||
|
||||
assert(mockFetch.calledOnce)
|
||||
assert.equal(mockFetch.getCall(0).args[0], `https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`)
|
||||
})
|
||||
|
||||
it('should call fetch with the expected url when passed an address and supported network, but a falsy block number', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
await incomingTransactionsController._fetchTxs('0xfakeaddress', null, ROPSTEN)
|
||||
|
||||
assert(mockFetch.calledOnce)
|
||||
assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`)
|
||||
})
|
||||
|
||||
it('should not fetch and return an empty object when passed an unsported network', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', null, 'UNSUPPORTED_NETWORK')
|
||||
|
||||
assert(mockFetch.notCalled)
|
||||
assert.deepEqual(result, {})
|
||||
})
|
||||
|
||||
it('should return the results from the fetch call, plus the address and currentNetworkID, when passed an address, block number and supported network', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN)
|
||||
|
||||
assert(mockFetch.calledOnce)
|
||||
assert.deepEqual(result, {
|
||||
someKey: 'someValue',
|
||||
address: '0xfakeaddress',
|
||||
currentNetworkID: 3,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_processTxFetchResponse', () => {
|
||||
it('should return a null block number and empty tx array if status is 0', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._processTxFetchResponse({
|
||||
status: '0',
|
||||
result: [{ id: 1 }],
|
||||
address: '0xfakeaddress',
|
||||
})
|
||||
|
||||
assert.deepEqual(result, {
|
||||
latestIncomingTxBlockNumber: null,
|
||||
txs: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('should return a null block number and empty tx array if the passed result array is empty', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._processTxFetchResponse({
|
||||
status: '1',
|
||||
result: [],
|
||||
address: '0xfakeaddress',
|
||||
})
|
||||
|
||||
assert.deepEqual(result, {
|
||||
latestIncomingTxBlockNumber: null,
|
||||
txs: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the expected block number and tx list when passed data from a successful fetch', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
incomingTransactionsController._normalizeTxFromEtherscan = (tx, currentNetworkID) => ({
|
||||
...tx,
|
||||
currentNetworkID,
|
||||
normalized: true,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._processTxFetchResponse({
|
||||
status: '1',
|
||||
address: '0xfakeaddress',
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
result: [
|
||||
{
|
||||
hash: '0xabc123',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 10,
|
||||
},
|
||||
{
|
||||
hash: '0xabc123',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 10,
|
||||
},
|
||||
{
|
||||
hash: '0xabc1234',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 9,
|
||||
},
|
||||
{
|
||||
hash: '0xabc12345',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5001,
|
||||
time: 11,
|
||||
},
|
||||
{
|
||||
hash: '0xabc123456',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5001,
|
||||
time: 12,
|
||||
},
|
||||
{
|
||||
hash: '0xabc1234567',
|
||||
txParams: {
|
||||
to: '0xanotherFakeaddress',
|
||||
},
|
||||
blockNumber: 5002,
|
||||
time: 13,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
assert.deepEqual(result, {
|
||||
latestIncomingTxBlockNumber: 5001,
|
||||
txs: [
|
||||
{
|
||||
hash: '0xabc1234',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 9,
|
||||
normalized: true,
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
},
|
||||
{
|
||||
hash: '0xabc123',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 10,
|
||||
normalized: true,
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
},
|
||||
{
|
||||
hash: '0xabc12345',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5001,
|
||||
time: 11,
|
||||
normalized: true,
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
},
|
||||
{
|
||||
hash: '0xabc123456',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5001,
|
||||
time: 12,
|
||||
normalized: true,
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_normalizeTxFromEtherscan', () => {
|
||||
it('should return the expected data when the tx is in error', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._normalizeTxFromEtherscan({
|
||||
timeStamp: '4444',
|
||||
isError: '1',
|
||||
blockNumber: 333,
|
||||
from: '0xa',
|
||||
gas: '11',
|
||||
gasPrice: '12',
|
||||
nonce: '13',
|
||||
to: '0xe',
|
||||
value: '15',
|
||||
hash: '0xg',
|
||||
}, 'FAKE_NETWORK')
|
||||
|
||||
assert.deepEqual(result, {
|
||||
blockNumber: 333,
|
||||
id: 54321,
|
||||
metamaskNetworkId: 'FAKE_NETWORK',
|
||||
status: 'failed',
|
||||
time: 4444000,
|
||||
txParams: {
|
||||
from: '0xa',
|
||||
gas: '0xb',
|
||||
gasPrice: '0xc',
|
||||
nonce: '0xd',
|
||||
to: '0xe',
|
||||
value: '0xf',
|
||||
},
|
||||
hash: '0xg',
|
||||
transactionCategory: 'incoming',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the expected data when the tx is not in error', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._normalizeTxFromEtherscan({
|
||||
timeStamp: '4444',
|
||||
isError: '0',
|
||||
blockNumber: 333,
|
||||
from: '0xa',
|
||||
gas: '11',
|
||||
gasPrice: '12',
|
||||
nonce: '13',
|
||||
to: '0xe',
|
||||
value: '15',
|
||||
hash: '0xg',
|
||||
}, 'FAKE_NETWORK')
|
||||
|
||||
assert.deepEqual(result, {
|
||||
blockNumber: 333,
|
||||
id: 54321,
|
||||
metamaskNetworkId: 'FAKE_NETWORK',
|
||||
status: 'confirmed',
|
||||
time: 4444000,
|
||||
txParams: {
|
||||
from: '0xa',
|
||||
gas: '0xb',
|
||||
gasPrice: '0xc',
|
||||
nonce: '0xd',
|
||||
to: '0xe',
|
||||
value: '0xf',
|
||||
},
|
||||
hash: '0xg',
|
||||
transactionCategory: 'incoming',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -36,6 +36,7 @@ export default class TransactionListItem extends PureComponent {
|
||||
rpcPrefs: PropTypes.object,
|
||||
data: PropTypes.string,
|
||||
getContractMethodData: PropTypes.func,
|
||||
isDeposit: PropTypes.bool,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@ -117,7 +118,7 @@ export default class TransactionListItem extends PureComponent {
|
||||
}
|
||||
|
||||
renderPrimaryCurrency () {
|
||||
const { token, primaryTransaction: { txParams: { data } = {} } = {}, value } = this.props
|
||||
const { token, primaryTransaction: { txParams: { data } = {} } = {}, value, isDeposit } = this.props
|
||||
|
||||
return token
|
||||
? (
|
||||
@ -132,7 +133,7 @@ export default class TransactionListItem extends PureComponent {
|
||||
className="transaction-list-item__amount transaction-list-item__amount--primary"
|
||||
value={value}
|
||||
type={PRIMARY}
|
||||
prefix="-"
|
||||
prefix={isDeposit ? '' : '-'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -21,8 +21,10 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const { showFiatInTestnets } = preferencesSelector(state)
|
||||
const isMainnet = getIsMainnet(state)
|
||||
const { transactionGroup: { primaryTransaction } = {} } = ownProps
|
||||
const { txParams: { gas: gasLimit, gasPrice, data } = {} } = primaryTransaction
|
||||
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance
|
||||
const { txParams: { gas: gasLimit, gasPrice, data, to } = {} } = primaryTransaction
|
||||
const selectedAddress = getSelectedAddress(state)
|
||||
const selectedAccountBalance = accounts[selectedAddress].balance
|
||||
const isDeposit = selectedAddress === to
|
||||
const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
|
||||
const { rpcPrefs } = selectRpcInfo || {}
|
||||
|
||||
@ -42,6 +44,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
selectedAccountBalance,
|
||||
hasEnoughCancelGas,
|
||||
rpcPrefs,
|
||||
isDeposit,
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,12 +71,13 @@ const mapDispatchToProps = dispatch => {
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { transactionGroup: { primaryTransaction, initialTransaction } = {} } = ownProps
|
||||
const { isDeposit } = stateProps
|
||||
const { retryTransaction, ...restDispatchProps } = dispatchProps
|
||||
const { txParams: { nonce, data } = {}, time } = initialTransaction
|
||||
const { txParams: { nonce, data } = {}, time = 0 } = initialTransaction
|
||||
const { txParams: { value } = {} } = primaryTransaction
|
||||
|
||||
const tokenData = data && getTokenData(data)
|
||||
const nonceAndDate = nonce ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time)
|
||||
const nonceAndDate = nonce && !isDeposit ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time)
|
||||
|
||||
return {
|
||||
...stateProps,
|
||||
|
@ -11,15 +11,34 @@
|
||||
}
|
||||
|
||||
&__header {
|
||||
flex: 0 0 auto;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: $Grey-400;
|
||||
border-bottom: 1px solid $Grey-100;
|
||||
padding: 8px 0 8px 20px;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding: 8px 0 8px 16px;
|
||||
&__tabs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__tab,
|
||||
&__tab--selected {
|
||||
flex: 0 0 auto;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: $Grey-400;
|
||||
padding: 8px 0 8px 20px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding: 8px 0 8px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__tab--selected {
|
||||
font-weight: bold;
|
||||
color: $Blue-400;
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,5 +20,6 @@ export const TRANSFER_FROM_ACTION_KEY = 'transferFrom'
|
||||
export const SIGNATURE_REQUEST_KEY = 'signatureRequest'
|
||||
export const CONTRACT_INTERACTION_KEY = 'contractInteraction'
|
||||
export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt'
|
||||
export const DEPOSIT_TRANSACTION_KEY = 'deposit'
|
||||
|
||||
export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift'
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
SIGNATURE_REQUEST_KEY,
|
||||
CONTRACT_INTERACTION_KEY,
|
||||
CANCEL_ATTEMPT_ACTION_KEY,
|
||||
DEPOSIT_TRANSACTION_KEY,
|
||||
} from '../constants/transactions'
|
||||
|
||||
import log from 'loglevel'
|
||||
@ -124,6 +125,10 @@ export function isTokenMethodAction (transactionCategory) {
|
||||
export function getTransactionActionKey (transaction) {
|
||||
const { msgParams, type, transactionCategory } = transaction
|
||||
|
||||
if (transactionCategory === 'incoming') {
|
||||
return DEPOSIT_TRANSACTION_KEY
|
||||
}
|
||||
|
||||
if (type === 'cancel') {
|
||||
return CANCEL_ATTEMPT_ACTION_KEY
|
||||
}
|
||||
|
@ -10,11 +10,16 @@ import {
|
||||
TRANSACTION_TYPE_RETRY,
|
||||
} from '../../../app/scripts/controllers/transactions/enums'
|
||||
import { hexToDecimal } from '../helpers/utils/conversions.util'
|
||||
|
||||
import { selectedTokenAddressSelector } from './tokens'
|
||||
import txHelper from '../../lib/tx-helper'
|
||||
|
||||
export const shapeShiftTxListSelector = state => state.metamask.shapeShiftTxList
|
||||
|
||||
export const incomingTxListSelector = state => {
|
||||
const selectedAddress = state.metamask.selectedAddress
|
||||
return Object.values(state.metamask.incomingTransactions)
|
||||
.filter(({ txParams }) => txParams.to === selectedAddress)
|
||||
}
|
||||
export const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
|
||||
export const selectedAddressTxListSelector = state => state.metamask.selectedAddressTxList
|
||||
export const unapprovedPersonalMsgsSelector = state => state.metamask.unapprovedPersonalMsgs
|
||||
@ -55,9 +60,10 @@ export const transactionsSelector = createSelector(
|
||||
selectedTokenAddressSelector,
|
||||
unapprovedMessagesSelector,
|
||||
shapeShiftTxListSelector,
|
||||
incomingTxListSelector,
|
||||
selectedAddressTxListSelector,
|
||||
(selectedTokenAddress, unapprovedMessages = [], shapeShiftTxList = [], transactions = []) => {
|
||||
const txsToRender = transactions.concat(unapprovedMessages, shapeShiftTxList)
|
||||
(selectedTokenAddress, unapprovedMessages = [], shapeShiftTxList = [], incomingTxList = [], transactions = []) => {
|
||||
const txsToRender = transactions.concat(unapprovedMessages, shapeShiftTxList, incomingTxList)
|
||||
|
||||
return selectedTokenAddress
|
||||
? txsToRender
|
||||
@ -158,17 +164,18 @@ const insertTransactionGroupByTime = (transactionGroups, transactionGroup) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @name mergeShapeshiftTransactionGroups
|
||||
* @name mergeNonNonceTransactionGroups
|
||||
* @private
|
||||
* @description Inserts (mutates) shapeshift transactionGroups into an array of nonce-ordered
|
||||
* transactionGroups by time. Shapeshift transactionGroups need to be sorted by time within the list
|
||||
* of transactions as they do not have nonces.
|
||||
* @description Inserts (mutates) transactionGroups that are not to be ordered by nonce into an array
|
||||
* of nonce-ordered transactionGroups by time. Shapeshift transactionGroups need to be sorted by time
|
||||
* within the list of transactions as they do not have nonces.
|
||||
* @param {transactionGroup[]} orderedTransactionGroups - Array of transactionGroups ordered by
|
||||
* nonce.
|
||||
* @param {transactionGroup[]} shapeshiftTransactionGroups - Array of shapeshift transactionGroups
|
||||
* @param {transactionGroup[]} nonNonceTransactionGroups - Array of transactionGroups not intended to be ordered by nonce,
|
||||
* but intended to be ordered by timestamp
|
||||
*/
|
||||
const mergeShapeshiftTransactionGroups = (orderedTransactionGroups, shapeshiftTransactionGroups) => {
|
||||
shapeshiftTransactionGroups.forEach(shapeshiftGroup => {
|
||||
const mergeNonNonceTransactionGroups = (orderedTransactionGroups, nonNonceTransactionGroups) => {
|
||||
nonNonceTransactionGroups.forEach(shapeshiftGroup => {
|
||||
insertTransactionGroupByTime(orderedTransactionGroups, shapeshiftGroup)
|
||||
})
|
||||
}
|
||||
@ -183,13 +190,14 @@ export const nonceSortedTransactionsSelector = createSelector(
|
||||
(transactions = []) => {
|
||||
const unapprovedTransactionGroups = []
|
||||
const shapeshiftTransactionGroups = []
|
||||
const incomingTransactionGroups = []
|
||||
const orderedNonces = []
|
||||
const nonceToTransactionsMap = {}
|
||||
|
||||
transactions.forEach(transaction => {
|
||||
const { txParams: { nonce } = {}, status, type, time: txTime, key } = transaction
|
||||
const { txParams: { nonce } = {}, status, type, time: txTime, key, transactionCategory } = transaction
|
||||
|
||||
if (typeof nonce === 'undefined') {
|
||||
if (typeof nonce === 'undefined' || transactionCategory === 'incoming') {
|
||||
const transactionGroup = {
|
||||
transactions: [transaction],
|
||||
initialTransaction: transaction,
|
||||
@ -200,6 +208,8 @@ export const nonceSortedTransactionsSelector = createSelector(
|
||||
|
||||
if (key === 'shapeshift') {
|
||||
shapeshiftTransactionGroups.push(transactionGroup)
|
||||
} else if (transactionCategory === 'incoming') {
|
||||
incomingTransactionGroups.push(transactionGroup)
|
||||
} else {
|
||||
insertTransactionGroupByTime(unapprovedTransactionGroups, transactionGroup)
|
||||
}
|
||||
@ -245,7 +255,8 @@ export const nonceSortedTransactionsSelector = createSelector(
|
||||
})
|
||||
|
||||
const orderedTransactionGroups = orderedNonces.map(nonce => nonceToTransactionsMap[nonce])
|
||||
mergeShapeshiftTransactionGroups(orderedTransactionGroups, shapeshiftTransactionGroups)
|
||||
mergeNonNonceTransactionGroups(orderedTransactionGroups, shapeshiftTransactionGroups)
|
||||
mergeNonNonceTransactionGroups(orderedTransactionGroups, incomingTransactionGroups)
|
||||
return unapprovedTransactionGroups.concat(orderedTransactionGroups)
|
||||
}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user