@exodus/solana-lib 1.3.15 → 1.4.1
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 +2 -2
- package/src/constants.js +2 -0
- package/src/helpers/tokenTransfer.js +15 -1
- package/src/index.js +1 -0
- package/src/transaction.js +61 -5
- package/src/tx/decode-tx-instructions.js +31 -6
- package/src/tx/sign-unsigned-tx.js +44 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-lib",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Exodus internal Solana low-level library",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
"lodash": "^4.17.11",
|
|
30
30
|
"tweetnacl": "^1.0.3"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "cafda9efb741487bae7db4c3187c8b69d3553151"
|
|
33
33
|
}
|
package/src/constants.js
CHANGED
|
@@ -9,6 +9,8 @@ export const STAKE_PROGRAM_ID = StakeProgram.programId
|
|
|
9
9
|
|
|
10
10
|
export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
|
|
11
11
|
|
|
12
|
+
export const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr')
|
|
13
|
+
|
|
12
14
|
export const MAGIC_EDEN_ESCROW_PROGRAM_ID = new PublicKey(
|
|
13
15
|
'MEisE1HzehtrDpAAT8PnLHjpSSkRYakotTuJRPjTpo8'
|
|
14
16
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Token } from '
|
|
1
|
+
import { Token } from '@exodus/solana-spl-token'
|
|
2
2
|
import { PublicKey, TransactionInstruction, SystemProgram, SYSVAR_RENT_PUBKEY } from '../vendor'
|
|
3
3
|
import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../constants'
|
|
4
4
|
import { findAssociatedTokenAddress } from '../encode'
|
|
@@ -70,3 +70,17 @@ export const createTokenTransferInstruction = (owner, fromTokenAddress, to, amou
|
|
|
70
70
|
|
|
71
71
|
return transferIx
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
export const createCloseAccountInstruction = ({
|
|
75
|
+
programId = TOKEN_PROGRAM_ID,
|
|
76
|
+
tokenPublicKey,
|
|
77
|
+
walletPublicKey,
|
|
78
|
+
}) => {
|
|
79
|
+
return Token.createCloseAccountInstruction(
|
|
80
|
+
programId,
|
|
81
|
+
tokenPublicKey,
|
|
82
|
+
walletPublicKey,
|
|
83
|
+
walletPublicKey,
|
|
84
|
+
[]
|
|
85
|
+
)
|
|
86
|
+
}
|
package/src/index.js
CHANGED
package/src/transaction.js
CHANGED
|
@@ -9,6 +9,7 @@ import { findAssociatedTokenAddress, createStakeAddress } from './encode'
|
|
|
9
9
|
import {
|
|
10
10
|
createAssociatedTokenAccount,
|
|
11
11
|
createTokenTransferInstruction,
|
|
12
|
+
createCloseAccountInstruction,
|
|
12
13
|
} from './helpers/tokenTransfer'
|
|
13
14
|
import {
|
|
14
15
|
PublicKey,
|
|
@@ -19,9 +20,10 @@ import {
|
|
|
19
20
|
StakeInstruction,
|
|
20
21
|
Authorized,
|
|
21
22
|
Lockup,
|
|
23
|
+
TransactionInstruction,
|
|
22
24
|
} from './vendor'
|
|
23
25
|
import { MagicEdenEscrowProgram } from './magiceden/escrow-program'
|
|
24
|
-
import { SEED, STAKE_PROGRAM_ID } from './constants'
|
|
26
|
+
import { MEMO_PROGRAM_ID, SEED, STAKE_PROGRAM_ID } from './constants'
|
|
25
27
|
|
|
26
28
|
class Tx {
|
|
27
29
|
constructor({
|
|
@@ -38,6 +40,8 @@ class Tx {
|
|
|
38
40
|
fromTokenAddresses, // sender token addresses
|
|
39
41
|
instructions,
|
|
40
42
|
feePayer,
|
|
43
|
+
memo,
|
|
44
|
+
reference,
|
|
41
45
|
} = {}) {
|
|
42
46
|
if (!instructions) {
|
|
43
47
|
assert(from, 'from is required')
|
|
@@ -69,6 +73,7 @@ class Tx {
|
|
|
69
73
|
fromTokenAddresses,
|
|
70
74
|
instructions,
|
|
71
75
|
feePayer,
|
|
76
|
+
reference,
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
if (tokenMintAddress) {
|
|
@@ -80,18 +85,41 @@ class Tx {
|
|
|
80
85
|
// SOL tx
|
|
81
86
|
this.buildSOLtransaction(this.txObj)
|
|
82
87
|
}
|
|
88
|
+
|
|
89
|
+
// If a memo is provided, add it to the transaction before adding the transfer instruction
|
|
90
|
+
if (memo) {
|
|
91
|
+
this.transaction.add(
|
|
92
|
+
new TransactionInstruction({
|
|
93
|
+
programId: MEMO_PROGRAM_ID,
|
|
94
|
+
keys: [],
|
|
95
|
+
data: Buffer.from(memo, 'utf8'),
|
|
96
|
+
})
|
|
97
|
+
)
|
|
98
|
+
}
|
|
83
99
|
}
|
|
84
100
|
|
|
85
|
-
buildSOLtransaction({ from, to, amount, recentBlockhash }) {
|
|
101
|
+
buildSOLtransaction({ from, to, amount, recentBlockhash, feePayer, reference }) {
|
|
86
102
|
const txInstruction = SystemProgram.transfer({
|
|
87
103
|
fromPubkey: new PublicKey(from),
|
|
88
104
|
toPubkey: new PublicKey(to),
|
|
89
105
|
lamports: amount,
|
|
90
106
|
})
|
|
91
107
|
|
|
108
|
+
// If reference accounts are provided, add them to the transfer instruction
|
|
109
|
+
if (reference) {
|
|
110
|
+
if (!Array.isArray(reference)) {
|
|
111
|
+
reference = [reference]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const pubkey of reference) {
|
|
115
|
+
txInstruction.keys.push({ pubkey, isWritable: false, isSigner: false })
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
92
119
|
this.transaction = new Transaction({
|
|
93
120
|
instructions: [txInstruction],
|
|
94
121
|
recentBlockhash,
|
|
122
|
+
feePayer: feePayer ? new PublicKey(feePayer) : undefined,
|
|
95
123
|
})
|
|
96
124
|
}
|
|
97
125
|
|
|
@@ -119,9 +147,12 @@ class Tx {
|
|
|
119
147
|
destinationAddressType,
|
|
120
148
|
isAssociatedTokenAccountActive,
|
|
121
149
|
fromTokenAddresses,
|
|
150
|
+
feePayer,
|
|
151
|
+
reference,
|
|
122
152
|
}) {
|
|
123
153
|
this.transaction = new Transaction({
|
|
124
154
|
recentBlockhash,
|
|
155
|
+
feePayer: feePayer ? new PublicKey(feePayer) : undefined,
|
|
125
156
|
})
|
|
126
157
|
// const isUnknown = destinationAddressType === null
|
|
127
158
|
// if (isUnknown) throw new Error('Destination SOL balance cannot be zero (address not active)') // cannot initialize without knowing the owner
|
|
@@ -147,10 +178,26 @@ class Tx {
|
|
|
147
178
|
}
|
|
148
179
|
|
|
149
180
|
const dest = isSOLaddress ? findAssociatedTokenAddress(to, tokenMintAddress) : to
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
181
|
+
const tokenTransferInstruction = createTokenTransferInstruction(
|
|
182
|
+
from,
|
|
183
|
+
tokenAccountAddress,
|
|
184
|
+
dest,
|
|
185
|
+
amountToSend
|
|
153
186
|
)
|
|
187
|
+
|
|
188
|
+
// If reference accounts are provided, add them to the transfer instruction
|
|
189
|
+
if (reference) {
|
|
190
|
+
if (!Array.isArray(reference)) {
|
|
191
|
+
reference = [reference]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const pubkey of reference) {
|
|
195
|
+
tokenTransferInstruction.keys.push({ pubkey, isWritable: false, isSigner: false })
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// add transfer token instruction
|
|
200
|
+
this.transaction.add(tokenTransferInstruction)
|
|
154
201
|
}
|
|
155
202
|
|
|
156
203
|
assert(amountLeft === 0, `Not enough balance to send ${amount} ${tokenMintAddress}`)
|
|
@@ -397,6 +444,15 @@ class Tx {
|
|
|
397
444
|
}
|
|
398
445
|
return bs58.encode(transaction.signature)
|
|
399
446
|
}
|
|
447
|
+
|
|
448
|
+
static createCloseAccount({ programId, tokenPublicKey, walletPublicKey, recentBlockhash }) {
|
|
449
|
+
const tx = new Transaction()
|
|
450
|
+
tx.add(createCloseAccountInstruction({ programId, tokenPublicKey, walletPublicKey }))
|
|
451
|
+
tx.feePayer = walletPublicKey
|
|
452
|
+
tx.recentBlockhash = recentBlockhash
|
|
453
|
+
|
|
454
|
+
return tx
|
|
455
|
+
}
|
|
400
456
|
}
|
|
401
457
|
|
|
402
458
|
export default Tx
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
|
|
2
2
|
import { MARKETS, TokenInstructions } from '@project-serum/serum'
|
|
3
|
+
import * as BufferLayout from '@exodus/buffer-layout'
|
|
4
|
+
|
|
3
5
|
import {
|
|
4
6
|
Message,
|
|
5
7
|
SystemInstruction,
|
|
@@ -18,6 +20,13 @@ type DecodedInstruction = {
|
|
|
18
20
|
},
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
const INSTRUCTION_LAYOUT = BufferLayout.union(BufferLayout.u8('instruction'))
|
|
24
|
+
INSTRUCTION_LAYOUT.addVariant(
|
|
25
|
+
12,
|
|
26
|
+
BufferLayout.struct([BufferLayout.nu64('amount'), BufferLayout.u8('decimals')]),
|
|
27
|
+
'transferChecked'
|
|
28
|
+
)
|
|
29
|
+
|
|
21
30
|
export const INSTRUCTION_TITLE_BY_TYPE = {
|
|
22
31
|
approve: 'Approve',
|
|
23
32
|
cancelOrder: 'Cancel Order',
|
|
@@ -43,6 +52,7 @@ export const INSTRUCTION_TITLE_BY_TYPE = {
|
|
|
43
52
|
systemAuthorizeNonceAccount: 'Authorize Nonce Account',
|
|
44
53
|
systemWithdrawNonceAccount: 'Withdraw Nonce Account',
|
|
45
54
|
transfer: 'Transfer Token',
|
|
55
|
+
transferChecked: 'Transfer Token Checked',
|
|
46
56
|
unknown: 'Unknown',
|
|
47
57
|
}
|
|
48
58
|
|
|
@@ -56,7 +66,7 @@ class InstructionKeys {
|
|
|
56
66
|
}
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
function getTransactionInstructionsFromMessage(message: Message): TransactionInstruction[] {
|
|
69
|
+
export function getTransactionInstructionsFromMessage(message: Message): TransactionInstruction[] {
|
|
60
70
|
const { accountKeys, instructions } = message
|
|
61
71
|
return instructions.map((instruction) => {
|
|
62
72
|
const { accounts, data, programIdIndex } = instruction
|
|
@@ -110,10 +120,17 @@ function decodeTokenInstructionData(data: Buffer) {
|
|
|
110
120
|
}
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
// Type TransferChecked
|
|
124
|
+
if (data.length > 1 && data[0] === 12) {
|
|
125
|
+
return INSTRUCTION_LAYOUT.decode(data)
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
return TokenInstructions.decodeTokenInstructionData(data)
|
|
114
129
|
}
|
|
115
130
|
|
|
116
|
-
function decodeTokenProgramInstruction(
|
|
131
|
+
export function decodeTokenProgramInstruction(
|
|
132
|
+
instruction: TransactionInstruction
|
|
133
|
+
): DecodedInstruction {
|
|
117
134
|
const decodedInstructionData = decodeTokenInstructionData(instruction.data)
|
|
118
135
|
|
|
119
136
|
if (!decodedInstructionData || Object.keys(decodedInstructionData).length > 1) {
|
|
@@ -202,10 +219,18 @@ export function decodeTransactionInstructions(
|
|
|
202
219
|
transactionMessages: Message[]
|
|
203
220
|
): DecodedInstruction[] {
|
|
204
221
|
const transactionInstructions = transactionMessages.reduce(
|
|
205
|
-
(prevInstructions: TransactionInstruction[], message: Message) =>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
(prevInstructions: TransactionInstruction[], message: Message) => {
|
|
223
|
+
let instructions
|
|
224
|
+
const isTransactionMessage =
|
|
225
|
+
message.instructions.length > 0 && message.instructions[0].programId !== undefined
|
|
226
|
+
if (isTransactionMessage) {
|
|
227
|
+
instructions = message.instructions
|
|
228
|
+
} else {
|
|
229
|
+
instructions = getTransactionInstructionsFromMessage(message)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return [...prevInstructions, ...instructions]
|
|
233
|
+
},
|
|
209
234
|
[]
|
|
210
235
|
)
|
|
211
236
|
return transactionInstructions.map((instruction) => {
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { merge } from 'lodash'
|
|
2
2
|
import assets from '@exodus/assets'
|
|
3
3
|
import type { UnsignedTransaction, SignedTransaction } from '@exodus/models/lib/types'
|
|
4
|
+
|
|
4
5
|
import { Transaction } from '../'
|
|
5
6
|
|
|
6
7
|
export function signUnsignedTx(
|
|
7
8
|
unsignedTx: UnsignedTransaction,
|
|
8
9
|
privateKey: Buffer
|
|
9
10
|
): SignedTransaction {
|
|
10
|
-
const { amount: unitAmount, from, method } = unsignedTx.txData
|
|
11
|
+
const { amount: unitAmount, from, method, transaction } = unsignedTx.txData
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
if (transaction) {
|
|
14
|
+
return _signTx({ tx: transaction, privateKey })
|
|
15
|
+
}
|
|
13
16
|
|
|
17
|
+
const asset = assets.solana
|
|
14
18
|
const address = from
|
|
15
19
|
const amount = unitAmount ? asset.currency.baseUnit(unitAmount).toNumber() : unitAmount
|
|
16
20
|
|
|
@@ -19,39 +23,51 @@ export function signUnsignedTx(
|
|
|
19
23
|
address,
|
|
20
24
|
amount,
|
|
21
25
|
})
|
|
26
|
+
|
|
27
|
+
const tx = createTx({ txData: args, method })
|
|
28
|
+
return _signTx({ tx, privateKey })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Signs plain tx.
|
|
32
|
+
const _signTx = ({ tx, privateKey }) => {
|
|
33
|
+
Transaction.sign(tx, privateKey)
|
|
34
|
+
const rawTx = Transaction.serialize(tx)
|
|
35
|
+
const txId = Transaction.getTxId(tx)
|
|
36
|
+
|
|
37
|
+
return { txId, rawTx }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const createTx = ({ txData, method }) => {
|
|
22
41
|
let tx
|
|
23
42
|
switch (method) {
|
|
24
43
|
case 'delegate':
|
|
25
|
-
tx = createDelegateTransaction(
|
|
44
|
+
tx = createDelegateTransaction(txData)
|
|
26
45
|
break
|
|
27
46
|
case 'undelegate':
|
|
28
|
-
tx = createUndelegateTransaction(
|
|
47
|
+
tx = createUndelegateTransaction(txData)
|
|
29
48
|
break
|
|
30
49
|
case 'withdraw':
|
|
31
|
-
tx = createWithdrawTransaction(
|
|
50
|
+
tx = createWithdrawTransaction(txData)
|
|
51
|
+
break
|
|
52
|
+
case 'closeAccount':
|
|
53
|
+
tx = createCloseAccountTransaction(txData)
|
|
32
54
|
break
|
|
33
55
|
case 'initializeEscrow': {
|
|
34
|
-
tx = createMagicEdenInitializeEscrowTransaction(
|
|
56
|
+
tx = createMagicEdenInitializeEscrowTransaction(txData)
|
|
35
57
|
break
|
|
36
58
|
}
|
|
37
59
|
case 'cancelEscrow':
|
|
38
|
-
tx = createMagicEdenCancelEscrowTransaction(
|
|
60
|
+
tx = createMagicEdenCancelEscrowTransaction(txData)
|
|
39
61
|
break
|
|
40
62
|
case 'exchange':
|
|
41
|
-
tx = createMagicEdenExchangeTransaction(
|
|
63
|
+
tx = createMagicEdenExchangeTransaction(txData)
|
|
42
64
|
break
|
|
43
65
|
default:
|
|
44
66
|
// SOL and Token tx
|
|
45
|
-
tx = createTokenTransaction(
|
|
67
|
+
tx = createTokenTransaction(txData)
|
|
46
68
|
break
|
|
47
69
|
}
|
|
48
|
-
|
|
49
|
-
// sign plain tx
|
|
50
|
-
Transaction.sign(tx, privateKey)
|
|
51
|
-
const rawTx = Transaction.serialize(tx)
|
|
52
|
-
const txId = Transaction.getTxId(tx)
|
|
53
|
-
|
|
54
|
-
return { txId, rawTx }
|
|
70
|
+
return tx
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
const createDelegateTransaction = ({ address, amount, pool, recentBlockhash, seed }) =>
|
|
@@ -146,6 +162,8 @@ const createTokenTransaction = ({
|
|
|
146
162
|
recentBlockhash,
|
|
147
163
|
to,
|
|
148
164
|
tokenMintAddress,
|
|
165
|
+
memo,
|
|
166
|
+
reference,
|
|
149
167
|
}) =>
|
|
150
168
|
new Transaction({
|
|
151
169
|
amount,
|
|
@@ -158,4 +176,14 @@ const createTokenTransaction = ({
|
|
|
158
176
|
recentBlockhash,
|
|
159
177
|
to,
|
|
160
178
|
tokenMintAddress,
|
|
179
|
+
memo,
|
|
180
|
+
reference,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const createCloseAccountTransaction = ({ account, programId, recentBlockhash, walletPublicKey }) =>
|
|
184
|
+
Transaction.createCloseAccount({
|
|
185
|
+
account,
|
|
186
|
+
programId,
|
|
187
|
+
recentBlockhash,
|
|
188
|
+
walletPublicKey,
|
|
161
189
|
})
|