@exodus/solana-lib 3.21.1 → 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 +10 -0
- package/package.json +2 -2
- package/src/tx/instruction-utils.js +70 -13
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.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
|
+
|
|
6
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)
|
|
7
17
|
|
|
8
18
|
**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.
|
|
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": "
|
|
51
|
+
"gitHead": "cf39bddd95d88ea2e99809810172368a5d4adc97"
|
|
52
52
|
}
|
|
@@ -4,9 +4,64 @@ import {
|
|
|
4
4
|
TOKEN_2022_PROGRAM_ID,
|
|
5
5
|
TOKEN_PROGRAM_ID,
|
|
6
6
|
} from '../constants.js'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
SYSTEM_INSTRUCTION_LAYOUTS,
|
|
9
|
+
TOKEN_INSTRUCTION_LAYOUTS,
|
|
10
|
+
TRANSFER_FEE_SUB_INSTRUCTIONS,
|
|
11
|
+
} from '../vendor/index.js'
|
|
8
12
|
import { toBuffer } from '../vendor/utils/to-buffer.js'
|
|
9
13
|
|
|
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
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
10
65
|
export function isTokenProgramInstruction(instruction) {
|
|
11
66
|
const programId = instruction?.programId
|
|
12
67
|
if (!programId) return false
|
|
@@ -16,12 +71,11 @@ export function isTokenProgramInstruction(instruction) {
|
|
|
16
71
|
export function isSystemTransferInstruction(instruction) {
|
|
17
72
|
const programId = instruction?.programId
|
|
18
73
|
if (!programId || !programId.equals(SYSTEM_PROGRAM_ID)) return false
|
|
19
|
-
|
|
20
|
-
if (!instruction
|
|
74
|
+
const reqs = SYSTEM_INSTRUCTION_REQUIREMENTS[SYSTEM_INSTRUCTION_LAYOUTS.Transfer.index]
|
|
75
|
+
if (!hasValidShape(instruction, reqs)) return false
|
|
21
76
|
try {
|
|
22
77
|
const buffer = toBuffer(instruction.data)
|
|
23
|
-
|
|
24
|
-
return buffer.readUInt32LE(0) === 2
|
|
78
|
+
return buffer.readUInt32LE(0) === SYSTEM_INSTRUCTION_LAYOUTS.Transfer.index
|
|
25
79
|
} catch {
|
|
26
80
|
return false
|
|
27
81
|
}
|
|
@@ -35,31 +89,34 @@ export function isComputeBudgetInstruction(instruction) {
|
|
|
35
89
|
|
|
36
90
|
export function isSetAuthorityInstruction(instruction) {
|
|
37
91
|
if (!isTokenProgramInstruction(instruction)) return false
|
|
38
|
-
|
|
92
|
+
const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.SetAuthority.index]
|
|
93
|
+
if (!hasValidShape(instruction, reqs)) return false
|
|
39
94
|
const buffer = toBuffer(instruction.data)
|
|
40
|
-
return buffer
|
|
95
|
+
return buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.SetAuthority.index
|
|
41
96
|
}
|
|
42
97
|
|
|
43
98
|
export function isTransferInstruction(instruction) {
|
|
44
99
|
if (!isTokenProgramInstruction(instruction)) return false
|
|
45
|
-
|
|
100
|
+
const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.Transfer.index]
|
|
101
|
+
if (!hasValidShape(instruction, reqs)) return false
|
|
46
102
|
const buffer = toBuffer(instruction.data)
|
|
47
|
-
return buffer
|
|
103
|
+
return buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.Transfer.index
|
|
48
104
|
}
|
|
49
105
|
|
|
50
106
|
export function isTransferCheckedInstruction(instruction) {
|
|
51
107
|
if (!isTokenProgramInstruction(instruction)) return false
|
|
52
|
-
|
|
108
|
+
const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.TransferChecked.index]
|
|
109
|
+
if (!hasValidShape(instruction, reqs)) return false
|
|
53
110
|
const buffer = toBuffer(instruction.data)
|
|
54
|
-
return buffer
|
|
111
|
+
return buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.TransferChecked.index
|
|
55
112
|
}
|
|
56
113
|
|
|
57
114
|
export function isTransferCheckedWithFeeInstruction(instruction) {
|
|
58
115
|
if (!isTokenProgramInstruction(instruction)) return false
|
|
59
|
-
|
|
116
|
+
const reqs = TOKEN_INSTRUCTION_REQUIREMENTS[TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index]
|
|
117
|
+
if (!hasValidShape(instruction, reqs)) return false
|
|
60
118
|
const buffer = toBuffer(instruction.data)
|
|
61
119
|
return (
|
|
62
|
-
buffer.length >= 2 &&
|
|
63
120
|
buffer[0] === TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index &&
|
|
64
121
|
buffer[1] === TRANSFER_FEE_SUB_INSTRUCTIONS.TransferCheckedWithFee
|
|
65
122
|
)
|