@exodus/solana-api 3.20.5 → 3.20.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 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.20.7](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.20.6...@exodus/solana-api@3.20.7) (2025-08-12)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix(solana): do not use coinAmount for staking txs (#6243)
13
+
14
+
15
+
16
+ ## [3.20.6](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.20.5...@exodus/solana-api@3.20.6) (2025-07-18)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+
22
+ * fix: SOL parse mint transaction (#6045)
23
+
24
+
25
+
6
26
  ## [3.20.5](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.20.4...@exodus/solana-api@3.20.5) (2025-07-11)
7
27
 
8
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.20.5",
3
+ "version": "3.20.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",
@@ -46,7 +46,7 @@
46
46
  "@exodus/assets-testing": "^1.0.0",
47
47
  "@exodus/solana-web3.js": "^1.63.1-exodus.9-rc3"
48
48
  },
49
- "gitHead": "a9e60de2523a51767c196be24865fa9d0bde7629",
49
+ "gitHead": "baa995bca5eb4f7c45041158f680e6b8859996e8",
50
50
  "bugs": {
51
51
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
52
52
  },
package/src/api.js CHANGED
@@ -22,7 +22,11 @@ import urljoin from 'url-join'
22
22
 
23
23
  import { Connection } from './connection.js'
24
24
  import { getStakeActivation } from './get-stake-activation/index.js'
25
- import { isSolTransferInstruction, isSplTransferInstruction } from './txs-utils.js'
25
+ import {
26
+ isSolTransferInstruction,
27
+ isSplMintInstruction,
28
+ isSplTransferInstruction,
29
+ } from './txs-utils.js'
26
30
 
27
31
  const createApi = createApiCJS.default || createApiCJS
28
32
 
@@ -473,12 +477,15 @@ export class Api {
473
477
  (ix) =>
474
478
  ix.parsed &&
475
479
  (isSplTransferInstruction({ program: ix.program, type: ix.parsed.type }) ||
480
+ isSplMintInstruction({ program: ix.program, type: ix.parsed.type }) ||
476
481
  (!includeUnparsed &&
477
482
  isSolTransferInstruction({ program: ix.program, type: ix.parsed.type })))
478
483
  )
479
484
  .map((ix) => {
480
- const source = lodash.get(ix, 'parsed.info.source')
481
- const destination = lodash.get(ix, 'parsed.info.destination')
485
+ let source = lodash.get(ix, 'parsed.info.source')
486
+ const destination = isSplMintInstruction({ program: ix.program, type: ix.parsed.type })
487
+ ? lodash.get(ix, 'parsed.info.account') // only for minting
488
+ : lodash.get(ix, 'parsed.info.destination')
482
489
  const amount = Number(
483
490
  lodash.get(ix, 'parsed.info.amount', 0) ||
484
491
  lodash.get(ix, 'parsed.info.tokenAmount.amount', 0)
@@ -545,6 +552,10 @@ export class Api {
545
552
  })
546
553
  if (!tokenAccount) return
547
554
 
555
+ if (isSplMintInstruction({ program: ix.program, type: ix.parsed.type })) {
556
+ source = lodash.get(ix, 'parsed.info.mintAuthority')
557
+ }
558
+
548
559
  const isSending = tokenAccountsByOwner.some(({ tokenAccountAddress }) => {
549
560
  return [source].includes(tokenAccountAddress)
550
561
  })
@@ -552,6 +563,8 @@ export class Api {
552
563
  // owner if it's a send tx
553
564
  return {
554
565
  id: txId,
566
+ program: ix.program,
567
+ type: ix.parsed.type,
555
568
  slot: txDetails.slot,
556
569
  owner: isSending ? ownerAddress : null,
557
570
  from: isSending ? ownerAddress : source,
@@ -679,6 +692,7 @@ export class Api {
679
692
  // Parse Token txs
680
693
  const tokenTxs = this._parseTokenTransfers({
681
694
  instructions,
695
+ innerInstructions,
682
696
  tokenAccountsByOwner,
683
697
  ownerAddress,
684
698
  fee,
@@ -769,6 +783,7 @@ export class Api {
769
783
 
770
784
  _parseTokenTransfers({
771
785
  instructions,
786
+ innerInstructions = [],
772
787
  tokenAccountsByOwner,
773
788
  ownerAddress,
774
789
  fee,
@@ -780,8 +795,9 @@ export class Api {
780
795
  preTokenBalances.length === 0 &&
781
796
  postTokenBalances.length === 0 &&
782
797
  !Array.isArray(tokenAccountsByOwner)
783
- )
798
+ ) {
784
799
  return []
800
+ }
785
801
 
786
802
  const tokenTxs = []
787
803
 
@@ -791,10 +807,13 @@ export class Api {
791
807
  if (isSplTransferInstruction({ program, type })) {
792
808
  let tokenAccount = lodash.find(tokenAccountsByOwner, { tokenAccountAddress: source })
793
809
  const isSending = !!tokenAccount
794
- if (!isSending)
810
+ if (!isSending) {
811
+ // receiving
795
812
  tokenAccount = lodash.find(tokenAccountsByOwner, {
796
813
  tokenAccountAddress: destination,
797
- }) // receiving
814
+ })
815
+ }
816
+
798
817
  if (!tokenAccount) return // no transfers with our addresses involved
799
818
 
800
819
  const owner = isSending ? ownerAddress : null
@@ -829,6 +848,36 @@ export class Api {
829
848
  }
830
849
  })
831
850
 
851
+ innerInstructions.forEach((parsedIx) => {
852
+ const { type, program, amount, from, to } = parsedIx
853
+
854
+ // Handle token minting (mintTo, mintToChecked)
855
+ if (isSplMintInstruction({ program, type })) {
856
+ const {
857
+ token: { tokenAccountAddress },
858
+ } = parsedIx
859
+
860
+ // Check if the destination token account belongs to our owner
861
+ const tokenAccount = lodash.find(tokenAccountsByOwner, {
862
+ tokenAccountAddress,
863
+ })
864
+
865
+ if (!tokenAccount) return // not our token account
866
+
867
+ delete tokenAccount.balance
868
+ delete tokenAccount.owner
869
+
870
+ tokenTxs.push({
871
+ owner: null, // no owner for minting (it's created from thin air)
872
+ token: tokenAccount,
873
+ from, // mint address as the source
874
+ to, // our address as recipient
875
+ amount: Number(amount || 0),
876
+ fee: 0, // no fee for receiving minted tokens
877
+ })
878
+ }
879
+ })
880
+
832
881
  return tokenTxs
833
882
  }
834
883
 
@@ -921,8 +970,10 @@ export class Api {
921
970
  if (
922
971
  tokenName === 'unknown' ||
923
972
  (filterByTokens.length > 0 && !filterByTokens.includes(tokenName))
924
- )
973
+ ) {
925
974
  return acc // filter by supported tokens only
975
+ }
976
+
926
977
  if (acc[tokenName]) {
927
978
  acc[tokenName] += Number(balance)
928
979
  }
package/src/connection.js CHANGED
@@ -54,8 +54,10 @@ export class Connection {
54
54
  console.log(`solana ws: reply timeout (${method}) - ${JSON.stringify(params)} - ${id}`)
55
55
  resolve(null)
56
56
  }, TIMEOUT)
57
- if (typeof this.rpcQueue[id].timeout.unref === 'function')
57
+ if (typeof this.rpcQueue[id].timeout.unref === 'function') {
58
58
  this.rpcQueue[id].timeout.unref()
59
+ }
60
+
59
61
  this.ws.send(JSON.stringify({ jsonrpc: '2.0', method, params, id }))
60
62
  })
61
63
  },
@@ -254,7 +254,7 @@ export const createUnsignedTxForSend = async ({
254
254
  }
255
255
 
256
256
  export const extractTxLogData = async ({ unsignedTx, api }) => {
257
- if (!unsignedTx.txData.transactionBuffer)
257
+ if (!unsignedTx.txData.transactionBuffer) {
258
258
  return {
259
259
  method: unsignedTx.txData.method,
260
260
  from: unsignedTx.txData.from,
@@ -264,6 +264,7 @@ export const extractTxLogData = async ({ unsignedTx, api }) => {
264
264
  usedFeePayer: unsignedTx.txMeta.usedFeePayer,
265
265
  fee: unsignedTx.txMeta.fee,
266
266
  }
267
+ }
267
268
 
268
269
  const txData = await parseTxBuffer(unsignedTx.txData.transactionBuffer, api)
269
270
  return {
package/src/get-fees.js CHANGED
@@ -35,8 +35,9 @@ export const getFeeAsyncFactory = ({ api }) => {
35
35
 
36
36
  const stakeAddresses = []
37
37
  for (const [addr, info] of Object.entries(stakingInfo.accounts || {})) {
38
- if (method === 'undelegate' && (info.state === 'active' || info.state === 'activating'))
38
+ if (method === 'undelegate' && (info.state === 'active' || info.state === 'activating')) {
39
39
  stakeAddresses.push(addr)
40
+ }
40
41
  }
41
42
 
42
43
  rest.stakeAddresses = stakeAddresses
@@ -21,12 +21,13 @@ export async function getStakeActivation(api, stakeAddress) {
21
21
  })(),
22
22
  ])
23
23
 
24
- if (!stakeAccount)
24
+ if (!stakeAccount) {
25
25
  return {
26
26
  status: 'inactive',
27
27
  active: 0,
28
28
  inactive: 0,
29
29
  }
30
+ }
30
31
 
31
32
  const rentExemptReserve = stakeAccount.data.parsed.info.meta.rentExemptReserve
32
33
  if (stakeAccount.data.parsed.discriminant === 1) {
package/src/tx-send.js CHANGED
@@ -62,17 +62,27 @@ export const createAndBroadcastTXFactory =
62
62
  : asset.feeAsset.currency.baseUnit(txLogData.fee)
63
63
 
64
64
  const isStakingTx = ['delegate', 'undelegate', 'withdraw'].includes(method)
65
- const coinAmount =
66
- (isStakingTx ? amount?.abs() : selfSend ? asset.currency.ZERO : amount?.abs().negate()) ||
67
- asset.currency.ZERO
65
+ let coinAmount = asset.currency.ZERO
66
+
67
+ if (amount) {
68
+ const absoluteAmount = amount.abs()
69
+ if (isStakingTx) {
70
+ coinAmount = absoluteAmount
71
+ } else if (!selfSend) {
72
+ coinAmount = absoluteAmount.negate()
73
+ }
74
+ }
75
+
76
+ let data = Object.create(null)
68
77
 
69
- let data
70
78
  if (isStakingTx) {
71
79
  data = {
72
- staking: { ...txLogData.stakingParams, stake: coinAmount.toBaseNumber() },
80
+ staking: {
81
+ ...txLogData.stakingParams,
82
+ stake: coinAmount.toBaseNumber(),
83
+ },
73
84
  }
74
- } else {
75
- data = Object.create(null)
85
+ coinAmount = asset.currency.ZERO
76
86
  }
77
87
 
78
88
  const tx = {
package/src/txs-utils.js CHANGED
@@ -4,6 +4,8 @@ const TRANSFER_INSTRUCTION_TYPES = new Set([
4
4
  'transferCheckedWithFee',
5
5
  ])
6
6
 
7
+ const MINT_INSTRUCTION_TYPES = new Set(['mintTo', 'mintToChecked'])
8
+
7
9
  const isSolanaTx = (tx) => tx.coinName === 'solana'
8
10
  export const isSolanaStaking = (tx) =>
9
11
  isSolanaTx(tx) && ['createAccountWithSeed', 'delegate'].includes(tx?.data?.staking?.method)
@@ -18,3 +20,6 @@ export const isSplTransferInstruction = ({ program, type }) =>
18
20
 
19
21
  export const isSolTransferInstruction = ({ program, type }) =>
20
22
  program === 'system' && TRANSFER_INSTRUCTION_TYPES.has(type)
23
+
24
+ export const isSplMintInstruction = ({ program, type }) =>
25
+ program === 'spl-token' && MINT_INSTRUCTION_TYPES.has(type)