@exodus/activity-txs 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +14 -1
- package/package.json +21 -16
- package/redux/constants/tx-types.js +9 -0
- package/redux/selectors/create-asset-source-base-activity.js +126 -0
- package/redux/selectors/create-full-activity.js +34 -0
- package/redux/selectors/create-multi-activity.js +56 -0
- package/redux/selectors/create-with-connections-activity.js +54 -0
- package/redux/selectors/create-with-fiat-activity.js +62 -0
- package/redux/selectors/create-with-nfts-activity.js +81 -0
- package/redux/selectors/index.js +12 -0
- package/redux/utils/formatters.js +107 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [4.1.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/activity-txs@4.0.1...@exodus/activity-txs@4.1.0) (2024-05-13)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- activity base selector ([#6712](https://github.com/ExodusMovement/exodus-hydra/issues/6712)) ([929a87c](https://github.com/ExodusMovement/exodus-hydra/commit/929a87ce359195aafb177248eed3f0c4fe544452))
|
|
11
|
+
- add withFiat selector ([#6766](https://github.com/ExodusMovement/exodus-hydra/issues/6766)) ([10c9aaf](https://github.com/ExodusMovement/exodus-hydra/commit/10c9aaf8e28c2ad13885e4f1c5d8ff6f345d6508))
|
|
12
|
+
- connections activity selector ([#6855](https://github.com/ExodusMovement/exodus-hydra/issues/6855)) ([37bc022](https://github.com/ExodusMovement/exodus-hydra/commit/37bc0221be3a6c536b0fbf5f6ee22b06e084667d))
|
|
13
|
+
- full activity selector ([#6861](https://github.com/ExodusMovement/exodus-hydra/issues/6861)) ([7d0d168](https://github.com/ExodusMovement/exodus-hydra/commit/7d0d1689c37e14169a24fd73670183c282f0ad41))
|
|
14
|
+
- multi activity ([#6870](https://github.com/ExodusMovement/exodus-hydra/issues/6870)) ([7d72596](https://github.com/ExodusMovement/exodus-hydra/commit/7d72596b9718511f5e3ca43f4b36a5890a7b6b76))
|
|
15
|
+
- withNfts selectors ([#6765](https://github.com/ExodusMovement/exodus-hydra/issues/6765)) ([1f6d3d6](https://github.com/ExodusMovement/exodus-hydra/commit/1f6d3d668c32b184ca26cdbbb47c418c007ea7a1))
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
- missing dependencies ([#5322](https://github.com/ExodusMovement/exodus-hydra/issues/5322)) ([01efedc](https://github.com/ExodusMovement/exodus-hydra/commit/01efedc7508fb14925277fdcd388afb721ac3dd1))
|
|
20
|
+
|
|
6
21
|
## [4.0.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/activity-txs@4.0.0...@exodus/activity-txs@4.0.1) (2023-12-20)
|
|
7
22
|
|
|
8
23
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @exodus/activity-txs
|
|
2
2
|
|
|
3
|
+
Activity consists of a set of asset transactions that are transformed using `asset.api.getActivityTxs`
|
|
4
|
+
which returns Array of Tx [models](https://github.com/ExodusMovement/exodus-core/blob/cdff25bb962b2301613588c615c5ac09b05f260a/packages/models/src/tx/index.js#L10): `[Tx, Tx, Tx]`
|
|
5
|
+
Most of the assets don't have this api and simply return original txs.
|
|
6
|
+
An example of this transformation is combining bitcoin batched transactions into a single activity item, instead of multiple Txs you get single Tx with adjusted `coinAmount`.
|
|
7
|
+
These are utilized in the UI to display transaction history.
|
|
8
|
+
|
|
9
|
+
This feature introduces atoms with activity items generated from txLog and accountState.
|
|
10
|
+
It provides an efficient way to store them by wallet accounts and to update this extensive object while avoiding unnecessary re-computations.
|
|
11
|
+
Additionally, it offers extra redux selectors to retrieve activity limited by size or batched activity items.
|
|
12
|
+
|
|
3
13
|
## Usage
|
|
4
14
|
|
|
5
|
-
ioc.use(activityTxs())
|
|
15
|
+
`ioc.use(activityTxs())`
|
|
16
|
+
|
|
17
|
+
selector example to get batched activity for specific asset from specific account:
|
|
18
|
+
`selectors.activityTxs.createBatchedAssetSourceSelector({ assetName, walletAccount })` return array of txs `[Tx, Tx, ...]`
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/activity-txs",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "The activity-txs feature",
|
|
5
|
-
"author": "Exodus Movement Inc.",
|
|
5
|
+
"author": "Exodus Movement, Inc.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/ExodusMovement/exodus-hydra.git"
|
|
@@ -25,35 +25,40 @@
|
|
|
25
25
|
"!**/*.test.js"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
|
-
"lint": "eslint . --ignore-path ../../.gitignore",
|
|
28
|
+
"lint": "run -T eslint . --ignore-path ../../.gitignore",
|
|
29
29
|
"lint:fix": "yarn lint --fix",
|
|
30
|
-
"test": "jest"
|
|
30
|
+
"test": "run -T jest"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@exodus/atoms": "^7.0.
|
|
34
|
-
"@exodus/basic-utils": "^2.
|
|
33
|
+
"@exodus/atoms": "^7.0.5",
|
|
34
|
+
"@exodus/basic-utils": "^2.3.0",
|
|
35
35
|
"@exodus/core-selectors": "^1.5.0",
|
|
36
|
+
"@exodus/models": "^10.1.0",
|
|
36
37
|
"@exodus/module": "^1.2.2",
|
|
37
38
|
"@exodus/multi-account-redux": "^2.0.1",
|
|
38
39
|
"lodash": "^4.17.21",
|
|
40
|
+
"minimalistic-assert": "^1.0.1",
|
|
39
41
|
"reselect": "^3.0.1"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@exodus/assets": "^8.0.95",
|
|
43
45
|
"@exodus/assets-base": "^8.1.10",
|
|
44
|
-
"@exodus/assets-feature": "^
|
|
46
|
+
"@exodus/assets-feature": "^5.3.1",
|
|
47
|
+
"@exodus/available-assets": "^8.2.0",
|
|
45
48
|
"@exodus/bitcoin-plugin": "^1.4.2",
|
|
46
|
-
"@exodus/blockchain-metadata": "^15.
|
|
47
|
-
"@exodus/
|
|
48
|
-
"@exodus/dependency-
|
|
49
|
-
"@exodus/
|
|
49
|
+
"@exodus/blockchain-metadata": "^15.3.0",
|
|
50
|
+
"@exodus/connected-origins": "^3.3.1",
|
|
51
|
+
"@exodus/dependency-injection": "^2.2.0",
|
|
52
|
+
"@exodus/dependency-preprocessors": "^5.2.0",
|
|
53
|
+
"@exodus/fiat-ramp": "^10.5.0",
|
|
54
|
+
"@exodus/nfts": "^9.2.1",
|
|
55
|
+
"@exodus/orders": "^4.8.1",
|
|
50
56
|
"@exodus/personal-notes": "^3.6.0",
|
|
51
|
-
"@exodus/redux-dependency-injection": "^3.
|
|
57
|
+
"@exodus/redux-dependency-injection": "^3.1.0",
|
|
52
58
|
"@exodus/storage-memory": "^2.1.1",
|
|
53
|
-
"@exodus/wallet-accounts": "^
|
|
54
|
-
"
|
|
55
|
-
"jest": "^29.1.2",
|
|
59
|
+
"@exodus/wallet-accounts": "^16.4.0",
|
|
60
|
+
"@types/minimalistic-assert": "^1.0.1",
|
|
56
61
|
"redux": "^4.2.1"
|
|
57
62
|
},
|
|
58
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "345159fcb1012fc873ba5ecc24880d5c54700d0b"
|
|
59
64
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createSelector } from 'reselect'
|
|
2
|
+
import { memoize } from '@exodus/basic-utils'
|
|
3
|
+
import { TX_TYPES } from '../constants/tx-types'
|
|
4
|
+
import { formatGroupedExchangeTxs, formattersByType } from '../utils/formatters'
|
|
5
|
+
|
|
6
|
+
const EMPTY = Object.freeze([])
|
|
7
|
+
|
|
8
|
+
const generateActivityId = ({ activityItem }) => {
|
|
9
|
+
const sentIndex = activityItem.data?.sentIndex
|
|
10
|
+
return sentIndex
|
|
11
|
+
? `${activityItem.assetName}.${activityItem.txId}_${sentIndex}`
|
|
12
|
+
: `${activityItem.assetName}.${activityItem.txId}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const groupTxsWithSameOrder = ({ txs, orderSet }) => {
|
|
16
|
+
const getOrder = (tx) => orderSet.getByTxId(tx.txId)
|
|
17
|
+
const exchangeByOrderId = {}
|
|
18
|
+
const result = []
|
|
19
|
+
for (const tx of txs) {
|
|
20
|
+
const order = getOrder(tx)
|
|
21
|
+
if (order) {
|
|
22
|
+
;(exchangeByOrderId[order.orderId] ?? (exchangeByOrderId[order.orderId] = [])).push(tx)
|
|
23
|
+
} else {
|
|
24
|
+
result.push({ tx, type: 'tx' })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Object.values(exchangeByOrderId).forEach((txsGroup) => {
|
|
29
|
+
const tx = txsGroup.sort((a, b) => b.date.getTime() - a.date.getTime())[0]
|
|
30
|
+
const order = getOrder(tx)
|
|
31
|
+
result.push({ tx, type: 'exchange', order })
|
|
32
|
+
})
|
|
33
|
+
return result
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const createAssetSourceBaseActivitySelectorDefinition = {
|
|
37
|
+
id: 'createAssetSourceBaseActivity',
|
|
38
|
+
selectorFactory: (
|
|
39
|
+
assetsSelector,
|
|
40
|
+
createBatchedAssetSourceSelector,
|
|
41
|
+
createOrdersInAccountSelector,
|
|
42
|
+
getPersonalNoteByTxIdSelector
|
|
43
|
+
) =>
|
|
44
|
+
memoize(
|
|
45
|
+
({ assetName, walletAccount }) =>
|
|
46
|
+
createSelector(
|
|
47
|
+
assetsSelector,
|
|
48
|
+
createBatchedAssetSourceSelector({ assetName, walletAccount }),
|
|
49
|
+
createOrdersInAccountSelector(walletAccount),
|
|
50
|
+
getPersonalNoteByTxIdSelector,
|
|
51
|
+
(assets, txs = EMPTY, orderSet, getPersonalNoteByTxId) => {
|
|
52
|
+
const activityResult = []
|
|
53
|
+
|
|
54
|
+
const groupedTxs = groupTxsWithSameOrder({ txs, orderSet })
|
|
55
|
+
|
|
56
|
+
for (const group of groupedTxs) {
|
|
57
|
+
const { tx, order, type: formatterType } = group
|
|
58
|
+
const personalNote = getPersonalNoteByTxId(tx.txId)
|
|
59
|
+
const formattedTx = formattersByType[formatterType]({
|
|
60
|
+
assets,
|
|
61
|
+
order,
|
|
62
|
+
tx,
|
|
63
|
+
personalNote,
|
|
64
|
+
})
|
|
65
|
+
formattedTx.id = generateActivityId({ activityItem: formattedTx })
|
|
66
|
+
|
|
67
|
+
activityResult.push(formattedTx)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const isIndexless = assets[assetName]?.baseAsset?.api?.features?.noHistory
|
|
71
|
+
if (isIndexless) {
|
|
72
|
+
const orderIdsFromActivity = new Set(activityResult.map((tx) => tx.orderId))
|
|
73
|
+
const ordersForIndexlessAssets = [...orderSet].filter(
|
|
74
|
+
(order) =>
|
|
75
|
+
[order.fromAsset, order.toAsset].includes(assetName) &&
|
|
76
|
+
order.exodusStatus !== 'syncing'
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
ordersForIndexlessAssets.forEach((order) => {
|
|
80
|
+
// Prevent dups by checking if this order already exists.
|
|
81
|
+
if (orderIdsFromActivity.has(order.orderId)) return
|
|
82
|
+
|
|
83
|
+
const isFromIndexlessAsset = order.fromAsset === assetName
|
|
84
|
+
const formattedItem = formatGroupedExchangeTxs({
|
|
85
|
+
tx: {
|
|
86
|
+
type: TX_TYPES.EXCHANGE,
|
|
87
|
+
date: order.date,
|
|
88
|
+
order,
|
|
89
|
+
pending: !['failed-final', 'complete-verified'].includes(order.status),
|
|
90
|
+
failed: order.status === 'failed-final',
|
|
91
|
+
data: { swapTx: true },
|
|
92
|
+
selfSend: false,
|
|
93
|
+
token: null, // TBD
|
|
94
|
+
bumpType: {},
|
|
95
|
+
coinName: isFromIndexlessAsset ? order.fromAsset : order.toAsset,
|
|
96
|
+
value: isFromIndexlessAsset ? order.fromAmount : order.toAmount,
|
|
97
|
+
coinAmount: isFromIndexlessAsset ? order.fromAmount : order.toAmount,
|
|
98
|
+
txId: isFromIndexlessAsset ? order.fromTxId : order.toTxId,
|
|
99
|
+
sent: isFromIndexlessAsset,
|
|
100
|
+
received: !isFromIndexlessAsset,
|
|
101
|
+
},
|
|
102
|
+
assets,
|
|
103
|
+
order,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
formattedItem.id = generateActivityId({ activityItem: formattedItem })
|
|
107
|
+
activityResult.push(formattedItem)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (activityResult.length === 0) return EMPTY
|
|
112
|
+
|
|
113
|
+
return activityResult.sort((a, b) => b.date.getTime() - a.date.getTime())
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
({ assetName, walletAccount }) => `${assetName}_${walletAccount}`
|
|
117
|
+
),
|
|
118
|
+
dependencies: [
|
|
119
|
+
{ module: 'assets', selector: 'all' },
|
|
120
|
+
{ selector: 'createBatchedAssetSourceSelector' },
|
|
121
|
+
{ module: 'orders', selector: 'createOrdersInAccount' },
|
|
122
|
+
{ module: 'personalNotes', selector: 'get' },
|
|
123
|
+
],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default createAssetSourceBaseActivitySelectorDefinition
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const selectorFactory =
|
|
2
|
+
(
|
|
3
|
+
createAssetSourceBaseActivity,
|
|
4
|
+
createWithFiatActivity,
|
|
5
|
+
createWithNftsActivity,
|
|
6
|
+
createWithConnectionsActivity
|
|
7
|
+
) =>
|
|
8
|
+
// call this once on client-side
|
|
9
|
+
(options = {}) => {
|
|
10
|
+
let createFullSelector = createAssetSourceBaseActivity
|
|
11
|
+
createFullSelector = createWithNftsActivity({
|
|
12
|
+
createActivitySelector: createFullSelector,
|
|
13
|
+
nftsNetworkNameToAssetName: options.nftsNetworkNameToAssetName || {},
|
|
14
|
+
})
|
|
15
|
+
createFullSelector = createWithFiatActivity({ createActivitySelector: createFullSelector })
|
|
16
|
+
createFullSelector = createWithConnectionsActivity({
|
|
17
|
+
createActivitySelector: createFullSelector,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return createFullSelector
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const createFullActivitySelectorDefinition = {
|
|
24
|
+
id: 'createFullActivity',
|
|
25
|
+
selectorFactory,
|
|
26
|
+
dependencies: [
|
|
27
|
+
{ selector: 'createAssetSourceBaseActivity' },
|
|
28
|
+
{ selector: 'createWithFiatActivity' },
|
|
29
|
+
{ selector: 'createWithNftsActivity' },
|
|
30
|
+
{ selector: 'createWithConnectionsActivity' },
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default createFullActivitySelectorDefinition
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { memoize } from '@exodus/basic-utils'
|
|
2
|
+
import { createSelector } from 'reselect'
|
|
3
|
+
import assert from 'minimalistic-assert'
|
|
4
|
+
|
|
5
|
+
const selectorFactory =
|
|
6
|
+
() =>
|
|
7
|
+
({ createAssetSourceActivitySelector, dedupExchange = true }) => {
|
|
8
|
+
assert(
|
|
9
|
+
createAssetSourceActivitySelector,
|
|
10
|
+
'please provide createAssetSourceActivitySelector selector factory'
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
return memoize(
|
|
14
|
+
({ assetNames, walletAccounts, ...rest }) => {
|
|
15
|
+
const selectors = []
|
|
16
|
+
for (const assetName of assetNames) {
|
|
17
|
+
for (const walletAccount of walletAccounts) {
|
|
18
|
+
selectors.push(
|
|
19
|
+
createAssetSourceActivitySelector({
|
|
20
|
+
assetName,
|
|
21
|
+
walletAccount,
|
|
22
|
+
...rest,
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return createSelector(...selectors, (...activities) => {
|
|
29
|
+
const ordersIncluded = new Set()
|
|
30
|
+
let result = activities.flat()
|
|
31
|
+
if (dedupExchange) {
|
|
32
|
+
result = result.filter((activityItem) => {
|
|
33
|
+
if (activityItem.orderId && !ordersIncluded.has(activityItem.orderId)) {
|
|
34
|
+
ordersIncluded.add(activityItem.orderId)
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return !ordersIncluded.has(activityItem.orderId)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return result.sort((a, b) => (b.date === a.date ? 0 : b.date > a.date ? 1 : -1))
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
({ assetNames, walletAccounts, ...rest }) =>
|
|
46
|
+
`${assetNames.join(',')}_${walletAccounts.join(',')}_${JSON.stringify(rest)}`
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const createMultiActivitySelectorDefinition = {
|
|
51
|
+
id: 'createMultiActivity',
|
|
52
|
+
selectorFactory,
|
|
53
|
+
dependencies: [],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default createMultiActivitySelectorDefinition
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { memoize } from '@exodus/basic-utils'
|
|
2
|
+
import { createSelector } from 'reselect'
|
|
3
|
+
import assert from 'minimalistic-assert'
|
|
4
|
+
|
|
5
|
+
const selectorFactory =
|
|
6
|
+
(getConnectedOriginsSelector) =>
|
|
7
|
+
({ createActivitySelector }) => {
|
|
8
|
+
assert(createActivitySelector, 'please provide activitySelector')
|
|
9
|
+
|
|
10
|
+
return memoize(
|
|
11
|
+
({ assetName, walletAccount, ...rest }) => {
|
|
12
|
+
const activitySelector = createActivitySelector({ assetName, walletAccount, ...rest })
|
|
13
|
+
assert(activitySelector, 'createActivitySelector must return selector')
|
|
14
|
+
if (getConnectedOriginsSelector.isFallback) return activitySelector
|
|
15
|
+
return createSelector(
|
|
16
|
+
getConnectedOriginsSelector,
|
|
17
|
+
activitySelector,
|
|
18
|
+
(getConnectedOrigins, activity) => {
|
|
19
|
+
if (!getConnectedOrigins) return activity
|
|
20
|
+
let hasChanges = false
|
|
21
|
+
|
|
22
|
+
const resultActivity = []
|
|
23
|
+
for (const activityItem of activity) {
|
|
24
|
+
const personalNote = activityItem.personalNote
|
|
25
|
+
const origin = personalNote?.providerData?.origin
|
|
26
|
+
const connection = origin ? getConnectedOrigins(origin) : null
|
|
27
|
+
if (connection) {
|
|
28
|
+
hasChanges = true
|
|
29
|
+
resultActivity.push({
|
|
30
|
+
...activityItem,
|
|
31
|
+
connection,
|
|
32
|
+
})
|
|
33
|
+
} else {
|
|
34
|
+
resultActivity.push(activityItem)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!hasChanges) return activity
|
|
39
|
+
|
|
40
|
+
return resultActivity
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
},
|
|
44
|
+
(deps) => JSON.stringify(deps)
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const createWithConnectionsActivitySelectorDefinition = {
|
|
49
|
+
id: 'createWithConnectionsActivity',
|
|
50
|
+
selectorFactory,
|
|
51
|
+
dependencies: [{ module: 'connectedOrigins', selector: 'get', optional: true }],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default createWithConnectionsActivitySelectorDefinition
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { memoize } from '@exodus/basic-utils'
|
|
2
|
+
import { createSelector } from 'reselect'
|
|
3
|
+
import assert from 'minimalistic-assert'
|
|
4
|
+
import { TX_TYPES } from '../constants/tx-types'
|
|
5
|
+
|
|
6
|
+
const selectorFactory =
|
|
7
|
+
(visibleOrdersSelector, assetsSelector) =>
|
|
8
|
+
({ createActivitySelector }) => {
|
|
9
|
+
assert(createActivitySelector, 'please provide activitySelector')
|
|
10
|
+
|
|
11
|
+
return memoize(
|
|
12
|
+
({ assetName, walletAccount, ...rest }) => {
|
|
13
|
+
const activitySelector = createActivitySelector({ assetName, walletAccount, ...rest })
|
|
14
|
+
assert(activitySelector, 'createActivitySelector must return selector')
|
|
15
|
+
if (visibleOrdersSelector.isFallback) return activitySelector
|
|
16
|
+
return createSelector(
|
|
17
|
+
visibleOrdersSelector,
|
|
18
|
+
assetsSelector,
|
|
19
|
+
activitySelector,
|
|
20
|
+
(fiatOrders, assets, activity) => {
|
|
21
|
+
if (!fiatOrders) {
|
|
22
|
+
return activity
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let hasChanges = false
|
|
26
|
+
|
|
27
|
+
const resultActivity = []
|
|
28
|
+
for (const activityItem of activity) {
|
|
29
|
+
const fiatOrder = fiatOrders.getByTxId(activityItem.txId)
|
|
30
|
+
|
|
31
|
+
if (fiatOrder) {
|
|
32
|
+
hasChanges = true
|
|
33
|
+
resultActivity.push({
|
|
34
|
+
...activityItem,
|
|
35
|
+
fiatOrder,
|
|
36
|
+
type: TX_TYPES.FIAT,
|
|
37
|
+
})
|
|
38
|
+
} else {
|
|
39
|
+
resultActivity.push(activityItem)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!hasChanges) return activity
|
|
44
|
+
|
|
45
|
+
return resultActivity
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
},
|
|
49
|
+
(deps) => JSON.stringify(deps)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const createWithFiatActivitySelectorDefinition = {
|
|
54
|
+
id: 'createWithFiatActivity',
|
|
55
|
+
selectorFactory,
|
|
56
|
+
dependencies: [
|
|
57
|
+
{ module: 'fiatRamp', selector: 'visibleOrders', optional: true },
|
|
58
|
+
{ module: 'assets', selector: 'all' },
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default createWithFiatActivitySelectorDefinition
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { memoize } from '@exodus/basic-utils'
|
|
2
|
+
import { createSelector } from 'reselect'
|
|
3
|
+
import { formatUnindexedNftTx } from '../utils/formatters'
|
|
4
|
+
import assert from 'minimalistic-assert'
|
|
5
|
+
import { TX_TYPES } from '../constants/tx-types'
|
|
6
|
+
|
|
7
|
+
const selectorFactory =
|
|
8
|
+
(createAssetSourceNftTxsById, assetsSelector) =>
|
|
9
|
+
({ createActivitySelector, nftsNetworkNameToAssetName }) => {
|
|
10
|
+
assert(createActivitySelector, 'please provide activitySelector')
|
|
11
|
+
assert(nftsNetworkNameToAssetName, 'please provide nftsNetworkNameToAssetName')
|
|
12
|
+
|
|
13
|
+
return memoize(
|
|
14
|
+
({ assetName, walletAccount, ...rest }) => {
|
|
15
|
+
const activitySelector = createActivitySelector({ assetName, walletAccount, ...rest })
|
|
16
|
+
if (createAssetSourceNftTxsById.isFallback) {
|
|
17
|
+
return activitySelector
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const nftsSelector = createAssetSourceNftTxsById({ assetName, walletAccount })
|
|
21
|
+
assert(nftsSelector, 'nfts redux feature must be installed to use this selector')
|
|
22
|
+
return createSelector(
|
|
23
|
+
nftsSelector,
|
|
24
|
+
assetsSelector,
|
|
25
|
+
activitySelector,
|
|
26
|
+
(nftsByTxId, assets, activity) => {
|
|
27
|
+
const indexedNftTxIds = new Set()
|
|
28
|
+
const resultActivity = []
|
|
29
|
+
let hasChanges = false
|
|
30
|
+
for (const activityItem of activity) {
|
|
31
|
+
const nft = nftsByTxId.get(activityItem.txId)
|
|
32
|
+
|
|
33
|
+
if (nft) {
|
|
34
|
+
hasChanges = true
|
|
35
|
+
indexedNftTxIds.add(nft.txId)
|
|
36
|
+
resultActivity.push({
|
|
37
|
+
...activityItem,
|
|
38
|
+
nft,
|
|
39
|
+
type: TX_TYPES.NFT,
|
|
40
|
+
sent: nft.sent === undefined ? activityItem.sent : nft.sent, // rely on nft metadata for nft txs
|
|
41
|
+
})
|
|
42
|
+
} else {
|
|
43
|
+
resultActivity.push(activityItem)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
;[...nftsByTxId.values()]
|
|
48
|
+
.filter(
|
|
49
|
+
(nftTx) =>
|
|
50
|
+
assetName === nftsNetworkNameToAssetName[nftTx.network] &&
|
|
51
|
+
!indexedNftTxIds.has(nftTx.txId)
|
|
52
|
+
)
|
|
53
|
+
.forEach((nftTx) => {
|
|
54
|
+
hasChanges = true
|
|
55
|
+
const assetName = nftsNetworkNameToAssetName[nftTx.network]
|
|
56
|
+
const asset = assets[assetName]
|
|
57
|
+
const formatted = formatUnindexedNftTx({ nftTx, asset })
|
|
58
|
+
|
|
59
|
+
resultActivity.push(formatted)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (!hasChanges) return activity
|
|
63
|
+
|
|
64
|
+
return resultActivity
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
},
|
|
68
|
+
({ assetName, walletAccount, ...rest }) => `${assetName}_${walletAccount}_${rest}`
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const createWithNftsActivitySelectorDefinition = {
|
|
73
|
+
id: 'createWithNftsActivity',
|
|
74
|
+
selectorFactory,
|
|
75
|
+
dependencies: [
|
|
76
|
+
{ module: 'nfts', selector: 'createAssetSourceNftTxsById', optional: true },
|
|
77
|
+
{ module: 'assets', selector: 'all' },
|
|
78
|
+
],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default createWithNftsActivitySelectorDefinition
|
package/redux/selectors/index.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import createLimitedAssetSourceSelectorDefinition from './create-limited-asset-source'
|
|
2
2
|
import createBatchedAssetSourceSelector from './create-batched-asset-source'
|
|
3
3
|
import createBatchedAssetSourceActivityByIdSelector from './create-batched-asset-source-by-id'
|
|
4
|
+
import createAssetSourceBaseActivitySelectorDefinition from './create-asset-source-base-activity'
|
|
5
|
+
import createWithNftsActivitySelectorDefinition from './create-with-nfts-activity'
|
|
6
|
+
import createWithFiatActivitySelectorDefinition from './create-with-fiat-activity'
|
|
7
|
+
import createWithConnectionsActivitySelectorDefinition from './create-with-connections-activity'
|
|
8
|
+
import createFullActivitySelectorDefinition from './create-full-activity'
|
|
9
|
+
import createMultiActivitySelectorDefinition from './create-multi-activity'
|
|
4
10
|
|
|
5
11
|
export default [
|
|
6
12
|
createLimitedAssetSourceSelectorDefinition,
|
|
7
13
|
createBatchedAssetSourceSelector,
|
|
8
14
|
createBatchedAssetSourceActivityByIdSelector,
|
|
15
|
+
createAssetSourceBaseActivitySelectorDefinition,
|
|
16
|
+
createWithNftsActivitySelectorDefinition,
|
|
17
|
+
createWithFiatActivitySelectorDefinition,
|
|
18
|
+
createWithConnectionsActivitySelectorDefinition,
|
|
19
|
+
createFullActivitySelectorDefinition,
|
|
20
|
+
createMultiActivitySelectorDefinition,
|
|
9
21
|
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { TX_TYPES } from '../constants/tx-types'
|
|
2
|
+
|
|
3
|
+
const FINAL_ORDER_STATUS = {
|
|
4
|
+
inProgress: 'in-progress',
|
|
5
|
+
failed: 'failed',
|
|
6
|
+
success: 'complete',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const EXODEX_TX_TYPES = {
|
|
10
|
+
PRE_SETUP: 'preSetupTransaction',
|
|
11
|
+
SETUP: 'setupTransaction',
|
|
12
|
+
SWAP: 'swapTransaction',
|
|
13
|
+
CLEANUP: 'cleanupTransaction',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const UI_TX_TYPE_FROM_EXODEX = {
|
|
17
|
+
[EXODEX_TX_TYPES.PRE_SETUP]: TX_TYPES.APPROVAL,
|
|
18
|
+
[EXODEX_TX_TYPES.SETUP]: TX_TYPES.APPROVAL,
|
|
19
|
+
[EXODEX_TX_TYPES.SWAP]: TX_TYPES.EXCHANGE,
|
|
20
|
+
[EXODEX_TX_TYPES.CLEANUP]: TX_TYPES.CLEANUP,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getTxType({ tx, order }) {
|
|
24
|
+
if (order) {
|
|
25
|
+
const txInfoFromOrder = order.txIds?.find((txFromOrder) => txFromOrder.txId === tx.txId)
|
|
26
|
+
return UI_TX_TYPE_FROM_EXODEX[txInfoFromOrder?.txType] || TX_TYPES.EXCHANGE
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return tx.sent ? TX_TYPES.SENT : TX_TYPES.RECEIVED
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const getMinConfirmations = (asset) => asset.baseAsset.minConfirmations || 1
|
|
33
|
+
|
|
34
|
+
export function isPending(tx, asset) {
|
|
35
|
+
return (
|
|
36
|
+
tx.confirmations < getMinConfirmations(asset) &&
|
|
37
|
+
!tx.dropped && // dropped txs are not pending anymore
|
|
38
|
+
!tx.error // errored txs are not pending anymore
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const formatBase = ({ tx, asset, order, personalNote }) => ({
|
|
43
|
+
...tx,
|
|
44
|
+
assetName: asset.name,
|
|
45
|
+
displayTicker: asset.displayTicker,
|
|
46
|
+
type: getTxType({ tx, asset, order }),
|
|
47
|
+
time: tx.date,
|
|
48
|
+
amount: tx.coinAmount.abs().toDefaultNumber(),
|
|
49
|
+
pending: isPending(tx, asset),
|
|
50
|
+
isOrderInProgress: !!order && order.exodusStatus === FINAL_ORDER_STATUS.inProgress,
|
|
51
|
+
confirmations: tx.confirmations,
|
|
52
|
+
failed: tx.failed,
|
|
53
|
+
received: tx.received,
|
|
54
|
+
personalNote,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export const formatGroupedExchangeTxs = ({ tx, assets, order, personalNote }) => {
|
|
58
|
+
const toAsset = assets[order.toAsset]
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
...formatBase({ tx, asset: toAsset, order, personalNote }),
|
|
62
|
+
failed: tx.failed || order.error,
|
|
63
|
+
pending: order.exodusStatus === FINAL_ORDER_STATUS.inProgress,
|
|
64
|
+
assetName: toAsset.name,
|
|
65
|
+
txId: tx.txId,
|
|
66
|
+
type: TX_TYPES.EXCHANGE,
|
|
67
|
+
time: tx.date,
|
|
68
|
+
fromAsset: order.fromAsset,
|
|
69
|
+
toAsset: order.toAsset,
|
|
70
|
+
amount: order.toAmount,
|
|
71
|
+
orderId: order.orderId,
|
|
72
|
+
data: {
|
|
73
|
+
swapTx: true,
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const formatGroupedTx = ({ tx, assets, personalNote }) =>
|
|
79
|
+
formatBase({
|
|
80
|
+
tx,
|
|
81
|
+
asset: assets[tx.coinName],
|
|
82
|
+
personalNote,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
export const formattersByType = {
|
|
86
|
+
exchange: formatGroupedExchangeTxs,
|
|
87
|
+
tx: formatGroupedTx,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const formatUnindexedNftTx = ({ asset, nftTx }) => {
|
|
91
|
+
return {
|
|
92
|
+
id: `${asset.name}.${nftTx.txId}`,
|
|
93
|
+
coinName: asset.name,
|
|
94
|
+
assetName: asset.name,
|
|
95
|
+
displayTicker: asset.displayTicker,
|
|
96
|
+
type: TX_TYPES.NFT,
|
|
97
|
+
time: new Date(nftTx.date),
|
|
98
|
+
from: nftTx.from,
|
|
99
|
+
to: nftTx.to,
|
|
100
|
+
sent: Boolean(nftTx.sent),
|
|
101
|
+
received: !nftTx.sent,
|
|
102
|
+
date: new Date(nftTx.date),
|
|
103
|
+
nft: nftTx,
|
|
104
|
+
txId: nftTx.txId,
|
|
105
|
+
selfSend: false,
|
|
106
|
+
}
|
|
107
|
+
}
|