@exodus/solana-api 3.27.5 → 3.27.7
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 +22 -0
- package/package.json +2 -2
- package/src/connection.js +3 -3
- package/src/tx-log/clarity-monitor.js +2 -1
- package/src/tx-log/merge-transactions.js +40 -0
- package/src/tx-log/solana-monitor.js +2 -1
- package/src/ws-api.js +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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.7](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.27.6...@exodus/solana-api@3.27.7) (2026-01-26)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* fix: Solana SPL token balance not updating via WebSocket notifications (#7323)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## [3.27.6](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.27.5...@exodus/solana-api@3.27.6) (2026-01-21)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
* fix: improve SOL WS events logging (#7293)
|
|
23
|
+
|
|
24
|
+
* fix: Solana merge multi-transfer transactions to prevent data loss (#7297)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
## [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
29
|
|
|
8
30
|
**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.
|
|
3
|
+
"version": "3.27.7",
|
|
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": "
|
|
52
|
+
"gitHead": "5194ac4148f9b6d08ec0b9b14ae1e111af04ae1a",
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
|
@@ -130,6 +131,17 @@ export class WsApi {
|
|
|
130
131
|
|
|
131
132
|
// SPL Token balance changed (both spl-token and spl-2022 have the first 165 bytes the same)
|
|
132
133
|
if (isSplTokenAccount || isSpl2022TokenAccount) {
|
|
134
|
+
// Handle jsonParsed encoding (data is an object with parsed info)
|
|
135
|
+
if (result.value.data?.parsed?.info) {
|
|
136
|
+
const parsed = result.value.data.parsed.info
|
|
137
|
+
return {
|
|
138
|
+
solAddress: parsed.owner,
|
|
139
|
+
amount: parsed.tokenAmount.amount,
|
|
140
|
+
tokenMintAddress: parsed.mint,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle base64 encoding (data is an array: [base64_string, "base64"])
|
|
133
145
|
const decoded = Token.decode(Buffer.from(result.value.data[0], 'base64'))
|
|
134
146
|
const tokenMintAddress = new PublicKey(decoded.mint).toBase58()
|
|
135
147
|
const solAddress = new PublicKey(decoded.owner).toBase58()
|
|
@@ -262,7 +274,7 @@ export class WsApi {
|
|
|
262
274
|
mappedTransactions.push(item)
|
|
263
275
|
}
|
|
264
276
|
|
|
265
|
-
const logItemsByAsset = lodash.groupBy(mappedTransactions,
|
|
277
|
+
const logItemsByAsset = lodash.groupBy(mergeByTxId(mappedTransactions), 'coinName')
|
|
266
278
|
return {
|
|
267
279
|
logItemsByAsset,
|
|
268
280
|
cursorState: transactions[0]?.id ? { cursor: transactions[0].id } : {},
|