@exodus/activity-txs 6.1.0 → 6.1.2

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 CHANGED
@@ -3,6 +3,18 @@
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
+ ## [6.1.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/activity-txs@6.1.1...@exodus/activity-txs@6.1.2) (2026-05-08)
7
+
8
+ ### Bug Fixes
9
+
10
+ - fix(activity-txs): fiat order duplicates (#16369)
11
+
12
+ ## [6.1.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/activity-txs@6.1.0...@exodus/activity-txs@6.1.1) (2026-03-24)
13
+
14
+ ### Bug Fixes
15
+
16
+ - fix(activity-txs): avoid crashes for incomplete unindexed swap orders (#15662)
17
+
6
18
  ## [6.1.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/activity-txs@6.0.0...@exodus/activity-txs@6.1.0) (2026-02-05)
7
19
 
8
20
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/activity-txs",
3
- "version": "6.1.0",
3
+ "version": "6.1.2",
4
4
  "description": "The activity-txs feature",
5
5
  "author": "Exodus Movement, Inc.",
6
6
  "repository": {
@@ -32,10 +32,10 @@
32
32
  "test": "run -T exodus-test --jest"
33
33
  },
34
34
  "dependencies": {
35
- "@exodus/atoms": "^9.0.0",
36
- "@exodus/basic-utils": "^3.0.1",
35
+ "@exodus/atoms": "^10.3.3",
36
+ "@exodus/basic-utils": "^5.0.0",
37
37
  "@exodus/core-selectors": "^4.0.0",
38
- "@exodus/models": "^12.0.1",
38
+ "@exodus/models": "^13.0.0",
39
39
  "@exodus/multi-account-redux": "^2.0.1",
40
40
  "@exodus/safe-string": "^1.3.0",
41
41
  "@exodus/typeforce": "^1.19.0",
@@ -45,19 +45,19 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@exodus/assets": "^11.0.0",
48
- "@exodus/assets-base": "^10.0.0",
49
- "@exodus/assets-feature": "^8.8.0",
50
- "@exodus/available-assets": "^8.10.1",
51
- "@exodus/bitcoin-plugin": "^1.4.2",
48
+ "@exodus/assets-base": "^12.0.0",
49
+ "@exodus/assets-feature": "^9.2.2",
50
+ "@exodus/available-assets": "^8.11.0",
51
+ "@exodus/bitcoin-plugin": "^2.0.0",
52
52
  "@exodus/connected-origins": "^4.3.0",
53
- "@exodus/error-tracking": "^3.3.0",
54
- "@exodus/fiat-ramp": "^15.12.0",
53
+ "@exodus/error-tracking": "^3.4.0",
54
+ "@exodus/fiat-ramp": "^15.21.1",
55
55
  "@exodus/logger": "^1.2.3",
56
56
  "@exodus/nfts": "^9.6.2",
57
- "@exodus/orders": "^6.0.1",
57
+ "@exodus/orders": "^6.1.3",
58
58
  "@exodus/personal-notes": "^3.9.0",
59
- "@exodus/redux-dependency-injection": "^4.1.2",
60
- "@exodus/wallet-accounts": "^20.2.1",
59
+ "@exodus/redux-dependency-injection": "^4.5.0",
60
+ "@exodus/wallet-accounts": "^20.4.3",
61
61
  "@types/minimalistic-assert": "^1.0.1",
62
62
  "redux": "^4.2.1"
63
63
  },
@@ -65,5 +65,5 @@
65
65
  "access": "public",
66
66
  "provenance": false
67
67
  },
68
- "gitHead": "44ef16c9fbb7fa3f5a503b048872145882b54dff"
68
+ "gitHead": "372a71f57099176bee974600b6ca683d60c3b6eb"
69
69
  }
@@ -107,6 +107,7 @@ const createAssetSourceBaseActivitySelectorDefinition = {
107
107
  tx,
108
108
  personalNote,
109
109
  })
