@exodus/solana-lib 3.18.0 → 3.18.2
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 +18 -0
- package/package.json +2 -2
- package/src/constants.js +9 -3
- package/src/tx/common.js +2 -0
- package/src/tx/instruction-normalizer.js +28 -0
- package/src/tx/parse-tx-buffer.js +163 -210
- package/src/tx/prepare-for-signing.js +5 -0
- package/src/vendor/index.js +7 -1
- package/src/vendor/stake-program.js +108 -0
- package/src/vendor/system-program.js +66 -18
- package/src/vendor/token-program.js +117 -42
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.18.2](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.18.1...@exodus/solana-lib@3.18.2) (2025-12-16)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @exodus/solana-lib
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [3.18.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.18.0...@exodus/solana-lib@3.18.1) (2025-12-15)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
* fix: check SOL transactionBuffer type (#7111)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## [3.18.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.15.3...@exodus/solana-lib@3.18.0) (2025-11-26)
|
|
7
25
|
|
|
8
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-lib",
|
|
3
|
-
"version": "3.18.
|
|
3
|
+
"version": "3.18.2",
|
|
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",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"type": "git",
|
|
48
48
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "e0c3dec90d1b6a5a1fac0ab54f3f7c551100ddb7"
|
|
51
51
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
PublicKey,
|
|
3
|
+
StakeProgram,
|
|
4
|
+
SystemProgram,
|
|
5
|
+
Token2022Program,
|
|
6
|
+
TokenProgram,
|
|
7
|
+
} from './vendor/index.js'
|
|
2
8
|
|
|
3
9
|
export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
|
|
4
10
|
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
|
|
@@ -7,13 +13,13 @@ export const SYSTEM_PROGRAM_ID = SystemProgram.programId
|
|
|
7
13
|
|
|
8
14
|
export const STAKE_PROGRAM_ID = StakeProgram.programId
|
|
9
15
|
|
|
10
|
-
export const TOKEN_PROGRAM_ID =
|
|
16
|
+
export const TOKEN_PROGRAM_ID = TokenProgram.programId
|
|
11
17
|
|
|
12
18
|
export const COMPUTE_BUDGET_PROGRAM_ID = new PublicKey(
|
|
13
19
|
'ComputeBudget111111111111111111111111111111'
|
|
14
20
|
)
|
|
15
21
|
|
|
16
|
-
export const TOKEN_2022_PROGRAM_ID =
|
|
22
|
+
export const TOKEN_2022_PROGRAM_ID = Token2022Program.programId
|
|
17
23
|
|
|
18
24
|
export const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr')
|
|
19
25
|
|
package/src/tx/common.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { VersionedTransaction } from '@exodus/solana-web3.js'
|
|
2
2
|
import base58 from 'bs58'
|
|
3
|
+
import assert from 'minimalistic-assert'
|
|
3
4
|
|
|
4
5
|
export function isVersionedTransaction(tx) {
|
|
5
6
|
return Number.isInteger(tx.version)
|
|
@@ -14,6 +15,7 @@ export function transactionToBase58(tx) {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function deserializeTransaction(tx) {
|
|
18
|
+
assert(tx instanceof Uint8Array, 'tx must be a Buffer or Uint8Array')
|
|
17
19
|
return VersionedTransaction.deserialize(tx)
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import bs58 from 'bs58'
|
|
2
|
+
|
|
3
|
+
export function getAccountKeys(message) {
|
|
4
|
+
// versioned: staticAccountKeys, legacy: accountKeys
|
|
5
|
+
return message.staticAccountKeys || message.accountKeys
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function normalizeInstruction(instruction) {
|
|
9
|
+
// legacy: bs58 string, versioned: Uint8Array
|
|
10
|
+
let normalizedData = instruction.data
|
|
11
|
+
if (typeof instruction.data === 'string') {
|
|
12
|
+
normalizedData = bs58.decode(instruction.data)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
programIdIndex: instruction.programIdIndex,
|
|
17
|
+
// versioned: accountKeyIndexes, legacy: accounts
|
|
18
|
+
accounts: instruction.accountKeyIndexes || instruction.accounts,
|
|
19
|
+
data: normalizedData,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getNormalizedInstructions(message) {
|
|
24
|
+
// versioned: compiledInstructions, legacy: instructions
|
|
25
|
+
const instructions = message.compiledInstructions || message.instructions || []
|
|
26
|
+
|
|
27
|
+
return instructions.map((instruction) => normalizeInstruction(instruction))
|
|
28
|
+
}
|
|
@@ -1,264 +1,217 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { SYSTEM_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../constants.js'
|
|
1
|
+
import { SYSTEM_PROGRAM_ID } from '../constants.js'
|
|
4
2
|
import {
|
|
3
|
+
StakeInstruction,
|
|
5
4
|
SYSTEM_INSTRUCTION_LAYOUTS,
|
|
6
5
|
SystemInstruction,
|
|
7
|
-
TOKEN_INSTRUCTION_LAYOUTS,
|
|
8
6
|
TokenInstruction,
|
|
9
7
|
} from '../vendor/index.js'
|
|
8
|
+
import { toBuffer } from '../vendor/utils/to-buffer.js'
|
|
10
9
|
import { deserializeTransaction } from './common.js'
|
|
10
|
+
import { getAccountKeys, getNormalizedInstructions } from './instruction-normalizer.js'
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (!programId.equals(TOKEN_PROGRAM_ID) && !programId.equals(TOKEN_2022_PROGRAM_ID)) {
|
|
19
|
-
return false
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Must have at least 3 accounts (from, to, authority, and potentially more for delegate transfers)
|
|
23
|
-
if (!Array.isArray(instruction.accounts) || instruction.accounts.length < 3) {
|
|
24
|
-
return false
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Check if the instruction data is transfer (0x03), transferChecked (0x0c/12),
|
|
28
|
-
// or TransferFeeExtension (0x1a/26) with TransferCheckedWithFee sub-instruction
|
|
29
|
-
// Note: Must handle both formats because this is called from isTokenTransfer()
|
|
30
|
-
// which uses non-normalized instructions
|
|
31
|
-
try {
|
|
32
|
-
let data = instruction.data
|
|
33
|
-
if (typeof data === 'string') {
|
|
34
|
-
data = bs58.decode(data)
|
|
35
|
-
}
|
|
12
|
+
const InstructionKind = {
|
|
13
|
+
TOKEN: 'token',
|
|
14
|
+
SYSTEM_TRANSFER: 'systemTransfer',
|
|
15
|
+
STAKE: 'stake',
|
|
16
|
+
}
|
|
36
17
|
|
|
37
|
-
|
|
18
|
+
// Get staked amount from CreateWithSeed instruction that created the stake account
|
|
19
|
+
function getStakedAmountFromCreateWithSeed(stakeAddress, instructions, accountKeys) {
|
|
20
|
+
for (const instruction of instructions) {
|
|
21
|
+
const programId = accountKeys[instruction.programIdIndex]
|
|
22
|
+
if (!programId.equals(SYSTEM_PROGRAM_ID)) continue
|
|
38
23
|
|
|
39
|
-
|
|
24
|
+
const buffer = toBuffer(instruction.data)
|
|
40
25
|
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
instructionType !== TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index
|
|
26
|
+
buffer.length < 4 ||
|
|
27
|
+
buffer.readUInt32LE(0) !== SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed.index
|
|
44
28
|
) {
|
|
45
|
-
|
|
29
|
+
continue
|
|
46
30
|
}
|
|
47
31
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const subInstruction = data[1]
|
|
52
|
-
if (subInstruction !== 1) return false // Only support TransferCheckedWithFee
|
|
53
|
-
}
|
|
54
|
-
} catch {
|
|
55
|
-
return false
|
|
56
|
-
}
|
|
32
|
+
// Check if this instruction created our stake account
|
|
33
|
+
const newAccountPubkey = accountKeys[instruction.accounts[1]].toBase58()
|
|
34
|
+
if (newAccountPubkey !== stakeAddress) continue
|
|
57
35
|
|
|
58
|
-
|
|
36
|
+
const { lamports } = SystemInstruction.decodeCreateWithSeedData(instruction.data)
|
|
37
|
+
return lamports
|
|
38
|
+
}
|
|
59
39
|
}
|
|
60
40
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
41
|
+
// TODO: Unify with parseTransaction in solana-api and use there as well?
|
|
42
|
+
// TODO: add support for swap instructions
|
|
43
|
+
export async function parseTxBuffer(buffer, api) {
|
|
44
|
+
const transaction = deserializeTransaction(buffer)
|
|
45
|
+
const { message } = transaction
|
|
64
46
|
|
|
65
|
-
|
|
47
|
+
const accountKeys = getAccountKeys(message)
|
|
48
|
+
const instructions = getNormalizedInstructions(message)
|
|
66
49
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
50
|
+
if (!Array.isArray(accountKeys) || !Array.isArray(instructions)) {
|
|
51
|
+
throw new TypeError('Invalid transaction structure')
|
|
52
|
+
}
|
|
70
53
|
|
|
71
|
-
|
|
72
|
-
function isSolTransferInstruction(instruction, accountKeys) {
|
|
73
|
-
if (!instruction || instruction.programIdIndex === undefined) return false
|
|
54
|
+
const decodedEntries = []
|
|
74
55
|
|
|
75
|
-
const
|
|
56
|
+
for (const [index, instruction] of instructions.entries()) {
|
|
57
|
+
try {
|
|
58
|
+
const decoded = decodeInstructionEntry({ instruction, accountKeys })
|
|
59
|
+
if (decoded) {
|
|
60
|
+
decodedEntries.push({
|
|
61
|
+
...decoded,
|
|
62
|
+
instruction,
|
|
63
|
+
index,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Skip unsupported instructions
|
|
68
|
+
}
|
|
69
|
+
}
|
|
76
70
|
|
|
77
|
-
|
|
71
|
+
const parsedInstructions = []
|
|
72
|
+
for (const entry of decodedEntries) {
|
|
73
|
+
const parsed = await parseDecodedInstruction(entry, {
|
|
74
|
+
accountKeys,
|
|
75
|
+
api,
|
|
76
|
+
instructions,
|
|
77
|
+
})
|
|
78
|
+
if (parsed) parsedInstructions.push(parsed)
|
|
79
|
+
}
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return false
|
|
81
|
+
if (parsedInstructions.length === 0) {
|
|
82
|
+
throw new Error('No supported instructions found in transaction')
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
return parsedInstructions
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function getMintAddressFromTokenAccount(tokenAccountAddress, api) {
|
|
87
89
|
try {
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
90
|
+
const accountInfo = await api.getAccountInfo(tokenAccountAddress)
|
|
91
|
+
if (accountInfo?.data?.parsed?.info?.mint) {
|
|
92
|
+
return accountInfo.data.parsed.info.mint
|
|
91
93
|
}
|
|
92
|
-
|
|
93
|
-
const buffer = Buffer.from(data)
|
|
94
|
-
if (buffer.readUInt32LE(0) !== SYSTEM_INSTRUCTION_LAYOUTS.Transfer.index) return false
|
|
95
94
|
} catch {
|
|
96
|
-
|
|
95
|
+
// Ignore errors when fetching mint address
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
return
|
|
98
|
+
return null
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
const {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
if (!Array.isArray(accountKeys) || !Array.isArray(instructions)) return false
|
|
107
|
-
|
|
108
|
-
return instructions.some((instruction) => isSolTransferInstruction(instruction, accountKeys))
|
|
109
|
-
}
|
|
101
|
+
function decodeInstructionEntry({ instruction, accountKeys }) {
|
|
102
|
+
const { accounts } = instruction
|
|
103
|
+
const buffer = toBuffer(instruction.data)
|
|
110
104
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
if (TokenInstruction.isProgramInstruction(instruction, accountKeys)) {
|
|
106
|
+
TokenInstruction.validateInstruction(instruction)
|
|
107
|
+
return {
|
|
108
|
+
kind: InstructionKind.TOKEN,
|
|
109
|
+
decoded: TokenInstruction.decodeInstructionData(buffer, accounts, accountKeys),
|
|
110
|
+
}
|
|
116
111
|
}
|
|
117
112
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
let normalizedData = instruction.data
|
|
125
|
-
if (typeof instruction.data === 'string') {
|
|
126
|
-
normalizedData = bs58.decode(instruction.data)
|
|
113
|
+
if (SystemInstruction.isProgramInstruction(instruction, accountKeys)) {
|
|
114
|
+
SystemInstruction.validateInstruction(instruction)
|
|
115
|
+
return {
|
|
116
|
+
kind: InstructionKind.SYSTEM_TRANSFER,
|
|
117
|
+
decoded: SystemInstruction.decodeInstructionData(buffer, accounts, accountKeys),
|
|
118
|
+
}
|
|
127
119
|
}
|
|
128
120
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
if (StakeInstruction.isProgramInstruction(instruction, accountKeys)) {
|
|
122
|
+
StakeInstruction.validateInstruction(instruction)
|
|
123
|
+
return {
|
|
124
|
+
kind: InstructionKind.STAKE,
|
|
125
|
+
decoded: StakeInstruction.decodeInstructionData(buffer, accounts, accountKeys),
|
|
126
|
+
}
|
|
133
127
|
}
|
|
128
|
+
|
|
129
|
+
return null
|
|
134
130
|
}
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return []
|
|
132
|
+
async function parseDecodedInstruction(entry, context) {
|
|
133
|
+
switch (entry.kind) {
|
|
134
|
+
case InstructionKind.TOKEN:
|
|
135
|
+
return parseTokenInstruction(entry, context)
|
|
136
|
+
case InstructionKind.SYSTEM_TRANSFER:
|
|
137
|
+
return parseSystemInstruction(entry, context)
|
|
138
|
+
case InstructionKind.STAKE:
|
|
139
|
+
return parseStakeInstruction(entry, context)
|
|
140
|
+
default:
|
|
141
|
+
return null
|
|
147
142
|
}
|
|
148
|
-
|
|
149
|
-
return instructions.map((inst) => normalizeInstruction(inst))
|
|
150
143
|
}
|
|
151
144
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
async function parseTokenInstruction({ decoded, index, instruction }, { accountKeys, api }) {
|
|
146
|
+
const programId = accountKeys[instruction.programIdIndex]
|
|
147
|
+
const { fromTokenAccount, toTokenAccount, mintAddress } = decoded
|
|
148
|
+
|
|
149
|
+
let fromOwner = fromTokenAccount
|
|
150
|
+
let toOwner = toTokenAccount
|
|
151
|
+
let resolvedMint = mintAddress
|
|
152
|
+
|
|
153
|
+
if (api) {
|
|
154
|
+
const ownerPromises = [
|
|
155
|
+
api.getTokenAddressOwner(fromTokenAccount),
|
|
156
|
+
api.getTokenAddressOwner(toTokenAccount),
|
|
157
|
+
]
|
|
158
|
+
if (!resolvedMint) {
|
|
159
|
+
ownerPromises.push(getMintAddressFromTokenAccount(fromTokenAccount, api))
|
|
160
|
+
}
|
|
157
161
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
const [fromOwnerRaw, toOwnerRaw, fetchedMint] = await Promise.all(ownerPromises)
|
|
163
|
+
fromOwner = fromOwnerRaw || fromTokenAccount
|
|
164
|
+
toOwner = toOwnerRaw || toTokenAccount
|
|
165
|
+
if (!resolvedMint) resolvedMint = fetchedMint
|
|
166
|
+
}
|
|
161
167
|
|
|
162
|
-
|
|
163
|
-
|
|
168
|
+
const parsedInstruction = {
|
|
169
|
+
method: decoded.method,
|
|
170
|
+
from: fromOwner,
|
|
171
|
+
to: toOwner,
|
|
172
|
+
amount: decoded.amount,
|
|
173
|
+
programId: programId.toBase58(),
|
|
174
|
+
instructionIndex: index,
|
|
175
|
+
fromTokenAccount,
|
|
176
|
+
toTokenAccount,
|
|
177
|
+
mintAddress: resolvedMint,
|
|
164
178
|
}
|
|
165
179
|
|
|
166
|
-
|
|
180
|
+
if (decoded.decimals !== undefined) parsedInstruction.decimals = decoded.decimals
|
|
181
|
+
if (decoded.fee !== undefined) parsedInstruction.fee = decoded.fee
|
|
167
182
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (isTokenTransferInstruction(instruction, accountKeys)) {
|
|
171
|
-
const decoded = TokenInstruction.decodeInstructionData(instruction.data)
|
|
172
|
-
const programId = accountKeys[instruction.programIdIndex]
|
|
173
|
-
|
|
174
|
-
// Transfer (3): [source, destination, authority]
|
|
175
|
-
// TransferChecked (12): [source, mint, destination, authority, ...]
|
|
176
|
-
// TransferCheckedWithFee (26,1): [source, mint, destination, authority, ...]
|
|
177
|
-
let fromTokenAccount, toTokenAccount, mintAddress
|
|
178
|
-
|
|
179
|
-
if (decoded.method === 'transferChecked' || decoded.method === 'transferCheckedWithFee') {
|
|
180
|
-
// TransferChecked and TransferCheckedWithFee: mint is at index 1, destination at index 2
|
|
181
|
-
fromTokenAccount = accountKeys[instruction.accounts[0]].toBase58()
|
|
182
|
-
mintAddress = accountKeys[instruction.accounts[1]].toBase58() // Mint directly from accounts
|
|
183
|
-
toTokenAccount = accountKeys[instruction.accounts[2]].toBase58()
|
|
184
|
-
} else {
|
|
185
|
-
// Transfer: destination is at index 1
|
|
186
|
-
fromTokenAccount = accountKeys[instruction.accounts[0]].toBase58()
|
|
187
|
-
toTokenAccount = accountKeys[instruction.accounts[1]].toBase58()
|
|
188
|
-
mintAddress = null
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Get token account owners in parallel (requires API calls)
|
|
192
|
-
let fromOwner, toOwner
|
|
193
|
-
if (api) {
|
|
194
|
-
;[fromOwner, toOwner, mintAddress] = await Promise.all([
|
|
195
|
-
api.getTokenAddressOwner(fromTokenAccount),
|
|
196
|
-
api.getTokenAddressOwner(toTokenAccount),
|
|
197
|
-
mintAddress
|
|
198
|
-
? Promise.resolve(mintAddress)
|
|
199
|
-
: getMintAddressFromTokenAccount(fromTokenAccount, api),
|
|
200
|
-
])
|
|
201
|
-
// Fallback to token account address if owner lookup fails
|
|
202
|
-
fromOwner = fromOwner || fromTokenAccount
|
|
203
|
-
toOwner = toOwner || toTokenAccount
|
|
204
|
-
} else {
|
|
205
|
-
fromOwner = fromTokenAccount
|
|
206
|
-
toOwner = toTokenAccount
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const parsedInstruction = {
|
|
210
|
-
method: decoded.method,
|
|
211
|
-
from: fromOwner,
|
|
212
|
-
to: toOwner,
|
|
213
|
-
amount: decoded.amount,
|
|
214
|
-
programId: programId.toBase58(),
|
|
215
|
-
instructionIndex: index,
|
|
216
|
-
fromTokenAccount,
|
|
217
|
-
toTokenAccount,
|
|
218
|
-
mintAddress,
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Include decimals if it's a transferChecked instruction
|
|
222
|
-
if (decoded.decimals !== undefined) {
|
|
223
|
-
parsedInstruction.decimals = decoded.decimals
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
parsedInstructions.push(parsedInstruction)
|
|
227
|
-
} else if (isSolTransferInstruction(instruction, accountKeys)) {
|
|
228
|
-
const { amount, method } = SystemInstruction.decodeInstructionData(instruction.data)
|
|
229
|
-
const programId = accountKeys[instruction.programIdIndex]
|
|
230
|
-
|
|
231
|
-
parsedInstructions.push({
|
|
232
|
-
method,
|
|
233
|
-
from: accountKeys[instruction.accounts[0]].toBase58(),
|
|
234
|
-
to: accountKeys[instruction.accounts[1]].toBase58(),
|
|
235
|
-
amount,
|
|
236
|
-
programId: programId.toBase58(),
|
|
237
|
-
instructionIndex: index,
|
|
238
|
-
})
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
} catch (error) {
|
|
242
|
-
console.log('instruction parsing error', error)
|
|
243
|
-
throw error
|
|
244
|
-
}
|
|
183
|
+
return parsedInstruction
|
|
184
|
+
}
|
|
245
185
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
186
|
+
function parseSystemInstruction({ decoded, index, instruction }, { accountKeys }) {
|
|
187
|
+
const programId = accountKeys[instruction.programIdIndex]
|
|
249
188
|
|
|
250
|
-
return
|
|
189
|
+
return {
|
|
190
|
+
method: decoded.method,
|
|
191
|
+
from: decoded.from,
|
|
192
|
+
to: decoded.to,
|
|
193
|
+
amount: decoded.amount,
|
|
194
|
+
programId: programId.toBase58(),
|
|
195
|
+
instructionIndex: index,
|
|
196
|
+
}
|
|
251
197
|
}
|
|
252
198
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
199
|
+
function parseStakeInstruction({ decoded, index, instruction }, { accountKeys, instructions }) {
|
|
200
|
+
const programId = accountKeys[instruction.programIdIndex]
|
|
201
|
+
const { stakeAccount: stakeAddress, ...rest } = decoded
|
|
202
|
+
|
|
203
|
+
const parsedInstruction = {
|
|
204
|
+
method: decoded.method,
|
|
205
|
+
stakeAddress,
|
|
206
|
+
programId: programId.toBase58(),
|
|
207
|
+
instructionIndex: index,
|
|
208
|
+
...rest,
|
|
261
209
|
}
|
|
262
210
|
|
|
263
|
-
|
|
211
|
+
if (decoded.method === 'delegate' && parsedInstruction.amount === undefined) {
|
|
212
|
+
const amount = getStakedAmountFromCreateWithSeed(stakeAddress, instructions, accountKeys)
|
|
213
|
+
if (amount !== undefined) parsedInstruction.amount = amount
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return parsedInstruction
|
|
264
217
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isNumberUnit } from '@exodus/currency'
|
|
2
2
|
import { VersionedTransaction } from '@exodus/solana-web3.js'
|
|
3
3
|
import BN from 'bn.js'
|
|
4
|
+
import assert from 'minimalistic-assert'
|
|
4
5
|
|
|
5
6
|
import { createMetaplexTransferTransaction } from '../helpers/metaplex-transfer.js'
|
|
6
7
|
import Transaction from '../transaction.js'
|
|
@@ -42,6 +43,10 @@ export function prepareForSigning(unsignedTx, { checkBalances = true } = {}) {
|
|
|
42
43
|
|
|
43
44
|
// Recreate a Web3.js Transaction instance if the buffer provided.
|
|
44
45
|
if (transactionBuffer) {
|
|
46
|
+
assert(
|
|
47
|
+
unsignedTx.txData.transactionBuffer instanceof Uint8Array,
|
|
48
|
+
'transactionBuffer must be a Buffer or Uint8Array'
|
|
49
|
+
)
|
|
45
50
|
return deserializeTransactionBytes(transactionBuffer)
|
|
46
51
|
}
|
|
47
52
|
|
package/src/vendor/index.js
CHANGED
|
@@ -9,4 +9,10 @@ export * from './stake-program.js'
|
|
|
9
9
|
export * from './sysvar.js'
|
|
10
10
|
export * from './transaction.js'
|
|
11
11
|
export * from './constants.js'
|
|
12
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
Token2022Program,
|
|
14
|
+
TOKEN_INSTRUCTION_LAYOUTS,
|
|
15
|
+
TokenInstruction,
|
|
16
|
+
TokenProgram,
|
|
17
|
+
TRANSFER_FEE_SUB_INSTRUCTIONS,
|
|
18
|
+
} from './token-program.js'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as BufferLayout from '@exodus/buffer-layout'
|
|
2
|
+
import BN from 'bn.js'
|
|
2
3
|
|
|
3
4
|
import { decodeData, encodeData } from './instruction.js'
|
|
4
5
|
import { PublicKey } from './publickey.js'
|
|
@@ -177,6 +178,52 @@ export const STAKE_INSTRUCTION_LAYOUTS = Object.freeze({
|
|
|
177
178
|
* Stake Instruction class
|
|
178
179
|
*/
|
|
179
180
|
export const StakeInstruction = {
|
|
181
|
+
/**
|
|
182
|
+
* Check if an instruction belongs to the Stake program (program ID check only)
|
|
183
|
+
* @param {Object} instruction - Normalized instruction object
|
|
184
|
+
* @param {Array} accountKeys - Account public keys array
|
|
185
|
+
* @returns {boolean} True if this is a stake program instruction
|
|
186
|
+
*/
|
|
187
|
+
isProgramInstruction(instruction, accountKeys) {
|
|
188
|
+
if (!instruction || instruction.programIdIndex === undefined) return false
|
|
189
|
+
const programId = accountKeys[instruction.programIdIndex]
|
|
190
|
+
return programId.equals(StakeProgram.programId)
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validate instruction data for decoding (length, type, accounts)
|
|
195
|
+
* Only supports fund movement instructions: Delegate, Deactivate, Withdraw, Split
|
|
196
|
+
* @param {Object} instruction - Normalized instruction object
|
|
197
|
+
* @throws {Error} If instruction is invalid for decoding
|
|
198
|
+
*/
|
|
199
|
+
validateInstruction(instruction) {
|
|
200
|
+
if (!Array.isArray(instruction.accounts) || instruction.accounts.length === 0) {
|
|
201
|
+
throw new Error('Stake instruction requires at least 1 account')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const buffer = Buffer.from(instruction.data)
|
|
205
|
+
if (buffer.length < 4) {
|
|
206
|
+
throw new Error('Stake instruction data too short')
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const instructionType = buffer.readUInt32LE(0)
|
|
210
|
+
|
|
211
|
+
switch (instructionType) {
|
|
212
|
+
case STAKE_INSTRUCTION_LAYOUTS.Delegate.index:
|
|
213
|
+
case STAKE_INSTRUCTION_LAYOUTS.Deactivate.index:
|
|
214
|
+
return
|
|
215
|
+
case STAKE_INSTRUCTION_LAYOUTS.Split.index:
|
|
216
|
+
case STAKE_INSTRUCTION_LAYOUTS.Withdraw.index:
|
|
217
|
+
if (buffer.length < 12) {
|
|
218
|
+
throw new Error('Split/Withdraw instruction data too short')
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return
|
|
222
|
+
default:
|
|
223
|
+
throw new Error(`Unsupported stake instruction type: ${instructionType}`)
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
|
|
180
227
|
/**
|
|
181
228
|
* Decode a stake instruction and retrieve the instruction type.
|
|
182
229
|
*/
|
|
@@ -286,6 +333,67 @@ export const StakeInstruction = {
|
|
|
286
333
|
)
|
|
287
334
|
}
|
|
288
335
|
},
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Decode raw staking instruction data.
|
|
339
|
+
* Only supports fund movement instructions: Delegate, Deactivate, Withdraw, Split
|
|
340
|
+
* IMPORTANT: Call isProgramInstruction() and validateInstruction() first.
|
|
341
|
+
* @param {Buffer} buffer - Instruction data buffer
|
|
342
|
+
* @param {Array} accounts - Account indices array
|
|
343
|
+
* @param {Array} accountKeys - Account public keys array
|
|
344
|
+
* @returns {Object} Decoded staking data with method and relevant fields
|
|
345
|
+
*/
|
|
346
|
+
decodeInstructionData(buffer, accounts, accountKeys) {
|
|
347
|
+
const getAccountAddress = (index) => {
|
|
348
|
+
const accountIndex = accounts[index]
|
|
349
|
+
if (accountIndex === undefined) return null
|
|
350
|
+
const key = accountKeys[accountIndex]
|
|
351
|
+
return key ? key.toBase58() : null
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const instructionType = buffer.readUInt32LE(0)
|
|
355
|
+
|
|
356
|
+
switch (instructionType) {
|
|
357
|
+
case STAKE_INSTRUCTION_LAYOUTS.Delegate.index:
|
|
358
|
+
// Accounts: [stakeAccount, voteAccount, clock, stakeHistory, stakeConfig, stakeAuthority]
|
|
359
|
+
return {
|
|
360
|
+
method: 'delegate',
|
|
361
|
+
stakeAccount: getAccountAddress(0),
|
|
362
|
+
validator: getAccountAddress(1),
|
|
363
|
+
stakeAuthority: getAccountAddress(5),
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
case STAKE_INSTRUCTION_LAYOUTS.Deactivate.index:
|
|
367
|
+
// Accounts: [stakeAccount, clock, stakeAuthority]
|
|
368
|
+
return {
|
|
369
|
+
method: 'deactivate',
|
|
370
|
+
stakeAccount: getAccountAddress(0),
|
|
371
|
+
stakeAuthority: getAccountAddress(2),
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
case STAKE_INSTRUCTION_LAYOUTS.Withdraw.index:
|
|
375
|
+
// Accounts: [stakeAccount, to, clock, stakeHistory, withdrawAuthority]
|
|
376
|
+
return {
|
|
377
|
+
method: 'withdraw',
|
|
378
|
+
stakeAccount: getAccountAddress(0),
|
|
379
|
+
amount: new BN(buffer.slice(4, 12), 'le'),
|
|
380
|
+
to: getAccountAddress(1),
|
|
381
|
+
withdrawAuthority: getAccountAddress(4),
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
case STAKE_INSTRUCTION_LAYOUTS.Split.index:
|
|
385
|
+
// Accounts: [stakeAccount, splitStakeAccount, stakeAuthority]
|
|
386
|
+
return {
|
|
387
|
+
method: 'split',
|
|
388
|
+
stakeAccount: getAccountAddress(0),
|
|
389
|
+
splitStakeAccount: getAccountAddress(1),
|
|
390
|
+
amount: new BN(buffer.slice(4, 12), 'le'),
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
default:
|
|
394
|
+
throw new Error(`Unsupported stake instruction type: ${instructionType}`)
|
|
395
|
+
}
|
|
396
|
+
},
|
|
289
397
|
}
|
|
290
398
|
|
|
291
399
|
/**
|
|
@@ -7,6 +7,7 @@ import { PublicKey } from './publickey.js'
|
|
|
7
7
|
import { SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY } from './sysvar.js'
|
|
8
8
|
import { Transaction, TransactionInstruction } from './transaction.js'
|
|
9
9
|
import * as Layout from './utils/layout.js'
|
|
10
|
+
import { toBuffer } from './utils/to-buffer.js'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Create account system transaction params
|
|
@@ -199,6 +200,40 @@ export const SYSTEM_INSTRUCTION_LAYOUTS = Object.freeze({
|
|
|
199
200
|
* System Instruction class
|
|
200
201
|
*/
|
|
201
202
|
export const SystemInstruction = {
|
|
203
|
+
/**
|
|
204
|
+
* Check if an instruction belongs to the System program (program ID check only)
|
|
205
|
+
* @param {Object} instruction - Normalized instruction object
|
|
206
|
+
* @param {Array} accountKeys - Account public keys array
|
|
207
|
+
* @returns {boolean} True if this is a system program instruction
|
|
208
|
+
*/
|
|
209
|
+
isProgramInstruction(instruction, accountKeys) {
|
|
210
|
+
if (!instruction || instruction.programIdIndex === undefined) return false
|
|
211
|
+
const programId = accountKeys[instruction.programIdIndex]
|
|
212
|
+
return programId.equals(SystemProgram.programId)
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Validate instruction data for transfer decoding (length, type, accounts)
|
|
217
|
+
* @param {Object} instruction - Normalized instruction object
|
|
218
|
+
* @throws {Error} If instruction is invalid for decoding
|
|
219
|
+
*/
|
|
220
|
+
validateInstruction(instruction) {
|
|
221
|
+
if (!Array.isArray(instruction.accounts) || instruction.accounts.length !== 2) {
|
|
222
|
+
throw new Error('System transfer instruction requires exactly 2 accounts')
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const buffer = toBuffer(instruction.data)
|
|
226
|
+
// Transfer layout: u32 instruction + u64 lamports = 12 bytes
|
|
227
|
+
if (buffer.length < 12) {
|
|
228
|
+
throw new Error('System transfer instruction data too short')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const instructionType = buffer.readUInt32LE(0)
|
|
232
|
+
if (instructionType !== SYSTEM_INSTRUCTION_LAYOUTS.Transfer.index) {
|
|
233
|
+
throw new Error(`Unsupported system instruction type: ${instructionType}`)
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
|
|
202
237
|
/**
|
|
203
238
|
* Decode a system instruction and retrieve the instruction type.
|
|
204
239
|
*/
|
|
@@ -260,27 +295,40 @@ export const SystemInstruction = {
|
|
|
260
295
|
},
|
|
261
296
|
|
|
262
297
|
/**
|
|
263
|
-
* Decode raw instruction data
|
|
298
|
+
* Decode raw instruction data.
|
|
299
|
+
* IMPORTANT: Call isProgramInstruction() and validateInstruction() first.
|
|
300
|
+
* @param {Buffer} buffer - Instruction data buffer
|
|
301
|
+
* @param {Array} accounts - Account indices array
|
|
302
|
+
* @param {Array} accountKeys - Account public keys array
|
|
303
|
+
* @returns {Object} Decoded instruction data with amount, method, and accounts
|
|
304
|
+
*/
|
|
305
|
+
decodeInstructionData(buffer, accounts, accountKeys) {
|
|
306
|
+
const { lamports } = decodeData(SYSTEM_INSTRUCTION_LAYOUTS.Transfer, buffer)
|
|
307
|
+
return {
|
|
308
|
+
method: 'systemTransfer',
|
|
309
|
+
amount: new BN(lamports),
|
|
310
|
+
from: accountKeys[accounts[0]].toBase58(),
|
|
311
|
+
to: accountKeys[accounts[1]].toBase58(),
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Decode raw CreateWithSeed instruction data
|
|
264
317
|
* @param {Buffer} data - Raw instruction data buffer
|
|
265
|
-
* @returns {Object} Decoded instruction data with
|
|
318
|
+
* @returns {Object} Decoded instruction data with lamports, seed, space, programId
|
|
266
319
|
*/
|
|
267
|
-
|
|
320
|
+
decodeCreateWithSeedData(data) {
|
|
268
321
|
const buffer = Buffer.from(data)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const instructionType = buffer.readUInt32LE(0)
|
|
280
|
-
throw new Error(`Unsupported instruction type for SystemProgram: ${instructionType}`)
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
throw err
|
|
322
|
+
const { base, seed, lamports, space, programId } = decodeData(
|
|
323
|
+
SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed,
|
|
324
|
+
buffer
|
|
325
|
+
)
|
|
326
|
+
return {
|
|
327
|
+
basePubkey: new PublicKey(base),
|
|
328
|
+
seed,
|
|
329
|
+
lamports: new BN(lamports),
|
|
330
|
+
space,
|
|
331
|
+
programId: new PublicKey(programId),
|
|
284
332
|
}
|
|
285
333
|
},
|
|
286
334
|
|
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import BN from 'bn.js'
|
|
2
2
|
|
|
3
|
+
import { PublicKey } from './publickey.js'
|
|
4
|
+
import { toBuffer } from './utils/to-buffer.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Token Program
|
|
8
|
+
*/
|
|
9
|
+
export const TokenProgram = {
|
|
10
|
+
/**
|
|
11
|
+
* Public key that identifies the Token program
|
|
12
|
+
*/
|
|
13
|
+
get programId() {
|
|
14
|
+
return new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Token-2022 (Token Extensions) Program
|
|
20
|
+
*/
|
|
21
|
+
export const Token2022Program = {
|
|
22
|
+
/**
|
|
23
|
+
* Public key that identifies the Token-2022 program
|
|
24
|
+
*/
|
|
25
|
+
get programId() {
|
|
26
|
+
return new PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb')
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
3
30
|
/**
|
|
4
31
|
* Token Program instruction layouts
|
|
5
32
|
* Token-2022 (Token Extensions) is backward compatible with these instruction types
|
|
@@ -119,62 +146,110 @@ export const TRANSFER_FEE_SUB_INSTRUCTIONS = Object.freeze({
|
|
|
119
146
|
*/
|
|
120
147
|
export const TokenInstruction = {
|
|
121
148
|
/**
|
|
122
|
-
*
|
|
123
|
-
* @param {
|
|
124
|
-
* @
|
|
149
|
+
* Check if an instruction belongs to the Token program (program ID check only)
|
|
150
|
+
* @param {Object} instruction - Normalized instruction object
|
|
151
|
+
* @param {Array} accountKeys - Account public keys array
|
|
152
|
+
* @returns {boolean} True if this is a token program instruction
|
|
125
153
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
154
|
+
isProgramInstruction(instruction, accountKeys) {
|
|
155
|
+
if (!instruction || instruction.programIdIndex === undefined) return false
|
|
156
|
+
const programId = accountKeys[instruction.programIdIndex]
|
|
157
|
+
return programId.equals(TokenProgram.programId) || programId.equals(Token2022Program.programId)
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validate instruction data for transfer decoding (length, type, accounts)
|
|
162
|
+
* @param {Object} instruction - Normalized instruction object
|
|
163
|
+
* @throws {Error} If instruction is invalid for decoding
|
|
164
|
+
*/
|
|
165
|
+
validateInstruction(instruction) {
|
|
166
|
+
if (!Array.isArray(instruction.accounts) || instruction.accounts.length < 3) {
|
|
167
|
+
throw new Error('Token instruction requires at least 3 accounts')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const data = toBuffer(instruction.data)
|
|
171
|
+
if (data.length < 9) {
|
|
172
|
+
throw new Error('Token instruction data too short')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const instructionType = data[0]
|
|
129
176
|
|
|
130
|
-
if (
|
|
177
|
+
if (instructionType === TOKEN_INSTRUCTION_LAYOUTS.Transfer.index) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (instructionType === TOKEN_INSTRUCTION_LAYOUTS.TransferChecked.index) {
|
|
182
|
+
if (data.length < 10) {
|
|
183
|
+
throw new Error('TransferChecked instruction data too short')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (instructionType === TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index) {
|
|
190
|
+
if (data.length < 19) {
|
|
191
|
+
throw new Error('TransferCheckedWithFee instruction data too short')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (data[1] !== TRANSFER_FEE_SUB_INSTRUCTIONS.TransferCheckedWithFee) {
|
|
195
|
+
throw new Error(`Unsupported TransferFeeExtension sub-instruction: ${data[1]}`)
|
|
196
|
+
}
|
|
131
197
|
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
throw new Error(`Unsupported token instruction type: ${instructionType}`)
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Decode raw SPL Token instruction data.
|
|
206
|
+
* IMPORTANT: Call isProgramInstruction() and validateInstruction() first.
|
|
207
|
+
* @param {Buffer} buffer - Instruction data buffer
|
|
208
|
+
* @param {Array} accounts - Account indices array
|
|
209
|
+
* @param {Array} accountKeys - Account public keys array
|
|
210
|
+
* @returns {Object} Decoded instruction data with amount, method, accounts, and optional decimals/fee
|
|
211
|
+
*/
|
|
212
|
+
decodeInstructionData(buffer, accounts, accountKeys) {
|
|
132
213
|
const instructionType = buffer[0]
|
|
214
|
+
const getAddress = (index) => accountKeys[accounts[index]].toBase58()
|
|
133
215
|
|
|
134
216
|
// Handle TransferFeeExtension (Token-2022 instruction 26)
|
|
135
217
|
if (instructionType === TOKEN_INSTRUCTION_LAYOUTS.TransferFeeExtension.index) {
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const amountBytes = buffer.slice(2, 10)
|
|
148
|
-
const amountBN = new BN(amountBytes, 'le')
|
|
149
|
-
const decimals = buffer[10]
|
|
150
|
-
const feeBytes = buffer.slice(11, 19)
|
|
151
|
-
const feeBN = new BN(feeBytes, 'le')
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
amount: amountBN,
|
|
155
|
-
method: 'transferCheckedWithFee',
|
|
156
|
-
decimals,
|
|
157
|
-
fee: feeBN,
|
|
158
|
-
}
|
|
218
|
+
// Format: [26, 1, amount(8), decimals(1), fee(8)]
|
|
219
|
+
return {
|
|
220
|
+
method: 'transferCheckedWithFee',
|
|
221
|
+
amount: new BN(buffer.slice(2, 10), 'le'),
|
|
222
|
+
decimals: buffer[10],
|
|
223
|
+
fee: new BN(buffer.slice(11, 19), 'le'),
|
|
224
|
+
// Accounts: [source, mint, destination, authority]
|
|
225
|
+
fromTokenAccount: getAddress(0),
|
|
226
|
+
mintAddress: getAddress(1),
|
|
227
|
+
toTokenAccount: getAddress(2),
|
|
159
228
|
}
|
|
160
|
-
|
|
161
|
-
// Other TransferFeeExtension sub-instructions not supported yet
|
|
162
|
-
throw new Error(`Unsupported TransferFeeExtension sub-instruction: ${subInstructionType}`)
|
|
163
229
|
}
|
|
164
230
|
|
|
165
|
-
const
|
|
166
|
-
const amountBN = new BN(amountBytes, 'le')
|
|
231
|
+
const amount = new BN(buffer.slice(1, 9), 'le')
|
|
167
232
|
|
|
168
233
|
if (instructionType === TOKEN_INSTRUCTION_LAYOUTS.Transfer.index) {
|
|
169
|
-
|
|
234
|
+
// Accounts: [source, destination, authority]
|
|
235
|
+
return {
|
|
236
|
+
method: 'transfer',
|
|
237
|
+
amount,
|
|
238
|
+
fromTokenAccount: getAddress(0),
|
|
239
|
+
mintAddress: null,
|
|
240
|
+
toTokenAccount: getAddress(1),
|
|
241
|
+
}
|
|
170
242
|
}
|
|
171
243
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
244
|
+
// TransferChecked
|
|
245
|
+
// Accounts: [source, mint, destination, authority]
|
|
246
|
+
return {
|
|
247
|
+
method: 'transferChecked',
|
|
248
|
+
amount,
|
|
249
|
+
decimals: buffer[9],
|
|
250
|
+
fromTokenAccount: getAddress(0),
|
|
251
|
+
mintAddress: getAddress(1),
|
|
252
|
+
toTokenAccount: getAddress(2),
|
|
176
253
|
}
|
|
177
|
-
|
|
178
|
-
throw new Error(`Unsupported instruction type: ${instructionType}`)
|
|
179
254
|
},
|
|
180
255
|
}
|