@exodus/solana-api 3.27.4 → 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 +20 -0
- package/package.json +2 -2
- package/src/connection.js +4 -4
- 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 +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
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
|
+
|
|
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)
|
|
19
|
+
|
|
20
|
+
**Note:** Version bump only for package @exodus/solana-api
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## [3.27.4](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.27.3...@exodus/solana-api@3.27.4) (2026-01-15)
|
|
7
27
|
|
|
8
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-api",
|
|
3
|
-
"version": "3.27.
|
|
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": "
|
|
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
|
@@ -5,7 +5,7 @@ import assert from 'minimalistic-assert'
|
|
|
5
5
|
import ms from 'ms'
|
|
6
6
|
|
|
7
7
|
const DEFAULT_RECONNECT_DELAY = ms('15s')
|
|
8
|
-
const PING_INTERVAL = ms('
|
|
8
|
+
const PING_INTERVAL = ms('25s')
|
|
9
9
|
|
|
10
10
|
const debug = debugLogger('exodus:solana-api')
|
|
11
11
|
|
|
@@ -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,11 +2,14 @@ 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
|
|
|
9
|
+
// Triton Whirligig WebSocket
|
|
10
|
+
const WS_ENDPOINT = 'wss://solana-triton.a.exodus.io/whirligig' // pointing to: wss://exodus-solanama-6db3.mainnet.rpcpool.com/<token>/whirligig
|
|
8
11
|
// Helius Advanced WebSocket
|
|
9
|
-
const WS_ENDPOINT = 'wss://solana-helius-wss.a.exodus.io/ws' // pointing to: wss://atlas-mainnet.helius-rpc.com/?api-key=<API_KEY>
|
|
12
|
+
// const WS_ENDPOINT = 'wss://solana-helius-wss.a.exodus.io/ws' // pointing to: wss://atlas-mainnet.helius-rpc.com/?api-key=<API_KEY>
|
|
10
13
|
|
|
11
14
|
export class WsApi {
|
|
12
15
|
constructor({ rpcUrl, wsUrl, assets }) {
|
|
@@ -88,7 +91,10 @@ export class WsApi {
|
|
|
88
91
|
{
|
|
89
92
|
vote: false,
|
|
90
93
|
// failed: true,
|
|
91
|
-
|
|
94
|
+
accounts: {
|
|
95
|
+
include: addresses,
|
|
96
|
+
},
|
|
97
|
+
// accountInclude: addresses, // Helius
|
|
92
98
|
},
|
|
93
99
|
{
|
|
94
100
|
commitment: 'confirmed',
|
|
@@ -147,7 +153,8 @@ export class WsApi {
|
|
|
147
153
|
tokenAccountsByOwner,
|
|
148
154
|
result,
|
|
149
155
|
}) {
|
|
150
|
-
const
|
|
156
|
+
const rawTransaction = result?.value ? result.value.transaction : result.transaction // for Triton Whirligig OR Helius Advanced WS
|
|
157
|
+
const parsedTx = parseTransaction(address, rawTransaction, tokenAccountsByOwner)
|
|
151
158
|
const timestamp = Date.now() // the notification event has no blockTime
|
|
152
159
|
|
|
153
160
|
if (!parsedTx.from && parsedTx.tokenTxs?.length === 0) return { logItemsByAsset: {} } // cannot parse it
|
|
@@ -256,7 +263,7 @@ export class WsApi {
|
|
|
256
263
|
mappedTransactions.push(item)
|
|
257
264
|
}
|
|
258
265
|
|
|
259
|
-
const logItemsByAsset = lodash.groupBy(mappedTransactions,
|
|
266
|
+
const logItemsByAsset = lodash.groupBy(mergeByTxId(mappedTransactions), 'coinName')
|
|
260
267
|
return {
|
|
261
268
|
logItemsByAsset,
|
|
262
269
|
cursorState: transactions[0]?.id ? { cursor: transactions[0].id } : {},
|