110
+ if (!formattedTx) continue
110
111
 
111
112
  activityResult.push({ ...formattedTx, walletAccount })
112
113
  }
@@ -131,8 +132,11 @@ const createAssetSourceBaseActivitySelectorDefinition = {
131
132
  // Prevent dups by checking if this order already exists.
132
133
  if (orderIdsFromActivity.has(order.orderId)) return
133
134
 
135
+ const formattedOrder = formatUnindexedOrder({ assetName, order })
136
+ if (!formattedOrder) return
137
+
134
138
  activityResult.push({
135
- ...formatUnindexedOrder({ assetName, order }),
139
+ ...formattedOrder,
136
140
  walletAccount,
137
141
  })
138
142
  })
@@ -4,6 +4,45 @@ import assert from 'minimalistic-assert'
4
4
  import { TX_TYPES } from '../constants/tx-types.js'
5
5
  import { ORDER_STATUS, ORDER_TYPES } from '@exodus/models/lib/fiat-order/constants.js'
6
6
 
7
+ const ORDER_TX_MATCH_WINDOW_MS = 3 * 24 * 60 * 60 * 1000
8
+
9
+ const NON_MATCHABLE_ORDER_STATUSES = new Set([
10
+ ORDER_STATUS.failed,
11
+ ORDER_STATUS.refunded,
12
+ ORDER_STATUS.expired,
13
+ ORDER_STATUS.error,
14
+ ])
15
+
16
+ const findMatchingOrderIndex = ({ activityItem, ordersWithoutTx }) => {
17
+ const coinAmount = activityItem.tx?.coinAmount
18
+ if (!coinAmount) {
19
+ return -1
20
+ }
21
+
22
+ const txTime = activityItem.date?.getTime?.()
23
+ if (!Number.isFinite(txTime)) {
24
+ return -1
25
+ }
26
+
27
+ const absAmount = coinAmount.abs()
28
+
29
+ return ordersWithoutTx.findIndex((order) => {
30
+ const isSell = order.orderType === ORDER_TYPES.sell
31
+ if (isSell !== activityItem.tx.sent) {
32
+ return false
33
+ }
34
+
35
+ const orderAmount = isSell ? order.fromAmount : order.toAmount
36
+ if (!orderAmount || !absAmount.equals(orderAmount)) {
37
+ return false
38
+ }
39
+
40
+ const orderTime = order.date?.getTime?.()
41
+
42
+ return Number.isFinite(orderTime) && Math.abs(txTime - orderTime) <= ORDER_TX_MATCH_WINDOW_MS
43
+ })
44
+ }
45
+
7
46
  const selectorFactory =
8
47
  (visibleOrdersSelector) =>
9
48
  ({ createActivitySelector }) => {
@@ -20,8 +59,8 @@ const selectorFactory =
20
59
  }
21
60
 
22
61
  let hasChanges = false
23
- const pendingFiatOrdersWithoutTx = []
24
62
  const fiatOrdersWithoutTx = []
63
+ const matchableOrdersWithoutTx = []
25
64
 
26
65
  if (displayFiatOrdersWithoutTx) {
27
66
  for (const order of fiatOrders) {
@@ -34,8 +73,8 @@ const selectorFactory =
34
73
 
35
74
  if (isRelatedBuyOrder || isRelatedSellOrder) {
36
75
  fiatOrdersWithoutTx.push(order)
37
- if (order.exodusStatus === ORDER_STATUS.in_progress) {
38
- pendingFiatOrdersWithoutTx.push(order)
76
+ if (!NON_MATCHABLE_ORDER_STATUSES.has(order.exodusStatus)) {
77
+ matchableOrdersWithoutTx.push(order)
39
78
  }
40
79
  }
41
80
  }
@@ -57,24 +96,24 @@ const selectorFactory =
57
96
  type: TX_TYPES.FIAT,
58
97
  })
59
98
  }
