@exodus/solana-lib 1.3.6 → 1.3.8
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/package.json +6 -2
- package/src/index.js +1 -0
- package/src/tx/decode-tx-instructions.js +218 -0
- package/src/tx/index.js +1 -0
- package/src/tx/simulate-and-sign-tx.js +12 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-lib",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"description": "Exodus internal Solana low-level library",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"@exodus/assets": "^8.0.53",
|
|
19
19
|
"@exodus/buffer-layout": "^1.2.0-exodus1",
|
|
20
20
|
"@exodus/models": "^8.4.0",
|
|
21
|
+
"@exodus/solana-spl-token": "0.1.8-exodus.1",
|
|
22
|
+
"@exodus/web3-solana-utils": "^0.11.0",
|
|
23
|
+
"@project-serum/serum": "0.13.64",
|
|
24
|
+
"@solana/web3.js": "1.31.0",
|
|
21
25
|
"bn.js": "^4.11.0",
|
|
22
26
|
"borsh": "^0.7.0",
|
|
23
27
|
"bs58": "^4.0.1",
|
|
@@ -25,5 +29,5 @@
|
|
|
25
29
|
"lodash": "^4.17.11",
|
|
26
30
|
"tweetnacl": "^1.0.3"
|
|
27
31
|
},
|
|
28
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "a0fdfde1511f31373bc94c8cd12c8bee22757876"
|
|
29
33
|
}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
|
|
2
|
+
import { MARKETS, TokenInstructions } from '@project-serum/serum'
|
|
3
|
+
import {
|
|
4
|
+
Message,
|
|
5
|
+
SystemInstruction,
|
|
6
|
+
SystemProgram,
|
|
7
|
+
TransactionInstruction,
|
|
8
|
+
PublicKey,
|
|
9
|
+
} from '@solana/web3.js'
|
|
10
|
+
import bs58 from 'bs58'
|
|
11
|
+
|
|
12
|
+
type DecodedInstruction = {
|
|
13
|
+
type: string,
|
|
14
|
+
title: string,
|
|
15
|
+
data: {
|
|
16
|
+
programId: PublicKey,
|
|
17
|
+
rawData?: Buffer,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const INSTRUCTION_TITLE_BY_TYPE = {
|
|
22
|
+
approve: 'Approve',
|
|
23
|
+
cancelOrder: 'Cancel Order',
|
|
24
|
+
cancelOrderV2: 'Cancel Order',
|
|
25
|
+
closeAccount: 'Close Account',
|
|
26
|
+
createAssociatedTokenAccount: 'Create Token Account',
|
|
27
|
+
createSyncNativeInstruction: 'Create Sync Native Account',
|
|
28
|
+
initializeAccount: 'Initialize Account',
|
|
29
|
+
initializeMint: 'Initialize Mint',
|
|
30
|
+
matchOrders: 'Match Orders',
|
|
31
|
+
mintTo: 'Mint To',
|
|
32
|
+
newOrder: 'Place Order',
|
|
33
|
+
newOrderV3: 'Place Order',
|
|
34
|
+
settleFunds: 'Settle Funds',
|
|
35
|
+
signMessage: 'Sign Message',
|
|
36
|
+
systemAssign: 'Assign',
|
|
37
|
+
systemAllocate: 'Allocate',
|
|
38
|
+
systemCreate: 'Create Account',
|
|
39
|
+
systemCreateWithSeed: 'Create Account With Seed',
|
|
40
|
+
systemTransfer: 'Transfer SOL',
|
|
41
|
+
systemInitializeNonceAccount: 'Initialize Nonce Account',
|
|
42
|
+
systemAdvanceNonceAccount: 'Advance Nonce Account',
|
|
43
|
+
systemAuthorizeNonceAccount: 'Authorize Nonce Account',
|
|
44
|
+
systemWithdrawNonceAccount: 'Withdraw Nonce Account',
|
|
45
|
+
transfer: 'Transfer Token',
|
|
46
|
+
unknown: 'Unknown',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class InstructionKeys {
|
|
50
|
+
constructor(keys) {
|
|
51
|
+
this.keys = keys
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getAddress(index: number) {
|
|
55
|
+
return this.keys[index] ? this.keys[index].pubkey : undefined
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getTransactionInstructionsFromMessage(message: Message): TransactionInstruction[] {
|
|
60
|
+
const { accountKeys, instructions } = message
|
|
61
|
+
return instructions.map((instruction) => {
|
|
62
|
+
const { accounts, data, programIdIndex } = instruction
|
|
63
|
+
return {
|
|
64
|
+
programId: accountKeys[programIdIndex],
|
|
65
|
+
keys: accounts.map((account, index) => ({
|
|
66
|
+
pubkey: accountKeys[account],
|
|
67
|
+
isSigner: message.isAccountSigner(index),
|
|
68
|
+
isWritable: message.isAccountWritable(index),
|
|
69
|
+
})),
|
|
70
|
+
data: bs58.decode(data),
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function decodeSystemInstruction(instruction: TransactionInstruction): DecodedInstruction {
|
|
76
|
+
const instructionType = SystemInstruction.decodeInstructionType(instruction)
|
|
77
|
+
|
|
78
|
+
let data
|
|
79
|
+
if (instructionType === 'Create') {
|
|
80
|
+
data = SystemInstruction.decodeCreateAccount(instruction)
|
|
81
|
+
} else if (instructionType.includes('Nonce')) {
|
|
82
|
+
data = SystemInstruction[`decodeNonce${instructionType.split('Nonce')[0]}`](instruction)
|
|
83
|
+
} else {
|
|
84
|
+
data = SystemInstruction[`decode${instructionType}`](instruction)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const type = `system${instructionType}`
|
|
88
|
+
return {
|
|
89
|
+
type,
|
|
90
|
+
title: INSTRUCTION_TITLE_BY_TYPE[type],
|
|
91
|
+
data,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function decodeTokenInstructionData(data: Buffer) {
|
|
96
|
+
if (data.length === 1) {
|
|
97
|
+
switch (data[0]) {
|
|
98
|
+
case 1:
|
|
99
|
+
return {
|
|
100
|
+
initializeAccount: {},
|
|
101
|
+
}
|
|
102
|
+
case 9:
|
|
103
|
+
return {
|
|
104
|
+
closeAccount: {},
|
|
105
|
+
}
|
|
106
|
+
case 17:
|
|
107
|
+
return {
|
|
108
|
+
createSyncNativeInstruction: {},
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return TokenInstructions.decodeTokenInstructionData(data)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function decodeTokenProgramInstruction(instruction: TransactionInstruction): DecodedInstruction {
|
|
117
|
+
const decodedInstructionData = decodeTokenInstructionData(instruction.data)
|
|
118
|
+
|
|
119
|
+
if (!decodedInstructionData || Object.keys(decodedInstructionData).length > 1) {
|
|
120
|
+
throw new Error('Unexpected Token Program instruction')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const keys = new InstructionKeys(instruction.keys)
|
|
124
|
+
|
|
125
|
+
const type = Object.keys(decodedInstructionData)[0]
|
|
126
|
+
|
|
127
|
+
let data = decodedInstructionData[type]
|
|
128
|
+
|
|
129
|
+
if (type === 'initializeAccount') {
|
|
130
|
+
data = {
|
|
131
|
+
accountPubkey: keys.getAddress(TokenInstructions.INITIALIZE_ACCOUNT_ACCOUNT_INDEX),
|
|
132
|
+
mintPubKey: keys.getAddress(TokenInstructions.INITIALIZE_ACCOUNT_MINT_INDEX),
|
|
133
|
+
ownerPubKey: keys.getAddress(TokenInstructions.INITIALIZE_ACCOUNT_OWNER_INDEX),
|
|
134
|
+
}
|
|
135
|
+
} else if (type === 'closeAccount' || type === 'transfer') {
|
|
136
|
+
data = {
|
|
137
|
+
...data,
|
|
138
|
+
sourcePubkey: keys.getAddress(TokenInstructions.TRANSFER_SOURCE_INDEX),
|
|
139
|
+
destinationPubKey: keys.getAddress(TokenInstructions.TRANSFER_DESTINATION_INDEX),
|
|
140
|
+
ownerPubKey: keys.getAddress(TokenInstructions.TRANSFER_OWNER_INDEX),
|
|
141
|
+
}
|
|
142
|
+
} else if (type === 'createSyncNativeInstruction') {
|
|
143
|
+
data = {
|
|
144
|
+
pubkey: keys.getAddress(0),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
type,
|
|
150
|
+
title: INSTRUCTION_TITLE_BY_TYPE[type],
|
|
151
|
+
data,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function decodeAssociatedTokenProgramInstruction(
|
|
156
|
+
instruction: TransactionInstruction
|
|
157
|
+
): DecodedInstruction {
|
|
158
|
+
const { programId } = instruction
|
|
159
|
+
const type = 'createAssociatedTokenAccount'
|
|
160
|
+
return {
|
|
161
|
+
type,
|
|
162
|
+
title: INSTRUCTION_TITLE_BY_TYPE[type],
|
|
163
|
+
data: { programId },
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function decodeMarketProgramInstruction(instruction: TransactionInstruction): DecodedInstruction {
|
|
168
|
+
throw new Error('Not yet implemented')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function decodeTokenInstruction(instruction: TransactionInstruction): DecodedInstruction {
|
|
172
|
+
const { programId } = instruction
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
if (programId.equals(TOKEN_PROGRAM_ID)) {
|
|
176
|
+
return decodeTokenProgramInstruction(instruction)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
|
|
180
|
+
return decodeAssociatedTokenProgramInstruction(instruction)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (MARKETS.some((market) => programId.equals(market.programId))) {
|
|
184
|
+
return decodeMarketProgramInstruction(instruction)
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.warn(err)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const type = 'unknown'
|
|
191
|
+
return {
|
|
192
|
+
type,
|
|
193
|
+
title: INSTRUCTION_TITLE_BY_TYPE[type],
|
|
194
|
+
data: {
|
|
195
|
+
programId,
|
|
196
|
+
rawData: instruction.data,
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function decodeTransactionInstructions(
|
|
202
|
+
transactionMessages: Message[]
|
|
203
|
+
): DecodedInstruction[] {
|
|
204
|
+
const transactionInstructions = transactionMessages.reduce(
|
|
205
|
+
(prevInstructions: TransactionInstruction[], message: Message) => [
|
|
206
|
+
...prevInstructions,
|
|
207
|
+
...getTransactionInstructionsFromMessage(message),
|
|
208
|
+
],
|
|
209
|
+
[]
|
|
210
|
+
)
|
|
211
|
+
return transactionInstructions.map((instruction) => {
|
|
212
|
+
if (instruction.programId.equals(SystemProgram.programId)) {
|
|
213
|
+
return decodeSystemInstruction(instruction)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return decodeTokenInstruction(instruction)
|
|
217
|
+
})
|
|
218
|
+
}
|
package/src/tx/index.js
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { Token, U64 } from '../helpers/spl-token'
|
|
2
2
|
import { PublicKey } from '../vendor/'
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Maximum over-the-wire size of a Transaction
|
|
6
|
-
*
|
|
7
|
-
* 1280 is IPv6 minimum MTU
|
|
8
|
-
* 40 bytes is the size of the IPv6 header
|
|
9
|
-
* 8 bytes is the size of the fragment header
|
|
10
|
-
*/
|
|
11
|
-
const PACKET_DATA_SIZE = 1280 - 40 - 8
|
|
12
|
-
const SIGNATURE_LENGTH = 64
|
|
13
|
-
|
|
14
4
|
export function computeBalance(futureAccountBalance: string, currentAccountBalance: string) {
|
|
15
5
|
return new U64(futureAccountBalance).sub(new U64(currentAccountBalance))
|
|
16
6
|
}
|
|
17
7
|
|
|
18
8
|
export function getTransactionSimulationParams(transactionMessage) {
|
|
9
|
+
let indexToProgramIds = transactionMessage.indexToProgramIds
|
|
19
10
|
const config = {
|
|
20
11
|
encoding: 'base64',
|
|
21
12
|
commitment: 'confirmed',
|
|
22
13
|
}
|
|
23
14
|
|
|
15
|
+
if (!indexToProgramIds) {
|
|
16
|
+
indexToProgramIds = new Map()
|
|
17
|
+
transactionMessage.instructions.forEach((instruction) =>
|
|
18
|
+
indexToProgramIds.set(
|
|
19
|
+
instruction.programIdIndex,
|
|
20
|
+
transactionMessage.accountKeys[instruction.programIdIndex]
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
24
25
|
const accountAddresses = transactionMessage.accountKeys
|
|
25
|
-
.filter((_, index) => !
|
|
26
|
+
.filter((_, index) => !indexToProgramIds.has(index))
|
|
26
27
|
.map((account) => account.toString())
|
|
27
28
|
|
|
28
29
|
config['accounts'] = {
|
|
@@ -86,19 +87,3 @@ export function filterAccountsByOwner(futureAccountsState, accountAddresses, pub
|
|
|
86
87
|
tokenAccounts,
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
|
-
|
|
90
|
-
export function buildRawTransaction(signData, requiredSignatureCount) {
|
|
91
|
-
const signatureCount = [requiredSignatureCount]
|
|
92
|
-
const signaturesLength = signatureCount.length + requiredSignatureCount * SIGNATURE_LENGTH
|
|
93
|
-
const rawTransactionLength = signData.length + signaturesLength
|
|
94
|
-
|
|
95
|
-
if (rawTransactionLength > PACKET_DATA_SIZE) {
|
|
96
|
-
throw Error(`Transaction too large: ${rawTransactionLength} > ${PACKET_DATA_SIZE}`)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const rawTransaction = Buffer.alloc(rawTransactionLength)
|
|
100
|
-
Buffer.from(signatureCount).copy(rawTransaction)
|
|
101
|
-
signData.copy(rawTransaction, signaturesLength)
|
|
102
|
-
|
|
103
|
-
return rawTransaction
|
|
104
|
-
}
|