@exodus/solana-lib 3.22.1 → 3.22.4

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,32 @@
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.22.4](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.22.2...@exodus/solana-lib@3.22.4) (2026-03-30)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: SOL memo program (#7696)
13
+
14
+
15
+
16
+ ## [3.22.3](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.22.2...@exodus/solana-lib@3.22.3) (2026-03-30)
17
+
18
+ **Note:** Version bump only for package @exodus/solana-lib
19
+
20
+
21
+
22
+
23
+
24
+ ## [3.22.2](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.22.1...@exodus/solana-lib@3.22.2) (2026-03-27)
25
+
26
+ **Note:** Version bump only for package @exodus/solana-lib
27
+
28
+
29
+
30
+
31
+
6
32
  ## [3.22.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.22.0...@exodus/solana-lib@3.22.1) (2026-03-18)
7
33
 
8
34
  **Note:** Version bump only for package @exodus/solana-lib
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-lib",
3
- "version": "3.22.1",
3
+ "version": "3.22.4",
4
4
  "description": "Solana utils, such as for cryptography, address encoding/decoding, transaction building, etc.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -48,5 +48,5 @@
48
48
  "type": "git",
49
49
  "url": "git+https://github.com/ExodusMovement/assets.git"
50
50
  },
51
- "gitHead": "e746a88ef78a0e7167587bfa14068ceb494d6fd7"
51
+ "gitHead": "0d8639ef50e192df1f302f0ac7ff378d919cc536"
52
52
  }
package/src/constants.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ ComputeBudgetProgram,
2
3
  PublicKey,
3
4
  StakeProgram,
4
5
  SystemProgram,
@@ -15,9 +16,7 @@ export const STAKE_PROGRAM_ID = StakeProgram.programId
15
16
 
16
17
  export const TOKEN_PROGRAM_ID = TokenProgram.programId
17
18
 
18
- export const COMPUTE_BUDGET_PROGRAM_ID = new PublicKey(
19
- 'ComputeBudget111111111111111111111111111111'
20
- )
19
+ export const COMPUTE_BUDGET_PROGRAM_ID = ComputeBudgetProgram.programId
21
20
 
22
21
  export const TOKEN_2022_PROGRAM_ID = Token2022Program.programId
23
22
 
@@ -0,0 +1,18 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ import { MEMO_PROGRAM_ID } from '../constants.js'
4
+ import { TransactionInstruction } from '../vendor/index.js'
5
+
6
+ /**
7
+ * Builds a legacy Memo program instruction (spl-memo).
8
+ * @see https://spl.solana.com/memo
9
+ */
10
+ export function createMemoInstruction(memo) {
11
+ assert(typeof memo === 'string', 'memo must be a string')
12
+
13
+ return new TransactionInstruction({
14
+ keys: [],
15
+ programId: MEMO_PROGRAM_ID,
16
+ data: Buffer.from(memo, 'utf8'),
17
+ })
18
+ }
@@ -2,6 +2,7 @@ import * as BufferLayout from '@exodus/buffer-layout'
2
2
 
