@exodus/solana-lib 3.21.0 → 3.22.0

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,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.22.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.21.1...@exodus/solana-lib@3.22.0) (2026-03-13)
7
+
8
+
9
+ ### Features
10
+
11
+
12
+ * feat: solana add instruction shape validation (#7575)
13
+
14
+
15
+
16
+ ## [3.21.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.21.0...@exodus/solana-lib@3.21.1) (2026-03-12)
17
+
18
+ **Note:** Version bump only for package @exodus/solana-lib
19
+
20
+
21
+
22
+
23
+
6
24
  ## [3.21.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.20.1...@exodus/solana-lib@3.21.0) (2026-03-10)
7
25
 
8
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-lib",
3
- "version": "3.21.0",
3
+ "version": "3.22.0",
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": "7da7b29b3177ad6155d5018c297e795cd4d3bda8"
51
+ "gitHead": "cf39bddd95d88ea2e99809810172368a5d4adc97"
52
52
  }
@@ -1,60 +1,122 @@
1
- import { COMPUTE_BUDGET_PROGRAM_ID } from '../constants.js'
2
1
  import {
3
- SystemInstruction,
2
+ COMPUTE_BUDGET_PROGRAM_ID,
3
+ SYSTEM_PROGRAM_ID,
4
+ TOKEN_2022_PROGRAM_ID,
5
+ TOKEN_PROGRAM_ID,
6
+ } from '../constants.js'
7
+ import {
8
+ SYSTEM_INSTRUCTION_LAYOUTS,
4
9
  TOKEN_INSTRUCTION_LAYOUTS,
5
- TokenInstruction,
6
10
  TRANSFER_FEE_SUB_INSTRUCTIONS,
7
11
  } from '../vendor/index.js'
8
12
  import { toBuffer } from '../vendor/utils/to-buffer.js'
9
13
 
