@exodus/solana-api 3.25.0 → 3.25.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 +18 -0
- package/package.json +3 -3
- package/src/account-state.js +0 -2
- package/src/api.js +0 -63
- package/src/create-unsigned-tx-for-send.js +9 -71
- package/src/tx-log/clarity-monitor.js +10 -93
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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.25.2](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.25.1...@exodus/solana-api@3.25.2) (2025-11-04)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @exodus/solana-api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [3.25.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.25.0...@exodus/solana-api@3.25.1) (2025-11-03)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
* fix(SOL): Fix mint address key mapping in clarity monitor balance aggregation (#6839)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## [3.25.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.24.1...@exodus/solana-api@3.25.0) (2025-10-30)
|
|
7
25
|
|
|
8
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-api",
|
|
3
|
-
"version": "3.25.
|
|
3
|
+
"version": "3.25.2",
|
|
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",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@exodus/fetch": "^1.7.3",
|
|
34
34
|
"@exodus/models": "^12.0.1",
|
|
35
35
|
"@exodus/simple-retry": "^0.0.6",
|
|
36
|
-
"@exodus/solana-lib": "^3.15.
|
|
36
|
+
"@exodus/solana-lib": "^3.15.2",
|
|
37
37
|
"@exodus/solana-meta": "^2.0.2",
|
|
38
38
|
"@exodus/timer": "^1.1.1",
|
|
39
39
|
"debug": "^4.1.1",
|
|
@@ -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": "c327da082bf6b55c270d4ab754c2abdc73e0a8de",
|
|
53
53
|
"bugs": {
|
|
54
54
|
"url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
|
|
55
55
|
},
|
package/src/account-state.js
CHANGED
|
@@ -19,11 +19,9 @@ export const createAccountState = ({ assetList }) => {
|
|
|
19
19
|
cursor: '',
|
|
20
20
|
balance: asset.currency.ZERO,
|
|
21
21
|
tokenBalances: Object.create(null),
|
|
22
|
-
delegatedTokenAmounts: Object.create(null),
|
|
23
22
|
rentExemptAmount: asset.currency.ZERO,
|
|
24
23
|
accountSize: 0,
|
|
25
24
|
ownerChanged: false,
|
|
26
|
-
delegatedTokenAccounts: [],
|
|
27
25
|
stakingInfo: {
|
|
28
26
|
loaded: false,
|
|
29
27
|
staking: {
|
package/src/api.js
CHANGED
|
@@ -962,69 +962,6 @@ export class Api {
|
|
|
962
962
|
: tokenAccounts
|
|
963
963
|
}
|
|
964
964
|
|
|
965
|
-
/**
|
|
966
|
-
* Get token account states for both owned and delegated accounts
|
|
967
|
-
* @param {string} address - The wallet address (potential delegate)
|
|
968
|
-
* @param {Array} delegatedAccounts - Array of delegated account objects from wallet-accounts
|
|
969
|
-
* @returns {Promise<Array>} Combined list of owned and delegated token accounts
|
|
970
|
-
*/
|
|
971
|
-
async getTokenAccountsIncludingDelegated(address, delegatedAccounts = []) {
|
|
972
|
-
// Get owned accounts (existing functionality)
|
|
973
|
-
const ownedAccounts = await this.getTokenAccountsByOwner(address)
|
|
974
|
-
|
|
975
|
-
// Fetch delegated account states
|
|
976
|
-
const delegatedAccountPromises = delegatedAccounts.map(async ({ delegatedAddress }) => {
|
|
977
|
-
try {
|
|
978
|
-
// Fetch the account info to get current balance and delegate status
|
|
979
|
-
const accountInfo = await this.getAccountInfo(delegatedAddress)
|
|
980
|
-
|
|
981
|
-
if (!accountInfo?.data?.parsed) return null
|
|
982
|
-
|
|
983
|
-
const parsedData = accountInfo.data.parsed
|
|
984
|
-
const info = parsedData.info
|
|
985
|
-
|
|
986
|
-
// Verify this account is actually delegated to us
|
|
987
|
-
if (info.delegate !== address) {
|
|
988
|
-
console.warn(`Delegated account ${delegatedAddress} is not delegated to ${address}`)
|
|
989
|
-
return null
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
const mintAddress = info.mint
|
|
993
|
-
const token = this.getTokenByAddress(mintAddress)
|
|
994
|
-
if (!token) return null
|
|
995
|
-
|
|
996
|
-
// Get token fee info if it's a Token2022 token
|
|
997
|
-
const { feeBasisPoints = 0, maximumFee = 0 } =
|
|
998
|
-
accountInfo.owner === TOKEN_2022_PROGRAM_ID.toBase58()
|
|
999
|
-
? await this.getTokenFeeBasisPoints(mintAddress)
|
|
1000
|
-
: {}
|
|
1001
|
-
|
|
1002
|
-
return {
|
|
1003
|
-
tokenAccountAddress: delegatedAddress,
|
|
1004
|
-
owner: info.owner, // External owner from on-chain data
|
|
1005
|
-
delegate: address, // You are the delegate
|
|
1006
|
-
isDelegated: true,
|
|
1007
|
-
delegatedAmount: info.delegatedAmount?.amount || '0',
|
|
1008
|
-
tokenName: token.name,
|
|
1009
|
-
ticker: token.ticker,
|
|
1010
|
-
balance: info.tokenAmount?.amount || '0',
|
|
1011
|
-
mintAddress,
|
|
1012
|
-
tokenProgram: accountInfo.owner, // TOKEN_PROGRAM_ID or TOKEN_2022_PROGRAM_ID
|
|
1013
|
-
decimals: info.tokenAmount?.decimals || token.decimals,
|
|
1014
|
-
feeBasisPoints,
|
|
1015
|
-
maximumFee,
|
|
1016
|
-
}
|
|
1017
|
-
} catch (error) {
|
|
1018
|
-
console.error(`Failed to fetch delegated account ${delegatedAddress}:`, error)
|
|
1019
|
-
return null
|
|
1020
|
-
}
|
|
1021
|
-
})
|
|
1022
|
-
|
|
1023
|
-
const delegatedAccountStates = await Promise.all(delegatedAccountPromises)
|
|
1024
|
-
|
|
1025
|
-
return [...ownedAccounts, ...delegatedAccountStates.filter(Boolean)]
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
965
|
async getTokensBalancesAndAccounts({ address, filterByTokens = [] }) {
|
|
1029
966
|
const accounts = await this.getTokenAccountsByOwner(address)
|
|
1030
967
|
|
|
@@ -117,79 +117,17 @@ export const createTxFactory = ({ assetClientInterface, api, feePayerClient }) =
|
|
|
117
117
|
throw err
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const [
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
assetClientInterface.getAccountState({
|
|
130
|
-
walletAccount,
|
|
131
|
-
assetName: baseAssetName,
|
|
132
|
-
}),
|
|
133
|
-
])
|
|
134
|
-
|
|
135
|
-
const delegatedAccounts = accountState?.delegatedTokenAccounts || []
|
|
136
|
-
|
|
137
|
-
// Fetch on-chain state for delegated accounts to get current balance and delegation info
|
|
138
|
-
const delegatedTokenAccountsForMint = await Promise.all(
|
|
139
|
-
delegatedAccounts
|
|
140
|
-
.filter(({ assetName: delegatedAssetName }) => {
|
|
141
|
-
// Get asset from assetClientInterface
|
|
142
|
-
const delegatedAsset = asset.name === delegatedAssetName ? asset : null
|
|
143
|
-
if (!delegatedAsset) return false
|
|
144
|
-
return delegatedAsset.mintAddress === tokenMintAddress
|
|
145
|
-
})
|
|
146
|
-
.map(async (delegatedAccount) => {
|
|
147
|
-
try {
|
|
148
|
-
const accountInfo = await api.rpcCall(
|
|
149
|
-
'getAccountInfo',
|
|
150
|
-
[delegatedAccount.delegatedAddress, { encoding: 'jsonParsed' }],
|
|
151
|
-
{ address: delegatedAccount.delegatedAddress }
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
if (!accountInfo?.value?.data?.parsed) return null
|
|
155
|
-
|
|
156
|
-
const info = accountInfo.value.data.parsed.info
|
|
157
|
-
|
|
158
|
-
// Verify delegation is still active
|
|
159
|
-
if (info.delegate !== fromAddress) {
|
|
160
|
-
console.warn(
|
|
161
|
-
`Delegated account ${delegatedAccount.delegatedAddress} is no longer delegated to ${fromAddress}`
|
|
162
|
-
)
|
|
163
|
-
return null
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
tokenAccountAddress: delegatedAccount.delegatedAddress,
|
|
168
|
-
mintAddress: tokenMintAddress,
|
|
169
|
-
balance: info.tokenAmount?.amount || '0',
|
|
170
|
-
decimals: info.tokenAmount?.decimals || 0,
|
|
171
|
-
tokenProgram: accountInfo.value.owner,
|
|
172
|
-
isDelegated: true,
|
|
173
|
-
delegatedAmount: info.delegatedAmount?.amount || '0',
|
|
174
|
-
}
|
|
175
|
-
} catch (error) {
|
|
176
|
-
console.error(
|
|
177
|
-
`Failed to fetch delegated account ${delegatedAccount.delegatedAddress}:`,
|
|
178
|
-
error
|
|
179
|
-
)
|
|
180
|
-
return null
|
|
181
|
-
}
|
|
182
|
-
})
|
|
120
|
+
const [destinationAddressType, isAssociatedTokenAccountActive, fromTokenAccountAddresses] =
|
|
121
|
+
await Promise.all([
|
|
122
|
+
api.getAddressType(toAddress),
|
|
123
|
+
api.isAssociatedTokenAccountActive(tokenAddress),
|
|
124
|
+
api.getTokenAccountsByOwner(fromAddress),
|
|
125
|
+
])
|
|
126
|
+
|
|
127
|
+
const fromTokenAddresses = fromTokenAccountAddresses.filter(
|
|
128
|
+
({ mintAddress }) => mintAddress === tokenMintAddress
|
|
183
129
|
)
|
|
184
130
|
|
|
185
|
-
const validDelegatedAccounts = delegatedTokenAccountsForMint.filter(Boolean)
|
|
186
|
-
|
|
187
|
-
// Combine owned and delegated accounts
|
|
188
|
-
const fromTokenAddresses = [
|
|
189
|
-
...fromTokenAccountAddresses.filter(({ mintAddress }) => mintAddress === tokenMintAddress),
|
|
190
|
-
...validDelegatedAccounts,
|
|
191
|
-
]
|
|
192
|
-
|
|
193
131
|
tokenParams = {
|
|
194
132
|
tokenMintAddress,
|
|
195
133
|
destinationAddressType,
|
|
@@ -250,84 +250,13 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
250
250
|
|
|
251
251
|
async getAccountsAndBalances({ refresh, address, accountState, walletAccount }) {
|
|
252
252
|
const tokens = Object.keys(this.assets).filter((name) => name !== this.asset.name)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
address,
|
|
261
|
-
filterByTokens: tokens,
|
|
262
|
-
}),
|
|
263
|
-
])
|
|
264
|
-
|
|
265
|
-
// Fetch delegated account balances - get full balances first, then delegated amounts
|
|
266
|
-
const delegatedBalances = {}
|
|
267
|
-
const delegatedTokenAmounts = {}
|
|
268
|
-
|
|
269
|
-
for (const { delegatedAddress } of delegatedAccounts) {
|
|
270
|
-
try {
|
|
271
|
-
const accountInfo = await this.api.rpcCall(
|
|
272
|
-
'getAccountInfo',
|
|
273
|
-
[delegatedAddress, { encoding: 'jsonParsed' }],
|
|
274
|
-
{ address: delegatedAddress }
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
if (!accountInfo?.value?.data?.parsed) continue
|
|
278
|
-
|
|
279
|
-
const info = accountInfo.value.data.parsed.info
|
|
280
|
-
|
|
281
|
-
// Verify this account is actually delegated to us
|
|
282
|
-
if (info.delegate !== address) {
|
|
283
|
-
console.warn(`Delegated account ${delegatedAddress} is not delegated to ${address}`)
|
|
284
|
-
continue
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const mintAddress = info.mint
|
|
288
|
-
const fullBalance = info.tokenAmount?.amount || '0'
|
|
289
|
-
const delegatedAmount = info.delegatedAmount?.amount || '0'
|
|
290
|
-
|
|
291
|
-
// Store full balance for combining with owned balances
|
|
292
|
-
const tokenName = this.api.tokens.get(mintAddress)?.name
|
|
293
|
-
if (!tokenName) continue
|
|
294
|
-
if (this.assets[tokenName]) {
|
|
295
|
-
const fullBalanceCurrency = this.assets[tokenName].currency.baseUnit(fullBalance)
|
|
296
|
-
if (delegatedBalances[mintAddress]) {
|
|
297
|
-
delegatedBalances[mintAddress] = delegatedBalances[mintAddress].add(fullBalanceCurrency)
|
|
298
|
-
} else {
|
|
299
|
-
delegatedBalances[mintAddress] = fullBalanceCurrency
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Store delegated amounts separately for the account state
|
|
304
|
-
if (this.assets[tokenName]) {
|
|
305
|
-
const delegatedAmountCurrency = this.assets[tokenName].currency.baseUnit(delegatedAmount)
|
|
306
|
-
if (!delegatedAmountCurrency.isZero()) {
|
|
307
|
-
if (!delegatedTokenAmounts[tokenName]) {
|
|
308
|
-
delegatedTokenAmounts[tokenName] = {}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
delegatedTokenAmounts[tokenName][delegatedAddress] = delegatedAmountCurrency
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
} catch (error) {
|
|
315
|
-
console.error(`Failed to fetch delegated account ${delegatedAddress}:`, error)
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Combine owned and delegated balances
|
|
320
|
-
const combinedBalances = { ...splBalances }
|
|
321
|
-
for (const [mintAddress, delegatedBalanceCurrency] of Object.entries(delegatedBalances)) {
|
|
322
|
-
const tokenName = this.api.tokens.get(mintAddress)?.name
|
|
323
|
-
if (tokenName && this.assets[tokenName]) {
|
|
324
|
-
const ownedBalance = this.assets[tokenName].currency.baseUnit(
|
|
325
|
-
combinedBalances[mintAddress] || 0
|
|
326
|
-
)
|
|
327
|
-
const totalBalance = ownedBalance.add(delegatedBalanceCurrency)
|
|
328
|
-
combinedBalances[mintAddress] = Number(totalBalance.toBaseString())
|
|
329
|
-
}
|
|
330
|
-
}
|
|
253
|
+
const [accountInfo, { balances: splBalances, accounts: tokenAccounts }] = await Promise.all([
|
|
254
|
+
this.api.getAccountInfo(address).catch(() => {}),
|
|
255
|
+
this.api.getTokensBalancesAndAccounts({
|
|
256
|
+
address,
|
|
257
|
+
filterByTokens: tokens,
|
|
258
|
+
}),
|
|
259
|
+
])
|
|
331
260
|
|
|
332
261
|
const solBalance = accountInfo?.lamports || 0
|
|
333
262
|
|
|
@@ -339,8 +268,8 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
339
268
|
|
|
340
269
|
const ownerChanged = await this.api.ownerChanged(address, accountInfo)
|
|
341
270
|
|
|
342
|
-
// we can have
|
|
343
|
-
const clientKnownTokens = omitBy(
|
|
271
|
+
// we can have splBalances for tokens that are not in our asset list
|
|
272
|
+
const clientKnownTokens = omitBy(splBalances, (v, mintAddress) => {
|
|
344
273
|
const tokenName = this.api.tokens.get(mintAddress)?.name
|
|
345
274
|
return !this.assets[tokenName]
|
|
346
275
|
})
|
|
@@ -351,9 +280,6 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
351
280
|
})
|
|
352
281
|
)
|
|
353
282
|
|
|
354
|
-
// Use owned token accounts for transaction tracking
|
|
355
|
-
const tokenAccounts = ownedTokenAccounts
|
|
356
|
-
|
|
357
283
|
const solBalanceChanged = this.#balanceChanged({
|
|
358
284
|
account: accountState,
|
|
359
285
|
newAccount: {
|
|
@@ -386,7 +312,6 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
386
312
|
account: {
|
|
387
313
|
balance,
|
|
388
314
|
tokenBalances,
|
|
389
|
-
delegatedTokenAmounts,
|
|
390
315
|
rentExemptAmount,
|
|
391
316
|
accountSize,
|
|
392
317
|
ownerChanged,
|
|
@@ -397,21 +322,13 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
397
322
|
}
|
|
398
323
|
|
|
399
324
|
async updateState({ account, cursorState, walletAccount, staking }) {
|
|
400
|
-
const {
|
|
401
|
-
balance,
|
|
402
|
-
tokenBalances,
|
|
403
|
-
delegatedTokenAmounts,
|
|
404
|
-
rentExemptAmount,
|
|
405
|
-
accountSize,
|
|
406
|
-
ownerChanged,
|
|
407
|
-
} = account
|
|
325
|
+
const { balance, tokenBalances, rentExemptAmount, accountSize, ownerChanged } = account
|
|
408
326
|
const newData = {
|
|
409
327
|
balance,
|
|
410
328
|
rentExemptAmount,
|
|
411
329
|
accountSize,
|
|
412
330
|
ownerChanged,
|
|
413
331
|
tokenBalances,
|
|
414
|
-
delegatedTokenAmounts,
|
|
415
332
|
stakingInfo: staking,
|
|
416
333
|
...cursorState,
|
|
417
334
|
}
|