3
3
  import {
4
4
  MPL_TOKEN_METADATA_PROGRAM_ID,
5
+ SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
5
6
  SYSTEM_PROGRAM_ID,
6
7
  SYSVAR_INSTRUCTIONS_PUBKEY,
7
8
  TOKEN_PROGRAM_ID,
@@ -13,7 +14,6 @@ import {
13
14
  getTokenRecordPDA,
14
15
  } from '../encode.js'
15
16
  import { PublicKey, Transaction, TransactionInstruction } from '../vendor/index.js'
16
- import { ASSOCIATED_TOKEN_PROGRAM_ID } from './spl-token.js'
17
17
 
18
18
  const TRANSFER_INSTRUCTION_DISCRIMINATOR = 49
19
19
  const TOKEN_AUTH_RULES_ID = new PublicKey('auth9SigNpDKz4sJJ1DfCTuZrZNSAgh9sFD3rboVmgg')
@@ -127,7 +127,7 @@ export const prepareMetaplexTransferTx = ({
127
127
  isSigner: false,
128
128
  },
129
129
  {
130
- pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
130
+ pubkey: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
131
131
  isWritable: false,
132
132
  isSigner: false,
133
133
  },
@@ -12,11 +12,6 @@ import * as Layout from '../vendor/utils/layout.js'
12
12
 
13
13
  // Extracted from https://github.com/ExodusMovement/solana-spl-token/blob/master/src/index.js#L263
14
14
 
15
- export const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey(
16
- 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
17
- )
18
- export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
19
-
20
15
  /**
21
16
  * 64-bit value
22
17
  */
package/src/index.js CHANGED
@@ -13,6 +13,7 @@ export {
13
13
  SystemProgram,
14
14
  } from './vendor/index.js'
15
15
  export { default as Transaction } from './transaction.js'
16
+ export { createMemoInstruction } from './helpers/memo.js'
16
17
  export { U64, Token } from './helpers/spl-token.js'
17
18
  export { createGetKeyIdentifier, getSupportedPurposes } from './key-identifier.js'
18
19
  export { createAgentTokenInitTx } from './helpers/create-agent-token-init-tx.js'
@@ -2,7 +2,7 @@ import BN from 'bn.js'
2
2
  import bs58 from 'bs58'
3
3
  import assert from 'minimalistic-assert'
4
4
 
5
- import { MEMO_PROGRAM_ID, SEED, STAKE_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from './constants.js'
5
+ import { SEED, STAKE_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from './constants.js'
6
6
  import { createStakeAddress, findAssociatedTokenAddress } from './encode.js'
7
7
  import { createTransferCheckedWithFeeInstruction } from './helpers/spl-token-2022.js'
8
8
  import {
@@ -20,9 +20,21 @@ import {
20
20
  StakeProgram,
21
21
  SystemProgram,
22
22
  Transaction,
23
- TransactionInstruction,
24
23
  } from './vendor/index.js'
25
24
 
25
+ function normalizeReferencePublicKeys(reference) {
26
+ if (reference == null) return null
27
+
28
+ const entries = Array.isArray(reference) ? reference : [reference]
29
+ if (entries.length === 0) return null
30
+
31
+ return entries.map((key) => {
32
+ if (key instanceof PublicKey) return key
33
+ assert(typeof key === 'string', 'reference must be base58 string(s) or PublicKey(s)')
34
+ return new PublicKey(key)
35
+ })
36
+ }
37
+
26
38
  class Tx {
27
39
  constructor(
28
40
  {
@@ -38,7 +50,6 @@ class Tx {
38
50
  fromTokenAddresses, // sender token addresses
39
51
  instructions,
40
52
  feePayer,
41
- memo,
42
53
  reference,
43
54
  } = {},
44
55
  options = {}
@@ -87,17 +98,6 @@ class Tx {
87
98
  // SOL tx
88
99
  this.buildSOLtransaction(this.txObj)
89
100
  }
90
-
91
- // If a memo is provided, add it to the transaction before adding the transfer instruction
92
- if (memo) {
93
- this.transaction.add(
94
- new TransactionInstruction({
95
- programId: MEMO_PROGRAM_ID,
96
- keys: [],
97
- data: Buffer.from(memo, 'utf8'),
98
- })
99
- )
100
- }
101
101
  }
102
102
 
103
103
  buildSOLtransaction({ from, to, amount, recentBlockhash, feePayer, reference }) {
@@ -107,13 +107,9 @@ class Tx {
107
107
  lamports: new BN(amount),
108
108
  })
109
109
 
110
- // If reference accounts are provided, add them to the transfer instruction
111
- if (reference) {
112
- if (!Array.isArray(reference)) {
113
- reference = [reference]
114
- }
115
-
116
- for (const pubkey of reference) {
110
+ const refKeys = normalizeReferencePublicKeys(reference)
111
+ if (refKeys) {
112
+ for (const pubkey of refKeys) {
117
113
  txInstruction.keys.push({ pubkey, isWritable: false, isSigner: false })
118
114
  }
119
115
  }
@@ -231,13 +227,9 @@ class Tx {
231
227
  )
232
228
  }
233
229
 
234
- // If reference accounts are provided, add them to the transfer instruction
235
- if (reference) {
236
- if (!Array.isArray(reference)) {
237
- reference = [reference]
238
- }
239
-
240
- for (const pubkey of reference) {
230
+ const refKeys = normalizeReferencePublicKeys(reference)
231
+ if (refKeys) {
232
+ for (const pubkey of refKeys) {
241
233
  tokenTransferInstruction.keys.push({ pubkey, isWritable: false, isSigner: false })
242
234
  }
243
235
  }
@@ -36,6 +36,9 @@ export function createUnsignedTx({
36
36
  // Wallet Connect
37
37
  instructions,
38
38
  feePayer,
39
+ memo,
40
+ /** Base58 string(s) and/or PublicKey; normalized when building transactions. */
41
+ reference,
39
42
  }) {
40
43
  return {
41
44
  txData: {
@@ -72,6 +75,8 @@ export function createUnsignedTx({
72
75
  // Wallet Connect
73
76
  instructions,
74
77
  feePayer,
78
+ memo,
79
+ reference,
75
80
  },
76
81
  txMeta: {
77
82
  assetName: asset.name,
@@ -1,8 +1,9 @@
1
1
  import * as BufferLayout from '@exodus/buffer-layout'
2
2
  import bs58 from 'bs58'
3
3
 
4
- import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../helpers/spl-token.js'
5
- import { SystemInstruction, SystemProgram } from './../vendor/index.js'
4
+ import { TOKEN_PROGRAM_ID } from '../constants.js'
5
+ import { SystemInstruction } from './../vendor/index.js'
6
+ import { isATAProgram, isSystemProgram } from './instruction-utils.js'
6
7
  import * as TokenInstructions from './token-instructions.js'
7
8
 
8
9
  const INSTRUCTION_LAYOUT = BufferLayout.union(BufferLayout.u8('instruction'))
@@ -184,7 +185,7 @@ function decodeTokenInstruction(instruction) {
184
185
  return decodeTokenProgramInstruction(instruction)
185
186
  }
186
187
 
187
- if (programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
188
+ if (isATAProgram(programId)) {
188
189
  return decodeAssociatedTokenProgramInstruction(instruction)
189
190
  }
190
191
  } catch (err) {
@@ -216,7 +217,7 @@ export function decodeTransactionInstructions(transactionMessages) {
216
217
  return [...prevInstructions, ...instructions]
217
218
  }, [])
218
219
  return transactionInstructions.map((instruction) => {
219
- if (instruction.programId.equals(SystemProgram.programId)) {
220
+ if (isSystemProgram(instruction.programId)) {
220
221
  return decodeSystemInstruction(instruction)
221
222
  }
222
223
 
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  COMPUTE_BUDGET_PROGRAM_ID,
3
+ SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
3
4
  SYSTEM_PROGRAM_ID,
4
5
  TOKEN_2022_PROGRAM_ID,
5
6
  TOKEN_PROGRAM_ID,
@@ -62,15 +63,30 @@ export function hasValidShape(instruction, reqs) {
62
63
  )
63
64
  }
64
65
 
65
- export function isTokenProgramInstruction(instruction) {
66
- const programId = instruction?.programId
66
+ export function isTokenProgram(programId) {
67
+ if (!programId) return false
68
+ const id = programId.toString()
69
+ return id === TOKEN_PROGRAM_ID.toString() || id === TOKEN_2022_PROGRAM_ID.toString()
70
+ }
71
+
72
+ export function isATAProgram(programId) {
67
73
  if (!programId) return false
68
- return programId.equals(TOKEN_PROGRAM_ID) || programId.equals(TOKEN_2022_PROGRAM_ID)
74
+ return programId.toString() === SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID.toString()
75
+ }
76
+
77
+ export function isSystemProgram(programId) {
78
+ if (!programId) return false
79
+ return programId.toString() === SYSTEM_PROGRAM_ID.toString()
80
+ }
81
+
82
+ /** @deprecated Use `isTokenProgram(instruction.programId)` instead. */
83
+ export function isTokenProgramInstruction(instruction) {
84
+ return isTokenProgram(instruction?.programId)
69
85
  }
70
86
 
71
87
  export function isSystemTransferInstruction(instruction) {
72
- const programId = instruction?.programId
73
- if (!programId || !programId.equals(SYSTEM_PROGRAM_ID)) return false
88
+ if (!isSystemProgram(instruction?.programId)) return false
89
+
74
90
  const reqs = SYSTEM_INSTRUCTION_REQUIREMENTS[SYSTEM_INSTRUCTION_LAYOUTS.Transfer.index]
75
91
  if (!hasValidShape(instruction, reqs)) return false
76
92
  try {
@@ -81,14 +97,18 @@ export function isSystemTransferInstruction(instruction) {
81
97
  }
82
98
  }
83
99
 
84
- export function isComputeBudgetInstruction(instruction) {
85
- const programId = instruction?.programId
100
+ export function isComputeBudgetProgram(programId) {
86
101
  if (!programId) return false
87
- return programId.equals(COMPUTE_BUDGET_PROGRAM_ID)
102
+ return programId.toString() === COMPUTE_BUDGET_PROGRAM_ID.toString()
103
+ }
104
+
105
+ /** @deprecated Use `isComputeBudgetProgram(instruction.programId)` instead. */
106
+ export function isComputeBudgetInstruction(instruction) {
107
+ return isComputeBudgetProgram(instruction?.programId)
88
108
  }
89
109
 
90
110
  export function isSetAuthorityInstruction(instruction) {
91
- if (!isTokenProgramInstruction(instruction)) return false
111
+ if (!isTokenProgram(instruction?.programId)) return false
92
112
  const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.SetAuthority.index]
93
113
  if (!hasValidShape(instruction, reqs)) return false
94
114
  const buffer = toBuffer(instruction.data)
@@ -96,7 +116,7 @@ export function isSetAuthorityInstruction(instruction) {
96
116
  }
97
117
 
98
118
  export function isTransferInstruction(instruction) {
99
- if (!isTokenProgramInstruction(instruction)) return false
119
+ if (!isTokenProgram(instruction?.programId)) return false
100
120
  const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.Transfer.index]
101
121
  if (!hasValidShape(instruction, reqs)) return false
102
122
  const buffer = toBuffer(instruction.data)
@@ -104,7 +124,7 @@ export function isTransferInstruction(instruction) {
104
124
  }
105
125
 
106
126
  export function isTransferCheckedInstruction(instruction) {
107
- if (!isTokenProgramInstruction(instruction)) return false
127
+ if (!isTokenProgram(instruction?.programId)) return false
108
128
  const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.TransferChecked.index]
109
129
  if (!hasValidShape(instruction, reqs)) return false
110
130
  const buffer = toBuffer(instruction.data)
@@ -112,7 +132,7 @@ export function isTransferCheckedInstruction(instruction) {
112
132
  }
113
133
 
114
134
  export function isTransferCheckedWithFeeInstruction(instruction) {
115
- if (!isTokenProgramInstruction(instruction)) return false
135
+ if (!isTokenProgram(instruction?.programId)) return false
116
136
  const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index]
117
137
  if (!hasValidShape(instruction, reqs)) return false
118
138
  const buffer = toBuffer(instruction.data)
@@ -1,4 +1,3 @@
1
- import { SYSTEM_PROGRAM_ID } from '../constants.js'
2
1
  import {
3
2
  StakeInstruction,
4
3
  SYSTEM_INSTRUCTION_LAYOUTS,
@@ -8,6 +7,7 @@ import {
8
7
  import { toBuffer } from '../vendor/utils/to-buffer.js'
9
8
  import { deserializeTransaction } from './common.js'
10
9
  import { getAccountKeys, getNormalizedInstructions } from './instruction-normalizer.js'
10
+ import { isSystemProgram } from './instruction-utils.js'
11
11
 
12
12
  const InstructionKind = {
13
13
  TOKEN: 'token',
@@ -19,7 +19,7 @@ const InstructionKind = {
19
19
  function getStakedAmountFromCreateWithSeed(stakeAddress, instructions, accountKeys) {
20
20
  for (const instruction of instructions) {
21
21
  const programId = accountKeys[instruction.programIdIndex]
22
- if (!programId.equals(SYSTEM_PROGRAM_ID)) continue
22
+ if (!isSystemProgram(programId)) continue
23
23
 
24
24
  const buffer = toBuffer(instruction.data)
25
25
  if (
@@ -4,6 +4,7 @@ import { VersionedTransaction } from '@exodus/solana-web3.js'
4
4
  import BN from 'bn.js'
5
5
  import assert from 'minimalistic-assert'
6
6
 
7
+ import { createMemoInstruction } from '../helpers/memo.js'
7
8
  import { createMetaplexTransferTransaction } from '../helpers/metaplex-transfer.js'
8
9
  import Transaction from '../transaction.js'
9
10
  import { ComputeBudgetProgram, PublicKey } from '../vendor/index.js'
@@ -125,6 +126,11 @@ const createTx = ({ txData, method, from }, options) => {
125
126
  addComputeBudgetToTransaction({ transaction: tx, computeUnits: txData.computeUnits })
126
127
  }
127
128
 
129
+ if (txData.memo) {
130
+ const prependCount = (txData.computeUnits ? 1 : 0) + (txData.priorityFee ? 1 : 0)
131
+ tx.instructions.splice(prependCount, 0, createMemoInstruction(txData.memo))
132
+ }
133
+
128
134
  const publicKey = new PublicKey(from)
129
135
  if (!tx.feePayer) {
130
136
  tx.feePayer = publicKey
@@ -227,7 +233,6 @@ const createTokenTransaction = (
227
233
  recentBlockhash,
228
234
  to,
229
235
  tokenMintAddress,
230
- memo,
231
236
  reference,
232
237
  },
233
238
  options
@@ -245,7 +250,6 @@ const createTokenTransaction = (
245
250
  recentBlockhash,
246
251
  to,
247
252
  tokenMintAddress,
248
- memo,
249
253
  reference,
250
254
  },
251
255
  options
@@ -1,5 +1,6 @@
1
1
  import { Token, U64 } from '../helpers/spl-token.js'
2
2
  import { PublicKey } from '../vendor/index.js'
3
+ import { isSystemProgram } from './instruction-utils.js'
3
4
 
4
5
  export function computeBalance(futureAccountBalance, currentAccountBalance) {
5
6
  return new U64(futureAccountBalance).sub(new U64(currentAccountBalance))
@@ -59,8 +60,6 @@ export function getTransactionSimulationParams(transactionMessage) {
59
60
  }
60
61
  }
61
62
 
62
- const isSolAccount = (account) => account === '11111111111111111111111111111111'
63
-
64
63
  export function filterAccountsByOwner(futureAccountsState, accountAddresses, publicKey) {
65
64
  const solAccounts = []
66
65
  const tokenAccounts = []
@@ -75,7 +74,7 @@ export function filterAccountsByOwner(futureAccountsState, accountAddresses, pub
75
74
  const accountAddress = accountAddresses[index]
76
75
 
77
76
  // Check if it's SOL account (not token)
78
- if (isSolAccount(futureAccount.owner.toString())) {
77
+ if (isSystemProgram(futureAccount.owner)) {
79
78
  if (accountAddress.toString() === publicKey.toString()) {
80
79
  solAccounts.push({
81
80
  amount: futureAccount.lamports,
@@ -2,13 +2,13 @@ import lodash from 'lodash'
2
2
  import assert from 'minimalistic-assert'
3
3
 
4
4
  import {
5
+ SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
5
6
  SPL_TOKEN_AUTHORITY_TYPE,
6
7
  SPL_TOKEN_INSTRUCTION_TYPE,
7
- TOKEN_2022_PROGRAM_ID,
8
8
  TOKEN_PROGRAM_ID,
9
9
  } from '../constants.js'
10
- import { ASSOCIATED_TOKEN_PROGRAM_ID } from '../helpers/spl-token.js'
11
10
  import { SYSVAR_RENT_PUBKEY } from '../vendor/index.js'
11
+ import { isATAProgram, isTokenProgram } from './instruction-utils.js'
12
12
 
13
13
  export function verifyOnlyFeePayerChanged(beforeTx, afterTx) {
14
14
  assert(
@@ -72,7 +72,7 @@ export function verifyOnlyFeePayerChanged(beforeTx, afterTx) {
72
72
  return keyStr === SYSVAR_RENT_PUBKEY.toString()
73
73
  })
74
74
 
75
- if (containsRentSysvar && isATAProgram(programId)) {
75
+ if (containsRentSysvar && (isATAProgram(programId) || isTokenProgram(programId))) {
76
76
  const adjustedBeforeAccounts = [...beforeAccounts]
77
77
  adjustedBeforeAccounts[0] = afterTx.message.accountKeys[0]
78
78
  assert(
@@ -237,19 +237,6 @@ export function verifyOnlyFeePayerChanged(beforeTx, afterTx) {
237
237
  )
238
238
  }
239
239
 
240
- function isTokenProgram(programId) {
241
- const programIdStr = programId.toString()
242
- return (
243
- programIdStr === TOKEN_PROGRAM_ID.toString() ||
244
- programIdStr === TOKEN_2022_PROGRAM_ID.toString()
245
- )
246
- }
247
-
248
- function isATAProgram(programId) {
249
- const programIdStr = programId.toString()
250
- return isTokenProgram(programId) || programIdStr === ASSOCIATED_TOKEN_PROGRAM_ID.toString()
251
- }
252
-
253
240
  function getTokenAccountCreations(transaction) {
254
241
  const createdAccounts = []
255
242
 
@@ -258,7 +245,7 @@ function getTokenAccountCreations(transaction) {
258
245
 
259
246
  // Check for ATA creation (Associated Token Account Program)
260
247
  if (
261
- programId === ASSOCIATED_TOKEN_PROGRAM_ID.toString() && // For ATA creation, the new account is typically at index 1
248
+ programId === SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID.toString() && // For ATA creation, the new account is typically at index 1
262
249
  // Index 0 is payer, Index 1 is the ATA being created, Index 5 is the token program
263
250
  instruction.accounts?.length >= 2
264
251
  ) {