@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-lib",
3
- "version": "1.3.15",
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": "3c0551a25d29f31be43d522e742d5754cd7deb5f"
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 './spl-token'
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
@@ -6,6 +6,7 @@ export * from './encode'
6
6
  export * from './keypair'
7
7
  export * from './tx'
8
8
  export {
9
+ TransactionInstruction,
9
10
  StakeInstruction,
10
11
  PublicKey,
11
12
  Transaction as SolanaWeb3Transaction,
@@ -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
- // add transfer token instruction
151
- this.transaction.add(
152
- createTokenTransferInstruction(from, tokenAccountAddress, dest, amountToSend)
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(instruction: TransactionInstruction): DecodedInstruction {
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
- ...prevInstructions,
207
- ...getTransactionInstructionsFromMessage(message),
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
- const asset = assets.solana
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(args)
44
+ tx = createDelegateTransaction(txData)
26
45
  break
27
46
  case 'undelegate':
28
- tx = createUndelegateTransaction(args)
47
+ tx = createUndelegateTransaction(txData)
29
48
  break
30
49
  case 'withdraw':
31
- tx = createWithdrawTransaction(args)
50
+ tx = createWithdrawTransaction(txData)
51
+ break
52
+ case 'closeAccount':
53
+ tx = createCloseAccountTransaction(txData)
32
54
  break
33
55
  case 'initializeEscrow': {
34
- tx = createMagicEdenInitializeEscrowTransaction(args)
56
+ tx = createMagicEdenInitializeEscrowTransaction(txData)
35
57
  break
36
58
  }
37
59
  case 'cancelEscrow':
38
- tx = createMagicEdenCancelEscrowTransaction(args)
60
+ tx = createMagicEdenCancelEscrowTransaction(txData)
39
61
  break
40
62
  case 'exchange':
41
- tx = createMagicEdenExchangeTransaction(args)
63
+ tx = createMagicEdenExchangeTransaction(txData)
42
64
  break
43
65
  default:
44
66
  // SOL and Token tx
45
- tx = createTokenTransaction(args)
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
  })