@exodus/activity-txs 1.0.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/README.md +5 -0
- package/atoms/activity-txs.js +126 -0
- package/atoms/index.js +1 -0
- package/index.js +14 -0
- package/package.json +58 -0
- package/plugin/index.js +23 -0
- package/redux/id.js +1 -0
- package/redux/index.js +19 -0
- package/redux/initial-state.js +5 -0
- package/redux/multi-account-helper.js +9 -0
- package/redux/selectors/create-batched-asset-source.js +74 -0
- package/redux/selectors/create-limited-asset-source.js +15 -0
- package/redux/selectors/index.js +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { combine, compute } from '@exodus/atoms'
|
|
2
|
+
import { createSelector } from 'reselect'
|
|
3
|
+
import { memoize } from 'lodash'
|
|
4
|
+
import { mapValues } from '@exodus/basic-utils'
|
|
5
|
+
import { createDeepEqualOutputSelector } from '@exodus/core-selectors/utils/deep-equal'
|
|
6
|
+
|
|
7
|
+
const createAssetSourceActivityTxsSelector = memoize(
|
|
8
|
+
({ assetName, walletAccount }) =>
|
|
9
|
+
createDeepEqualOutputSelector(
|
|
10
|
+
(args) => args.txSet,
|
|
11
|
+
(args) => args.baseAssetAccountState,
|
|
12
|
+
(args) => args.asset,
|
|
13
|
+
(txSet, baseAssetAccountState, asset) =>
|
|
14
|
+
asset.api.getActivityTxs({
|
|
15
|
+
txs: [...txSet],
|
|
16
|
+
asset,
|
|
17
|
+
accountState: baseAssetAccountState,
|
|
18
|
+
})
|
|
19
|
+
),
|
|
20
|
+
({ assetName, walletAccount }) => `${assetName}_${walletAccount}`
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const createAssetSourceTxsSelector = memoize(
|
|
24
|
+
({ assetName, walletAccount }) =>
|
|
25
|
+
createSelector(
|
|
26
|
+
(args) => args.txSet,
|
|
27
|
+
(txSet) => {
|
|
28
|
+
return [...txSet]
|
|
29
|
+
}
|
|
30
|
+
),
|
|
31
|
+
({ assetName, walletAccount }) => `${assetName}_${walletAccount}`
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const areActivitiesEqual = (obj1 = {}, obj2 = {}) => {
|
|
35
|
+
// Get wallet account keys from both objects
|
|
36
|
+
const obj1WalletAccounts = Object.keys(obj1)
|
|
37
|
+
const obj2WalletAccounts = Object.keys(obj2)
|
|
38
|
+
|
|
39
|
+
// Check if both objects have the same number of wallet accounts
|
|
40
|
+
if (obj1WalletAccounts.length !== obj2WalletAccounts.length) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Iterate over each wallet account in obj1
|
|
45
|
+
for (const walletAccount of obj1WalletAccounts) {
|
|
46
|
+
// Check if obj2 has the same wallet account
|
|
47
|
+
if (!obj2.hasOwnProperty(walletAccount)) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const obj1Assets = obj1[walletAccount] || {}
|
|
52
|
+
const obj2Assets = obj2[walletAccount] || {}
|
|
53
|
+
|
|
54
|
+
const obj1AssetNames = Object.keys(obj1Assets)
|
|
55
|
+
const obj2AssetNames = Object.keys(obj2Assets)
|
|
56
|
+
|
|
57
|
+
if (obj1AssetNames.length !== obj2AssetNames.length) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Iterate over each asset in obj1Assets
|
|
62
|
+
for (const assetName of obj1AssetNames) {
|
|
63
|
+
// Check if obj2Assets has the same asset name and the data matches
|
|
64
|
+
if (
|
|
65
|
+
!obj2Assets.hasOwnProperty(assetName) ||
|
|
66
|
+
obj1Assets[assetName] !== obj2Assets[assetName]
|
|
67
|
+
) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const createActivityTxsAtom = ({ txLogsAtom, accountStatesAtom, assetsModule }) => {
|
|
77
|
+
const inputAtom = combine({
|
|
78
|
+
txLogs: txLogsAtom,
|
|
79
|
+
accountStates: accountStatesAtom,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
let prevResult
|
|
83
|
+
|
|
84
|
+
const selector = ({ txLogs, accountStates }) => {
|
|
85
|
+
const { value } = txLogs
|
|
86
|
+
const { value: accountStatesValue } = accountStates
|
|
87
|
+
const result = mapValues(value, (accountTxLogs, walletAccount) =>
|
|
88
|
+
mapValues(accountTxLogs, (assetTxSet, assetName) => {
|
|
89
|
+
const asset = assetsModule.getAsset(assetName)
|
|
90
|
+
const baseAssetName = asset.baseAsset.name
|
|
91
|
+
if (asset?.api?.getActivityTxs) {
|
|
92
|
+
return createAssetSourceActivityTxsSelector(assetName)({
|
|
93
|
+
asset,
|
|
94
|
+
txSet: assetTxSet,
|
|
95
|
+
baseAssetAccountState: accountStatesValue?.[walletAccount]?.[baseAssetName],
|
|
96
|
+
})
|
|
97
|
+
} else {
|
|
98
|
+
return createAssetSourceTxsSelector(assetName)({
|
|
99
|
+
txSet: assetTxSet,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
)
|
|
104
|
+
if (areActivitiesEqual(result, prevResult)) {
|
|
105
|
+
return prevResult
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
prevResult = result
|
|
109
|
+
|
|
110
|
+
return result
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return compute({
|
|
114
|
+
atom: inputAtom,
|
|
115
|
+
selector,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const activityTxsAtomDefinition = {
|
|
120
|
+
id: 'activityTxsAtom',
|
|
121
|
+
type: 'atom',
|
|
122
|
+
factory: createActivityTxsAtom,
|
|
123
|
+
dependencies: ['txLogsAtom', 'accountStatesAtom', 'assetsModule'],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default activityTxsAtomDefinition
|
package/atoms/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as activityTxsAtomDefinition } from './activity-txs'
|
package/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { activityTxsAtomDefinition } from './atoms'
|
|
2
|
+
import activityTxsPluginDefinition from './plugin'
|
|
3
|
+
|
|
4
|
+
const activityTxs = () => ({
|
|
5
|
+
id: 'activityTxs',
|
|
6
|
+
definitions: [
|
|
7
|
+
{
|
|
8
|
+
definition: activityTxsAtomDefinition,
|
|
9
|
+
},
|
|
10
|
+
{ definition: activityTxsPluginDefinition },
|
|
11
|
+
],
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export default activityTxs
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@exodus/activity-txs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The activity-txs feature",
|
|
5
|
+
"author": "Exodus Movement Inc.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ExodusMovement/exodus-hydra.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/ExodusMovement/exodus-hydra/tree/master/features/activity-txs",
|
|
11
|
+
"license": "UNLICENSED",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/ExodusMovement/exodus-hydra/issues?q=is%3Aissue+is%3Aopen+label%3Aactivity-txs"
|
|
14
|
+
},
|
|
15
|
+
"main": "index.js",
|
|
16
|
+
"files": [
|
|
17
|
+
"module",
|
|
18
|
+
"atoms",
|
|
19
|
+
"api",
|
|
20
|
+
"plugin",
|
|
21
|
+
"redux",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md",
|
|
24
|
+
"!**/__tests__/**",
|
|
25
|
+
"!**/*.test.js"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"lint": "eslint . --ignore-path ../../.gitignore",
|
|
29
|
+
"lint:fix": "yarn lint --fix",
|
|
30
|
+
"test": "jest"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@exodus/atoms": "^5.7.1",
|
|
34
|
+
"@exodus/basic-utils": "^2.1.0",
|
|
35
|
+
"@exodus/core-selectors": "^1.5.0",
|
|
36
|
+
"@exodus/module": "^1.2.0",
|
|
37
|
+
"@exodus/multi-account-redux": "^2.0.0",
|
|
38
|
+
"lodash": "^4.17.21",
|
|
39
|
+
"reselect": "^3.0.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@exodus/assets": "^8.0.95",
|
|
43
|
+
"@exodus/assets-base": "^8.1.10",
|
|
44
|
+
"@exodus/assets-feature": "^3.1.1",
|
|
45
|
+
"@exodus/blockchain-metadata": "^14.0.0",
|
|
46
|
+
"@exodus/dependency-injection": "^2.0.1",
|
|
47
|
+
"@exodus/dependency-preprocessors": "^3.1.1",
|
|
48
|
+
"@exodus/models": "^10.1.0",
|
|
49
|
+
"@exodus/personal-notes": "^3.6.0",
|
|
50
|
+
"@exodus/redux-dependency-injection": "^3.0.0",
|
|
51
|
+
"@exodus/storage-memory": "^2.1.1",
|
|
52
|
+
"@exodus/wallet-accounts": "^14.1.0",
|
|
53
|
+
"eslint": "^8.44.0",
|
|
54
|
+
"jest": "^29.1.2",
|
|
55
|
+
"redux": "^4.2.1"
|
|
56
|
+
},
|
|
57
|
+
"gitHead": "12b0a2c583d69e2262471f70e6ac64ed7d7f5553"
|
|
58
|
+
}
|
package/plugin/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import createAtomObserver from '@exodus/atoms/src/factories/observer'
|
|
2
|
+
|
|
3
|
+
const createActivityTxsPlugin = ({ activityTxsAtom, port }) => {
|
|
4
|
+
const observer = createAtomObserver({ atom: activityTxsAtom, port, event: 'activityTxs' })
|
|
5
|
+
const onLoad = () => {
|
|
6
|
+
observer.start()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const onStop = () => {
|
|
10
|
+
observer.unregister()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return { onLoad, onStop }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const activityTxsPluginDefinition = {
|
|
17
|
+
id: 'activityTxsPlugin',
|
|
18
|
+
type: 'plugin',
|
|
19
|
+
factory: createActivityTxsPlugin,
|
|
20
|
+
dependencies: ['activityTxsAtom', 'port'],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default activityTxsPluginDefinition
|
package/redux/id.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default 'activityTxs'
|
package/redux/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import id from './id'
|
|
2
|
+
import initialState from './initial-state'
|
|
3
|
+
import selectorDefinitions from './selectors'
|
|
4
|
+
import helper from './multi-account-helper'
|
|
5
|
+
import { setAccounts } from '@exodus/multi-account-redux/src/common'
|
|
6
|
+
|
|
7
|
+
const activityTxsReduxDefinition = {
|
|
8
|
+
id,
|
|
9
|
+
type: 'redux-module',
|
|
10
|
+
initialState,
|
|
11
|
+
eventReducers: {
|
|
12
|
+
activityTxs: (state, data) => {
|
|
13
|
+
return setAccounts(state, data)
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
selectorDefinitions: [...helper.selectorDefinitions, ...selectorDefinitions],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default activityTxsReduxDefinition
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { memoize, get, groupBy, merge } from 'lodash'
|
|
2
|
+
import { createSelector } from 'reselect'
|
|
3
|
+
import { Tx } from '@exodus/models'
|
|
4
|
+
|
|
5
|
+
const collapseBatch = ({ batch, assets }) => {
|
|
6
|
+
const asset = assets[batch[0].coinName]
|
|
7
|
+
const isToken = asset.baseAsset.name !== asset.name
|
|
8
|
+
|
|
9
|
+
const totalBatchValue = batch.reduce(
|
|
10
|
+
(sum, otherTx) => sum.add(otherTx.coinAmount),
|
|
11
|
+
asset.currency.ZERO
|
|
12
|
+
)
|
|
13
|
+
const totalBatchFee = batch.reduce(
|
|
14
|
+
(sum, otherTx) => (otherTx.feeAmount ? sum.add(otherTx.feeAmount) : sum),
|
|
15
|
+
(isToken ? asset.baseAsset : asset).currency.ZERO
|
|
16
|
+
)
|
|
17
|
+
const baseData = batch[0].data || {}
|
|
18
|
+
const batchedIds = batch.map((tx) => tx.txId)
|
|
19
|
+
|
|
20
|
+
const txJSON = batch[0].toJSON()
|
|
21
|
+
|
|
22
|
+
return Tx.fromJSON(
|
|
23
|
+
merge({}, txJSON, {
|
|
24
|
+
coinAmount: totalBatchValue,
|
|
25
|
+
feeAmount: totalBatchFee,
|
|
26
|
+
...(batchedIds.length > 1 && {
|
|
27
|
+
data: {
|
|
28
|
+
...baseData,
|
|
29
|
+
batchedIds,
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
}),
|
|
33
|
+
{ assets }
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const groupAndCollapseBatched = ({ txs, personalNotesWithBatchId, assets }) => {
|
|
38
|
+
const { NO_BATCH: unbatchedTransactions = [], ...batches } = groupBy(txs, (tx) =>
|
|
39
|
+
get(personalNotesWithBatchId.get(tx.txId), 'dapp.batch.id', 'NO_BATCH')
|
|
40
|
+
)
|
|
41
|
+
const batchedTransactions = Object.values(batches).map((batch) =>
|
|
42
|
+
collapseBatch({ batch, assets })
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return [...unbatchedTransactions, ...batchedTransactions]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const createBatchedAssetSourceSelectorDefinition = {
|
|
49
|
+
id: 'createBatchedAssetSourceSelector',
|
|
50
|
+
dependencies: [
|
|
51
|
+
{ selector: 'createLimitedAssetSourceSelector' },
|
|
52
|
+
{ module: 'personalNotes', selector: 'withBatchId' },
|
|
53
|
+
{ module: 'assets', selector: 'all' },
|
|
54
|
+
],
|
|
55
|
+
selectorFactory: (
|
|
56
|
+
createLimitedAssetSourceSelector,
|
|
57
|
+
personalNotesWithBatchIdSelector,
|
|
58
|
+
assetsSelector
|
|
59
|
+
) =>
|
|
60
|
+
memoize(({ assetName, walletAccount, limit }) =>
|
|
61
|
+
createSelector(
|
|
62
|
+
createLimitedAssetSourceSelector({ assetName, walletAccount }),
|
|
63
|
+
personalNotesWithBatchIdSelector,
|
|
64
|
+
assetsSelector,
|
|
65
|
+
(txs, personalNotesWithBatchId, assets) => {
|
|
66
|
+
return personalNotesWithBatchId.size > 0
|
|
67
|
+
? groupAndCollapseBatched({ txs, personalNotesWithBatchId, assets })
|
|
68
|
+
: txs
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default createBatchedAssetSourceSelectorDefinition
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { memoize } from 'lodash'
|
|
2
|
+
import { createSelector } from 'reselect'
|
|
3
|
+
|
|
4
|
+
const createLimitedAssetSourceSelectorDefinition = {
|
|
5
|
+
id: 'createLimitedAssetSourceSelector',
|
|
6
|
+
dependencies: [{ selector: 'createAssetSourceSelector' }],
|
|
7
|
+
selectorFactory: (createAssetSourceSelector) =>
|
|
8
|
+
memoize(({ assetName, walletAccount, limit }) =>
|
|
9
|
+
createSelector(createAssetSourceSelector({ assetName, walletAccount }), (txs) =>
|
|
10
|
+
limit ? txs.slice(-limit) : txs
|
|
11
|
+
)
|
|
12
|
+
),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default createLimitedAssetSourceSelectorDefinition
|