60
- } else {
61
- if (activityItem.pending && pendingFiatOrdersWithoutTx.length > 0) {
62
- const matchingOrderIndex = pendingFiatOrdersWithoutTx.findIndex((order) => {
63
- const orderAmount =
64
- order.orderType === ORDER_TYPES.sell ? order.fromAmount : order.toAmount
65
99
 
66
- return activityItem.tx?.coinAmount?.abs().equals(orderAmount)
67
- })
100
+ continue
101
+ }
68
102
 
69
- if (matchingOrderIndex !== -1) {
70
- hasChanges = true
71
- pendingFiatOrdersWithoutTx.splice(matchingOrderIndex, 1)
72
- continue
73
- }
74
- }
103
+ if (matchableOrdersWithoutTx.length > 0) {
104
+ const matchingOrderIndex = findMatchingOrderIndex({
105
+ activityItem,
106
+ ordersWithoutTx: matchableOrdersWithoutTx,
107
+ })
75
108
 
76
- resultActivity.push(activityItem)
109
+ if (matchingOrderIndex !== -1) {
110
+ matchableOrdersWithoutTx.splice(matchingOrderIndex, 1)
111
+ hasChanges = true
112
+ continue
113
+ }
77
114
  }
115
+
116
+ resultActivity.push(activityItem)
78
117
  }
79
118
 
80
119
  for (const fiatOrder of fiatOrdersWithoutTx) {
@@ -21,6 +21,13 @@ const UI_TX_TYPE_FROM_EXODEX = new Map([
21
21
  [EXODEX_TX_TYPES.CLEANUP, TX_TYPES.CLEANUP],
22
22
  ])
23
23
 
24
+ function getSwapDisplayAmount({ tx, order }) {
25
+ if (tx.coinName === order.fromAsset && order.fromAmount) return order.fromAmount.abs()
26
+ if (tx.coinName === order.toAsset && order.toAmount) return order.toAmount.abs()
27
+ if (tx.coinAmount?.abs) return tx.coinAmount.abs()
28
+ return null
29
+ }
30
+
24
31
  function getOrderType({ order, tx }) {
25
32
  const txInfoFromOrder = tx
26
33
  ? order.txIds?.find((txFromOrder) => txFromOrder.txId === tx.txId)
@@ -58,24 +65,30 @@ const getSwapSharedProps = ({
58
65
  }
59
66
  }
60
67
 
61
- export const formatSwapActivity = ({ tx, order, personalNote }) =>
62
- getSwapSharedProps({
68
+ export const formatSwapActivity = ({ tx, order, personalNote }) => {
69
+ const displayAmount = getSwapDisplayAmount({ tx, order })
70
+ if (!displayAmount) return null
71
+
72
+ return getSwapSharedProps({
63
73
  date: tx.date,
64
74
  assetName: tx.coinName,
65
75
  txId: tx.txId,
66
76
  tx,
67
77
  order,
68
78
  failed: !!(tx.failed || order.error),
69
- displayAmount: order.toAmount.abs(),
79
+ displayAmount,
70
80
  personalNote,
71
81
  pending: order.exodusStatus === FINAL_ORDER_STATUS.inProgress,
72
82
  })
83
+ }
73
84
 
74
85
  export const formatUnindexedOrder = ({ order, assetName: _assetName, personalNote }) => {
75
86
  const isFromIndexlessAsset = order.fromAsset === _assetName
76
87
  const assetName = isFromIndexlessAsset ? order.fromAsset : order.toAsset
77
- let displayAmount = isFromIndexlessAsset ? order.fromAmount : order.toAmount
78
- displayAmount = displayAmount.abs()
88
+ const amount = isFromIndexlessAsset ? order.fromAmount : order.toAmount
89
+ if (!amount) return null
90
+
91
+ const displayAmount = amount.abs()
79
92
  const txId = isFromIndexlessAsset ? order.fromTxId : order.toTxId
80
93
 
81
94
  return getSwapSharedProps({