@exodus/solana-api 3.25.1 → 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 +8 -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 -105
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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
|
+
|
|
6
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)
|
|
7
15
|
|
|
8
16
|
|
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,96 +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
|
-
const delegatedAmountCurrency = this.assets[tokenName].currency.baseUnit(delegatedAmount)
|
|
303
|
-
if (!delegatedAmountCurrency.equals(this.assets[tokenName].currency.ZERO)) {
|
|
304
|
-
if (!delegatedTokenAmounts[tokenName]) {
|
|
305
|
-
delegatedTokenAmounts[tokenName] = {}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
delegatedTokenAmounts[tokenName][delegatedAddress] = delegatedAmountCurrency
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
} catch (error) {
|
|
312
|
-
console.error(`Failed to fetch delegated account ${delegatedAddress}:`, error)
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const combinedBalances = {}
|
|
317
|
-
|
|
318
|
-
const tokenNameToMintAddress = {}
|
|
319
|
-
for (const account of ownedTokenAccounts || []) {
|
|
320
|
-
if (account.tokenName && account.mintAddress) {
|
|
321
|
-
tokenNameToMintAddress[account.tokenName] = account.mintAddress
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
for (const [tokenName, balance] of Object.entries(splBalances)) {
|
|
326
|
-
const mintAddress = tokenNameToMintAddress[tokenName]
|
|
327
|
-
|
|
328
|
-
if (mintAddress) {
|
|
329
|
-
combinedBalances[mintAddress] = balance
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
for (const [mintAddress, delegatedBalanceCurrency] of Object.entries(delegatedBalances)) {
|
|
334
|
-
const tokenName = this.api.tokens.get(mintAddress)?.name
|
|
335
|
-
if (tokenName && this.assets[tokenName]) {
|
|
336
|
-
const ownedBalance = this.assets[tokenName].currency.baseUnit(
|
|
337
|
-
combinedBalances[mintAddress] || 0
|
|
338
|
-
)
|
|
339
|
-
const totalBalance = ownedBalance.add(delegatedBalanceCurrency)
|
|
340
|
-
combinedBalances[mintAddress] = Number(totalBalance.toBaseString())
|
|
341
|
-
}
|
|
342
|
-
}
|
|
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
|
+
])
|
|
343
260
|
|
|
344
261
|
const solBalance = accountInfo?.lamports || 0
|
|
345
262
|
|
|
@@ -351,8 +268,8 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
351
268
|
|
|
352
269
|
const ownerChanged = await this.api.ownerChanged(address, accountInfo)
|
|
353
270
|
|
|
354
|
-
// we can have
|
|
355
|
-
const clientKnownTokens = omitBy(
|
|
271
|
+
// we can have splBalances for tokens that are not in our asset list
|
|
272
|
+
const clientKnownTokens = omitBy(splBalances, (v, mintAddress) => {
|
|
356
273
|
const tokenName = this.api.tokens.get(mintAddress)?.name
|
|
357
274
|
return !this.assets[tokenName]
|
|
358
275
|
})
|
|
@@ -363,9 +280,6 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
363
280
|
})
|
|
364
281
|
)
|
|
365
282
|
|
|
366
|
-
// Use owned token accounts for transaction tracking
|
|
367
|
-
const tokenAccounts = ownedTokenAccounts
|
|
368
|
-
|
|
369
283
|
const solBalanceChanged = this.#balanceChanged({
|
|
370
284
|
account: accountState,
|
|
371
285
|
newAccount: {
|
|
@@ -398,7 +312,6 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
398
312
|
account: {
|
|
399
313
|
balance,
|
|
400
314
|
tokenBalances,
|
|
401
|
-
delegatedTokenAmounts,
|
|
402
315
|
rentExemptAmount,
|
|
403
316
|
accountSize,
|
|
404
317
|
ownerChanged,
|
|
@@ -409,21 +322,13 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
409
322
|
}
|
|
410
323
|
|
|
411
324
|
async updateState({ account, cursorState, walletAccount, staking }) {
|
|
412
|
-
const {
|
|
413
|
-
balance,
|
|
414
|
-
tokenBalances,
|
|
415
|
-
delegatedTokenAmounts,
|
|
416
|
-
rentExemptAmount,
|
|
417
|
-
accountSize,
|
|
418
|
-
ownerChanged,
|
|
419
|
-
} = account
|
|
325
|
+
const { balance, tokenBalances, rentExemptAmount, accountSize, ownerChanged } = account
|
|
420
326
|
const newData = {
|
|
421
327
|
balance,
|
|
422
328
|
rentExemptAmount,
|
|
423
329
|
accountSize,
|
|
424
330
|
ownerChanged,
|
|
425
331
|
tokenBalances,
|
|
426
|
-
delegatedTokenAmounts,
|
|
427
332
|
stakingInfo: staking,
|
|
428
333
|
...cursorState,
|
|
429
334
|
}
|