@exodus/solana-api 3.30.8 → 3.30.9

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,16 @@
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.30.9](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.30.8...@exodus/solana-api@3.30.9) (2026-04-04)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: sponsored Solana token sends when CU simulation fails (#7707)
13
+
14
+
15
+
6
16
  ## [3.30.8](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.30.7...@exodus/solana-api@3.30.8) (2026-04-02)
7
17
 
8
18
  **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.30.8",
3
+ "version": "3.30.9",
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,8 +33,8 @@
33
33
  "@exodus/fetch": "^1.7.3",
34
34
  "@exodus/models": "^13.0.0",
35
35
  "@exodus/simple-retry": "^0.0.6",
36
- "@exodus/solana-lib": "^3.22.3",
37
- "@exodus/solana-meta": "^2.0.2",
36
+ "@exodus/solana-lib": "^3.22.5",
37
+ "@exodus/solana-meta": "^2.9.0",
38
38
  "@exodus/timer": "^1.1.1",
39
39
  "debug": "^4.1.1",
40
40
  "delay": "^4.0.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": "c4e253a7ee39a0ee2524ff3c0e528fe3614d15de",
52
+ "gitHead": "a2cd3fbc02d07f1c3dc127be4d7896292d2dc967",
53
53
  "bugs": {
54
54
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
55
55
  },
@@ -10,7 +10,9 @@ import assert from 'minimalistic-assert'
10
10
 
11
11
  import { maybeAddFeePayerWithAuth } from './fee-payer.js'
12
12
 
13
- const CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS = 300
13
+ const COMPUTE_BUDGET_INSTRUCTIONS_CU = 300
14
+ const SOL_TRANSFER_CU = 150 + COMPUTE_BUDGET_INSTRUCTIONS_CU
15
+ const DEFAULT_COMPUTE_UNIT_LIMIT = 200_000
14
16
  const TOKEN_ACCOUNT_CREATION_SIZE = 165 // size of the token account
15
17
 
16
18
  export const createTxFactory = ({ assetClientInterface, api, feePayerClient }) => {
@@ -65,6 +67,7 @@ export const createTxFactory = ({ assetClientInterface, api, feePayerClient }) =
65
67
 
66
68
  const feeData =
67
69
  providedFeeData ?? (await assetClientInterface.getFeeConfig({ assetName: baseAssetName }))
70
+ const shouldTryFeePayer = feeData.enableFeePayer && useFeePayer
68
71
 
69
72
  const fromAddress =
70
73
  providedFromAddress ??
@@ -211,18 +214,22 @@ export const createTxFactory = ({ assetClientInterface, api, feePayerClient }) =
211
214
  reference,
212
215
  memo,
213
216
  // Effective: platform enable AND per-tx intent
214
- useFeePayer,
217
+ useFeePayer: shouldTryFeePayer,
215
218
  ...tokenParams,
216
219
  ...stakingParams,
217
220
  ...magicEdenParams,
218
221
  })
219
222
 
220
223
  unsignedTx.txMeta.stakingParams = stakingParams
224
+ const isFeeSponsoredTokenTransfer = isToken && unsignedTx.txMeta.useFeePayer && !nft && !method
225
+ const shouldUseSafeComputeUnitLimit =
226
+ isFeeSponsoredTokenTransfer &&
227
+ (!unsignedTx.txData.isAssociatedTokenAccountActive || isExchange)
221
228
 
222
229
  const resolveUnitConsumed = async () => {
223
230
  // this avoids unnecessary simulations. Also the simulation fails with InsufficientFundsForRent when sending all.
224
231
  if (asset.name === asset.baseAsset.name && amount && !nft && !method) {
225
- return 150 + CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS
232
+ return SOL_TRANSFER_CU
226
233
  }
227
234
 
228
235
  // Simulate with unsigned transaction. The fee payer service is deterministic -
@@ -237,10 +244,31 @@ export const createTxFactory = ({ assetClientInterface, api, feePayerClient }) =
237
244
  // we use this method to compute unitsConsumed
238
245
  // we can throw error here and fallback to ~0.025 SOL or estimate fee based on the method
239
246
  console.log('error getting units consumed:', err)
247
+ const serializedError = typeof err === 'string' ? err : JSON.stringify(err)
248
+ const isRentOrBalanceError = /insufficientfunds|rent/i.test(serializedError)
249
+ const isAccountNotFoundError = /accountnotfound/i.test(serializedError)
250
+
251
+ // Fee sponsorship is injected after this simulation step. Overestimate CU to
252
+ // avoid on-chain failures; the fee payer absorbs the cost.
253
+ if (!unitsConsumed && isFeeSponsoredTokenTransfer && isRentOrBalanceError) {
254
+ return DEFAULT_COMPUTE_UNIT_LIMIT
255
+ }
256
+
257
+ if (!unitsConsumed && isFeeSponsoredTokenTransfer && isAccountNotFoundError) {
258
+ const senderBaseBalanceLamports = BigInt(await api.getBalance(fromAddress))
259
+ if (senderBaseBalanceLamports === BigInt(0)) return DEFAULT_COMPUTE_UNIT_LIMIT
260
+ }
261
+
240
262
  if (!unitsConsumed) throw new Error(err)
241
263
  }
242
264
 
243
- return unitsConsumed + CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS
265
+ const estimatedComputeUnitLimit = unitsConsumed + COMPUTE_BUDGET_INSTRUCTIONS_CU
266
+
267
+ if (shouldUseSafeComputeUnitLimit && estimatedComputeUnitLimit < DEFAULT_COMPUTE_UNIT_LIMIT) {
268
+ return DEFAULT_COMPUTE_UNIT_LIMIT
269
+ }
270
+
271
+ return estimatedComputeUnitLimit
244
272
  }
245
273
 
246
274
  const priorityFee = feeData.priorityFee
@@ -272,7 +300,6 @@ export const createTxFactory = ({ assetClientInterface, api, feePayerClient }) =
272
300
  const tx = await maybeAddFeePayerWithAuth({
273
301
  unsignedTx,
274
302
  feePayerClient,
275
- enableFeePayer: feeData.enableFeePayer,
276
303
  })
277
304
 
278
305
  const fee = tx.txMeta.usedFeePayer ? asset.feeAsset.currency.ZERO : calculatedFee
package/src/fee-payer.js CHANGED
@@ -145,11 +145,11 @@ export const feePayerClientFactory = ({
145
145
  * @param {Object} params.unsignedTx - The unsigned transaction
146
146
  * @param {Object} params.feePayerClient - The fee payer client instance
147
147
  */
148
- export const maybeAddFeePayerWithAuth = async ({ unsignedTx, feePayerClient, enableFeePayer }) => {
148
+ export const maybeAddFeePayerWithAuth = async ({ unsignedTx, feePayerClient }) => {
149
149
  let unsignedTxWithFeePayer = unsignedTx
150
150
 
151
- // Skip if no client or explicitly disabled
152
- if (!feePayerClient || !enableFeePayer || !unsignedTx.txMeta.useFeePayer) {
151
+ // `txMeta.useFeePayer` already combines platform-level and per-tx intent.
152
+ if (!feePayerClient || !unsignedTx.txMeta.useFeePayer) {
153
153
  unsignedTxWithFeePayer.txMeta.usedFeePayer = false
154
154
  return unsignedTxWithFeePayer
155
155
  }