10
- function getProgramId(instruction, accountKeys) {
11
- if (!instruction || instruction.programIdIndex === undefined) return null
12
- return accountKeys[instruction.programIdIndex]
14
+ /** Token instruction type index → { minBytes, minAccounts } for hasValidShape. Uses TOKEN_INSTRUCTION_LAYOUTS indices. */
15
+ export const TOKEN_INSTRUCTION_REQUIREMENTS = Object.freeze({
16
+ [TOKEN_INSTRUCTION_LAYOUTS.InitializeAccount.index]: { minBytes: 1, minAccounts: 4 },
17
+ [TOKEN_INSTRUCTION_LAYOUTS.InitializeAccount2.index]: { minBytes: 33, minAccounts: 3 },
18
+ [TOKEN_INSTRUCTION_LAYOUTS.InitializeAccount3.index]: { minBytes: 33, minAccounts: 2 },
19
+ [TOKEN_INSTRUCTION_LAYOUTS.Transfer.index]: { minBytes: 9, minAccounts: 3 },
20
+ [TOKEN_INSTRUCTION_LAYOUTS.TransferChecked.index]: { minBytes: 10, minAccounts: 4 },
21
+ [TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index]: { minBytes: 19, minAccounts: 4 },
22
+ [TOKEN_INSTRUCTION_LAYOUTS.CloseAccount.index]: { minBytes: 1, minAccounts: 3 },
23
+ [TOKEN_INSTRUCTION_LAYOUTS.Approve.index]: { minBytes: 9, minAccounts: 3 },
24
+ [TOKEN_INSTRUCTION_LAYOUTS.ApproveChecked.index]: { minBytes: 10, minAccounts: 4 },
25
+ [TOKEN_INSTRUCTION_LAYOUTS.Revoke.index]: { minBytes: 1, minAccounts: 2 },
26
+ [TOKEN_INSTRUCTION_LAYOUTS.SyncNative.index]: { minBytes: 1, minAccounts: 1 },
27
+ [TOKEN_INSTRUCTION_LAYOUTS.SetAuthority.index]: { minBytes: 35, minAccounts: 2 },
28
+ })
29
+
30
+ /** System program instruction type index → { minBytes, minAccounts } for hasValidShape. */
31
+ export const SYSTEM_INSTRUCTION_REQUIREMENTS = Object.freeze({
32
+ [SYSTEM_INSTRUCTION_LAYOUTS.Transfer.index]: { minBytes: 12, minAccounts: 2 },
33
+ })
34
+
35
+ /**
36
+ * Checks that an instruction has the required shape (data length and account count).
37
+ * Works with both legacy shape (keys/data) and normalized shape (accounts/data).
38
+ *
39
+ * @param {Object} instruction - Instruction object with `data` and either `keys` or `accounts`
40
+ * @param {Object} reqs - Requirements: `{ minBytes?: number, minAccounts?: number }`
41
+ * @returns {boolean}
42
+ */
43
+ export function hasValidShape(instruction, reqs) {
44
+ if (!reqs) return false
45
+ let data = null
46
+ if (instruction?.data != null) {
47
+ try {
48
+ data = toBuffer(instruction.data)
49
+ } catch {
50
+ return false
51
+ }
52
+ }
53
+
54
+ const minBytes = reqs.minBytes ?? 0
55
+ const minAccounts = reqs.minAccounts ?? 0
56
+ const accounts = instruction?.keys ?? instruction?.accounts
57
+ return (
58
+ data != null &&
59
+ data.length >= minBytes &&
60
+ Array.isArray(accounts) &&
61
+ accounts.length >= minAccounts
62
+ )
13
63
  }
14
64
 
15
- export function isTokenProgramInstruction(instruction, accountKeys) {
16
- return TokenInstruction.isProgramInstruction(instruction, accountKeys)
65
+ export function isTokenProgramInstruction(instruction) {
66
+ const programId = instruction?.programId
67
+ if (!programId) return false
68
+ return programId.equals(TOKEN_PROGRAM_ID) || programId.equals(TOKEN_2022_PROGRAM_ID)
17
69
  }
18
70
 
19
- export function isSystemTransferInstruction(instruction, accountKeys) {
20
- if (!SystemInstruction.isProgramInstruction(instruction, accountKeys)) return false
71
+ export function isSystemTransferInstruction(instruction) {
72
+ const programId = instruction?.programId
73
+ if (!programId || !programId.equals(SYSTEM_PROGRAM_ID)) return false
74
+ const reqs = SYSTEM_INSTRUCTION_REQUIREMENTS[SYSTEM_INSTRUCTION_LAYOUTS.Transfer.index]
75
+ if (!hasValidShape(instruction, reqs)) return false
21
76
  try {
22
- SystemInstruction.validateInstruction(instruction)
23
- return true
77
+ const buffer = toBuffer(instruction.data)
78
+ return buffer.readUInt32LE(0) === SYSTEM_INSTRUCTION_LAYOUTS.Transfer.index
24
79
  } catch {
25
80
  return false
26
81
  }
27
82
  }
28
83
 
29
- export function isComputeBudgetInstruction(instruction, accountKeys) {
30
- const programId = getProgramId(instruction, accountKeys)
84
+ export function isComputeBudgetInstruction(instruction) {
85
+ const programId = instruction?.programId
31
86
  if (!programId) return false
32
87
  return programId.equals(COMPUTE_BUDGET_PROGRAM_ID)
33
88
  }
34
89
 
35
- export function isSetAuthorityInstruction(instruction, accountKeys) {
36
- if (!isTokenProgramInstruction(instruction, accountKeys)) return false
90
+ export function isSetAuthorityInstruction(instruction) {
91
+ if (!isTokenProgramInstruction(instruction)) return false
92
+ const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.SetAuthority.index]
93
+ if (!hasValidShape(instruction, reqs)) return false
37
94
  const buffer = toBuffer(instruction.data)
38
- return buffer.length > 0 && buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.SetAuthority.index
95
+ return buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.SetAuthority.index
39
96
  }
40
97
 
41
- export function isTransferInstruction(instruction, accountKeys) {
42
- if (!isTokenProgramInstruction(instruction, accountKeys)) return false
98
+ export function isTransferInstruction(instruction) {
99
+ if (!isTokenProgramInstruction(instruction)) return false
100
+ const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.Transfer.index]
101
+ if (!hasValidShape(instruction, reqs)) return false
43
102
  const buffer = toBuffer(instruction.data)
44
- return buffer.length > 0 && buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.Transfer.index
103
+ return buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.Transfer.index
45
104
  }
46
105
 
47
- export function isTransferCheckedInstruction(instruction, accountKeys) {
48
- if (!isTokenProgramInstruction(instruction, accountKeys)) return false
106
+ export function isTransferCheckedInstruction(instruction) {
107
+ if (!isTokenProgramInstruction(instruction)) return false
108
+ const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.TransferChecked.index]
109
+ if (!hasValidShape(instruction, reqs)) return false
49
110
  const buffer = toBuffer(instruction.data)
50
- return buffer.length > 0 && buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.TransferChecked.index
111
+ return buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.TransferChecked.index
51
112
  }
52
113
 
53
- export function isTransferCheckedWithFeeInstruction(instruction, accountKeys) {
54
- if (!isTokenProgramInstruction(instruction, accountKeys)) return false
114
+ export function isTransferCheckedWithFeeInstruction(instruction) {
115
+ if (!isTokenProgramInstruction(instruction)) return false
116
+ const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index]
117
+ if (!hasValidShape(instruction, reqs)) return false
55
118
  const buffer = toBuffer(instruction.data)
56
119
  return (
57
- buffer.length >= 2 &&
58
120
  buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index &&
59
121
  buffer[1] === TRANSFER_FEE_SUB_INSTRUCTIONS.TransferCheckedWithFee
60
122
  )