@exodus/solana-api 3.27.5 → 3.27.6

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
+ ## [3.27.6](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.27.5...@exodus/solana-api@3.27.6) (2026-01-21)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: improve SOL WS events logging (#7293)
13
+
14
+ * fix: Solana merge multi-transfer transactions to prevent data loss (#7297)
15
+
16
+
17
+
6
18
  ## [3.27.5](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.27.4...@exodus/solana-api@3.27.5) (2026-01-19)
7
19
 
8
20
  **Note:** Version bump only for package @exodus/solana-api
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.27.5",
3
+ "version": "3.27.6",
4
4
  "description": "Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Solana",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -49,7 +49,7 @@
49
49
  "@exodus/assets-testing": "^1.0.0",
50
50
  "@exodus/solana-web3.js": "^1.63.1-exodus.9-rc3"
51
51
  },
52
- "gitHead": "7b4ab87737c74a125365ae729af09dff9779b7f0",
52
+ "gitHead": "9bc961015aa41912a6cbcfde9ef98e090fe39b41",
53
53
  "bugs": {
54
54
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
55
55
  },
package/src/connection.js CHANGED
@@ -118,7 +118,7 @@ export class Connection {
118
118
  this.onMsg(json)
119
119
  } catch (e) {
120
120
  debug(e)
121
- debug('Cannot process msg:', evt.data)
121
+ debug('Cannot process msg:', evt)
122
122
  }
123
123
  }
124
124
 
@@ -135,11 +135,11 @@ export class Connection {
135
135
  }
136
136
 
137
137
  onError(evt) {
138
- debug('Error on WS:', evt.data)
138
+ debug('Error on WS:', evt)
139
139
  }
140
140
 
141
141
  onClose(evt) {
142
- debug('Closing WS')
142
+ debug('Closing WS', evt)
143
143
  clearInterval(this.pingTimeout)
144
144
  clearTimeout(this.reconnectTimeout)
145
145
  this.onConnectionClose(evt)
@@ -5,6 +5,7 @@ import assert from 'minimalistic-assert'
5
5
  import ms from 'ms'
6
6
 
7
7
  import { DEFAULT_POOL_ADDRESS } from '../account-state.js'
8
+ import { mergeByTxId } from './merge-transactions.js'
8
9
 
9
10
  const DEFAULT_REMOTE_CONFIG = {
10
11
  clarityUrl: [],
@@ -261,7 +262,7 @@ export class SolanaClarityMonitor extends BaseMonitor {
261
262
  mappedTransactions.push(item)
262
263
  }
263
264
 
264
- const logItemsByAsset = lodash.groupBy(mappedTransactions, (item) => item.coinName)
265
+ const logItemsByAsset = lodash.groupBy(mergeByTxId(mappedTransactions), 'coinName')
265
266
  return {
266
267
  logItemsByAsset,
267
268
  hasNewTxs: transactions.length > 0,
@@ -0,0 +1,40 @@
1
+ import lodash from 'lodash'
2
+
3
+ const { groupBy, uniq } = lodash
4
+
5
+ // Merge multiple transfers of the same token within one transaction (e.g., Jupiter swaps)
6
+ export function mergeByTxId(transactions) {
7
+ const grouped = groupBy(transactions, (tx) => `${tx.txId}:${tx.coinName}`)
8
+
9
+ return Object.values(grouped).map((group) => {
10
+ if (group.length === 1) return group[0]
11
+
12
+ const base = group[0]
13
+ const currency = base.currencies[base.coinName]
14
+ const totalAmount = group.reduce((sum, tx) => sum.add(tx.coinAmount), currency.ZERO)
15
+
16
+ if (base.coinAmount.isPositive) {
17
+ return {
18
+ ...base,
19
+ coinAmount: totalAmount,
20
+ from: uniq(group.flatMap((tx) => tx.from)),
21
+ data: {
22
+ ...base.data,
23
+ received: group.map((tx) => ({
24
+ address: tx.from[0],
25
+ amount: tx.coinAmount.toDefaultString({ unit: true }),
26
+ })),
27
+ },
28
+ }
29
+ }
30
+
31
+ return {
32
+ ...base,
33
+ coinAmount: totalAmount,
34
+ data: {
35
+ ...base.data,
36
+ sent: group.flatMap((tx) => tx.data?.sent || []),
37
+ },
38
+ }
39
+ })
40
+ }
@@ -4,6 +4,7 @@ import assert from 'minimalistic-assert'
4
4
  import ms from 'ms'
5
5
 
6
6
  import { DEFAULT_POOL_ADDRESS } from '../account-state.js'
7
+ import { mergeByTxId } from './merge-transactions.js'
7
8
 
8
9
  const DEFAULT_REMOTE_CONFIG = {
9
10
  rpcs: [],
@@ -244,7 +245,7 @@ export class SolanaMonitor extends BaseMonitor {
244
245
  mappedTransactions.push(item)
245
246
  }
246
247
 
247
- const logItemsByAsset = _.groupBy(mappedTransactions, (item) => item.coinName)
248
+ const logItemsByAsset = _.groupBy(mergeByTxId(mappedTransactions), 'coinName')
248
249
  return {
249
250
  logItemsByAsset,
250
251
  hasNewTxs: transactions.length > 0,
package/src/ws-api.js CHANGED
@@ -2,6 +2,7 @@ import { PublicKey, Token, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, U64 } from '
2
2
  import lodash from 'lodash'
3
3
 
4
4
  import { Connection } from './connection.js'
5
+ import { mergeByTxId } from './tx-log/merge-transactions.js'
5
6
  import { parseTransaction } from './tx-parser.js'
6
7
  import { isSolAddressPoisoningTx } from './txs-utils.js'
7
8
 
@@ -262,7 +263,7 @@ export class WsApi {
262
263
  mappedTransactions.push(item)
263
264
  }
264
265
 
265
- const logItemsByAsset = lodash.groupBy(mappedTransactions, (item) => item.coinName)
266
+ const logItemsByAsset = lodash.groupBy(mergeByTxId(mappedTransactions), 'coinName')
266
267
  return {
267
268
  logItemsByAsset,
268
269
  cursorState: transactions[0]?.id ? { cursor: transactions[0].id } : {},