@exodus/solana-lib 1.7.5 → 1.8.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.
@@ -6,52 +6,49 @@ import {
6
6
 
7
7
  export const getSupportedPurposes = () => [44]
8
8
 
9
- export const createGetKeyIdentifier = ({ bip44 } = {}) => (partialParams = {}) => {
10
- assert(typeof bip44 === 'number', 'bip44 must be a number')
9
+ export const createGetKeyIdentifier =
10
+ ({ bip44 } = {}) =>
11
+ (partialParams = {}) => {
12
+ assert(typeof bip44 === 'number', 'bip44 must be a number')
11
13
 
12
- const params = {
13
- chainIndex: 0,
14
- addressIndex: 0,
15
- ...partialParams,
16
- }
17
- const unhardenedBip44 = unhardenDerivationIndex(bip44)
18
- const { compatibilityMode, purpose, accountIndex, addressIndex } = params
14
+ const params = {
15
+ chainIndex: 0,
16
+ addressIndex: 0,
17
+ ...partialParams,
18
+ }
19
+ const unhardenedBip44 = unhardenDerivationIndex(bip44)
20
+ const { compatibilityMode, purpose, accountIndex, addressIndex } = params
19
21
 
20
- const allowedPurposes = getSupportedPurposes()
21
- assert(
22
- allowedPurposes.includes(purpose),
23
- `purpose was ${purpose}, which is not allowed. Can be one of the following: ${allowedPurposes.join(
24
- ', '
25
- )}`
26
- )
22
+ const allowedPurposes = getSupportedPurposes()
23
+ assert(
24
+ allowedPurposes.includes(purpose),
25
+ `purpose was ${purpose}, which is not allowed. Can be one of the following: ${allowedPurposes.join(
26
+ ', '
27
+ )}`
28
+ )
27
29
 
28
- switch (compatibilityMode) {
29
- case 'phantom':
30
- // Phantom doesn't use chainIndex (normal vs change address)
31
- return {
32
- derivationAlgorithm: 'SLIP10',
33
- derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'/${addressIndex}'`,
34
- keyType: 'nacl',
35
- }
36
- case 'ledger':
37
- return {
38
- derivationAlgorithm: 'SLIP10',
39
- derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'`,
40
- keyType: 'nacl',
41
- }
42
- case 'trust':
43
- return {
44
- derivationAlgorithm: 'SLIP10',
45
- derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'`,
46
- keyType: 'nacl',
47
- }
48
- case 'mathwallet':
49
- return {
50
- derivationAlgorithm: 'BIP32',
51
- derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'/${addressIndex}`,
52
- keyType: 'nacl',
53
- }
54
- default:
55
- return _createGetKeyIdentifier({ bip44, keyType: 'nacl' })(params)
30
+ switch (compatibilityMode) {
31
+ case 'phantom':
32
+ // Phantom doesn't use chainIndex (normal vs change address)
33
+ return {
34
+ derivationAlgorithm: 'SLIP10',
35
+ derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'/${addressIndex}'`,
36
+ keyType: 'nacl',
37
+ }
38
+ case 'ledger':
39
+ case 'trust':
40
+ return {
41
+ derivationAlgorithm: 'SLIP10',
42
+ derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'`,
43
+ keyType: 'nacl',
44
+ }
45
+ case 'mathwallet':
46
+ return {
47
+ derivationAlgorithm: 'BIP32',
48
+ derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'/${addressIndex}`,
49
+ keyType: 'nacl',
50
+ }
51
+ default:
52
+ return _createGetKeyIdentifier({ bip44, keyType: 'nacl' })(params)
53
+ }
56
54
  }
57
- }
package/src/keypair.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import nacl from 'tweetnacl'
2
- import { PublicKey } from './vendor'
2
+ import { PublicKey } from './vendor/publickey'
3
3
 
4
4
  export function getKeyPairFromPrivateKey(privateKey) {
5
5
  const { publicKey, secretKey } = nacl.sign.keyPair.fromSeed(
@@ -19,13 +19,9 @@ const idl = {
19
19
  }
20
20
 
21
21
  export function sighash(nameSpace, ixName) {
22
- let preimage = `${nameSpace}:${ixName}`
22
+ const preimage = `${nameSpace}:${ixName}`
23
23
 
24
- return Buffer.from(
25
- createHash('sha256')
26
- .update(preimage)
27
- .digest()
28
- ).slice(0, 8)
24
+ return Buffer.from(createHash('sha256').update(preimage).digest()).slice(0, 8)
29
25
  }
30
26
 
31
27
  export function encodeData(name, args = {}) {
@@ -4,16 +4,16 @@ import { encodeData } from './coders'
4
4
 
5
5
  const PLATFORM_FEES_PROGRAM_ID = new PublicKey('2NZukH2TXpcuZP4htiuT8CFxcaQSWzkkR6kepSWnZ24Q')
6
6
 
7
- export class MagicEdenEscrowProgram {
8
- static get programId() {
7
+ export const MagicEdenEscrowProgram = {
8
+ get programId() {
9
9
  return MAGIC_EDEN_ESCROW_PROGRAM_ID
10
- }
10
+ },
11
11
 
12
- static get space() {
12
+ get space() {
13
13
  return 80
14
- }
14
+ },
15
15
 
16
- static initializeEscrow(params) {
16
+ initializeEscrow(params) {
17
17
  const {
18
18
  initializerPubkey,
19
19
  initializerDepositTokenPubkey,
@@ -55,9 +55,9 @@ export class MagicEdenEscrowProgram {
55
55
  programId: this.programId,
56
56
  data: encodeData('initializeEscrow', { takerAmount, escrowBump }),
57
57
  })
58
- }
58
+ },
59
59
 
60
- static cancelEscrow(params) {
60
+ cancelEscrow(params) {
61
61
  const { initializerPubkey, initializerDepositTokenPubkey, pdaPubkey, escrowPubkey } = params
62
62
 
63
63
  return new Transaction().add({
@@ -91,9 +91,9 @@ export class MagicEdenEscrowProgram {
91
91
  programId: this.programId,
92
92
  data: encodeData('cancelEscrow'),
93
93
  })
94
- }
94
+ },
95
95
 
96
- static exchange(params) {
96
+ exchange(params) {
97
97
  const {
98
98
  expectedTakerAmount,
99
99
  expectedMintPubkey,
@@ -167,5 +167,5 @@ export class MagicEdenEscrowProgram {
167
167
  expectedMint: expectedMintPubkey.toBuffer(),
168
168
  }),
169
169
  })
170
- }
170
+ },
171
171
  }
@@ -2,6 +2,7 @@ import assert from 'assert'
2
2
  import BN from 'bn.js'
3
3
  import bs58 from 'bs58'
4
4
  import { get } from 'lodash'
5
+ import feeData from './fee-data/solana'
5
6
 
6
7
  import { getKeyPairFromPrivateKey } from './keypair'
7
8
  import { findAssociatedTokenAddress, createStakeAddress } from './encode'
@@ -20,6 +21,7 @@ import {
20
21
  Authorized,
21
22
  Lockup,
22
23
  TransactionInstruction,
24
+ ComputeBudgetProgram,
23
25
  } from './vendor'
24
26
  import { MagicEdenEscrowProgram } from './magiceden/escrow-program'
25
27
  import { MEMO_PROGRAM_ID, SEED, STAKE_PROGRAM_ID } from './constants'
@@ -30,7 +32,7 @@ class Tx {
30
32
  to,
31
33
  amount,
32
34
  recentBlockhash,
33
- // fee, // (Fee per Signature: 5000 lamports)
35
+ fee, // (Fee per Signature: 5000 lamports)
34
36
  // Tokens related:
35
37
  // pass either name or mintAddress, if both, mintAddress has priority
36
38
  tokenMintAddress,
@@ -48,14 +50,15 @@ class Tx {
48
50
  assert(amount, 'amount is required')
49
51
  assert(typeof amount === 'number', 'amount must be a number')
50
52
  }
53
+
51
54
  assert(recentBlockhash, 'recentBlockhash is required')
52
55
  if (tokenMintAddress) {
53
56
  assert(
54
- typeof destinationAddressType !== 'undefined',
57
+ destinationAddressType !== undefined,
55
58
  'destinationAddressType is required when sending tokens'
56
59
  )
57
60
  assert(
58
- typeof isAssociatedTokenAccountActive !== 'undefined',
61
+ isAssociatedTokenAccountActive !== undefined,
59
62
  'isAssociatedTokenAccountActive is required when sending tokens'
60
63
  ) // needed to create the recipient account
61
64
  assert(Array.isArray(fromTokenAddresses), 'fromTokenAddresses Array is required')
@@ -95,6 +98,17 @@ class Tx {
95
98
  })
96
99
  )
97
100
  }
101
+
102
+ // if fee greater than base fee. Add prioritization fee:
103
+ if (fee > feeData.fee.toBaseNumber()) {
104
+ const ratio = fee - feeData.fee.toBaseNumber()
105
+ if (ratio > 1_000_000) throw new Error('Prioritization fee is too high')
106
+
107
+ const priorityFeeInstruction = ComputeBudgetProgram.setComputeUnitPrice({
108
+ microLamports: ratio || 100,
109
+ }) // 1 microLamport = 0.000001 lamports
110
+ this.transaction.add(priorityFeeInstruction)
111
+ }
98
112
  }
99
113
 
100
114
  buildSOLtransaction({ from, to, amount, recentBlockhash, feePayer, reference }) {
@@ -229,8 +243,7 @@ class Tx {
229
243
  votePubkey: poolKey, // pool vote key
230
244
  })
231
245
 
232
- const transaction = new Transaction({ recentBlockhash }).add(programTx).add(delegateTx)
233
- return transaction
246
+ return new Transaction({ recentBlockhash }).add(programTx).add(delegateTx)
234
247
  }
235
248
 
236
249
  static undelegate({ address, stakeAddresses, recentBlockhash }) {
@@ -388,7 +401,7 @@ class Tx {
388
401
  const stakingInstructions = tx.instructions.filter(
389
402
  (ix) => ix.programId.toString() === STAKE_PROGRAM_ID.toString()
390
403
  )
391
- const isStakingTx = !!stakingInstructions.length
404
+ const isStakingTx = stakingInstructions.length > 0
392
405
 
393
406
  if (!isStakingTx) return null // normal transfer tx
394
407
 
@@ -396,7 +409,7 @@ class Tx {
396
409
  txId,
397
410
  owner: tx.getFeePayer().toString(), // SOL sender
398
411
  }
399
- const txDetails = stakingInstructions.reduce((info, ix) => {
412
+ return stakingInstructions.reduce((info, ix) => {
400
413
  const type = StakeInstruction.decodeInstructionType(ix)
401
414
  switch (type) {
402
415
  case 'Delegate':
@@ -424,14 +437,13 @@ class Tx {
424
437
  return info
425
438
  }
426
439
  }, info)
427
-
428
- return txDetails
429
440
  }
430
441
 
431
442
  getTxId() {
432
443
  if (!this.transaction.signature) {
433
444
  throw new Error('Cannot get txId, tx is not signed')
434
445
  }
446
+
435
447
  return bs58.encode(this.transaction.signature)
436
448
  }
437
449
 
@@ -441,6 +453,7 @@ class Tx {
441
453
  if (!transaction.signature) {
442
454
  throw new Error('Cannot get txId, tx is not signed')
443
455
  }
456
+
444
457
  return bs58.encode(transaction.signature)
445
458
  }
446
459
 
@@ -34,11 +34,11 @@ export function buildRawTransaction(signData, signatures) {
34
34
  const rawTransactionLength = signData.length + signaturesLength
35
35
 
36
36
  if (signatures.length > MAX_SIGNATURES) {
37
- throw Error(`Too many signatures: ${signatures.length} > ${MAX_SIGNATURES}`)
37
+ throw new Error(`Too many signatures: ${signatures.length} > ${MAX_SIGNATURES}`)
38
38
  }
39
39
 
40
40
  if (rawTransactionLength > PACKET_DATA_SIZE) {
41
- throw Error(`Transaction too large: ${rawTransactionLength} > ${PACKET_DATA_SIZE}`)
41
+ throw new Error(`Transaction too large: ${rawTransactionLength} > ${PACKET_DATA_SIZE}`)
42
42
  }
43
43
 
44
44
  const rawTransaction = Buffer.alloc(rawTransactionLength)
@@ -50,7 +50,7 @@ export function buildRawTransaction(signData, signatures) {
50
50
  }
51
51
 
52
52
  if (signature.length !== SIGNATURE_LENGTH) {
53
- throw Error('Invalid signature length')
53
+ throw new Error('Invalid signature length')
54
54
  }
55
55
 
56
56
  Buffer.from(signature).copy(rawTransaction, signatureCount.length + index * SIGNATURE_LENGTH)
package/src/tx/common.js CHANGED
@@ -13,6 +13,7 @@ export function getTxId(tx) {
13
13
  if (signature === null) {
14
14
  throw new Error('Cannot get transaction ID of unsigned transaction')
15
15
  }
16
+
16
17
  return base58.encode(signature)
17
18
  }
18
19
 
@@ -1,4 +1,4 @@
1
- import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
1
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../helpers/spl-token'
2
2
  import { MARKETS, TokenInstructions } from '@project-serum/serum'
3
3
  import * as BufferLayout from '@exodus/buffer-layout'
4
4
 
@@ -126,23 +126,37 @@ export function decodeTokenProgramInstruction(instruction) {
126
126
 
127
127
  let data = decodedInstructionData[type]
128
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),
129
+ switch (type) {
130
+ case 'initializeAccount': {
131
+ data = {
132
+ accountPubkey: keys.getAddress(TokenInstructions.INITIALIZE_ACCOUNT_ACCOUNT_INDEX),
133
+ mintPubKey: keys.getAddress(TokenInstructions.INITIALIZE_ACCOUNT_MINT_INDEX),
134
+ ownerPubKey: keys.getAddress(TokenInstructions.INITIALIZE_ACCOUNT_OWNER_INDEX),
135
+ }
136
+
137
+ break
134
138
  }
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),
139
+
140
+ case 'closeAccount':
141
+ case 'transfer': {
142
+ data = {
143
+ ...data,
144
+ sourcePubkey: keys.getAddress(TokenInstructions.TRANSFER_SOURCE_INDEX),
145
+ destinationPubKey: keys.getAddress(TokenInstructions.TRANSFER_DESTINATION_INDEX),
146
+ ownerPubKey: keys.getAddress(TokenInstructions.TRANSFER_OWNER_INDEX),
147
+ }
148
+
149
+ break
141
150
  }
142
- } else if (type === 'createSyncNativeInstruction') {
143
- data = {
144
- pubkey: keys.getAddress(0),
151
+
152
+ case 'createSyncNativeInstruction': {
153
+ data = {
154
+ pubkey: keys.getAddress(0),
155
+ }
156
+
157
+ break
145
158
  }
159
+ // No default
146
160
  }
147
161
 
148
162
  return {
@@ -1,6 +1,7 @@
1
1
  import { asset } from '@exodus/solana-meta'
2
2
 
3
- import { Transaction, PublicKey } from '../'
3
+ import { PublicKey } from '../vendor'
4
+ import Transaction from '../transaction'
4
5
  import { createMetaplexTransferTransaction } from '../helpers/metaplex-transfer'
5
6
 
6
7
  /**
@@ -9,25 +10,27 @@ import { createMetaplexTransferTransaction } from '../helpers/metaplex-transfer'
9
10
  * @returns a Solana Web3.js Transaction object
10
11
  */
11
12
  export function prepareForSigning(unsignedTx) {
12
- const { amount: unitAmount, from, method, transaction } = unsignedTx.txData
13
+ const { amount: unitAmount, fee: feeAmount, from, method, transaction } = unsignedTx.txData
13
14
 
14
- if (transaction) {
15
- // unsignedTx contained a transaction in web3.js format
16
- return transaction
17
- } else {
18
- // Create
15
+ if (!transaction) {
16
+ // Create a transaction in web3.js format
19
17
  const address = from
20
18
  const amount = unitAmount ? asset.currency.baseUnit(unitAmount).toNumber() : unitAmount
19
+ const fee = feeAmount ? asset.currency.baseUnit(feeAmount).toNumber() : feeAmount
21
20
 
22
- const txData = { ...unsignedTx.txData, address, amount }
21
+ const txData = { ...unsignedTx.txData, address, amount, fee }
23
22
 
24
23
  const transaction = createTx({ txData, method })
25
24
 
26
25
  if (!transaction.feePayer) {
27
26
  transaction.feePayer = new PublicKey(from)
28
27
  }
28
+
29
29
  return transaction
30
30
  }
31
+
32
+ // unsignedTx contained a transaction in web3.js format
33
+ return transaction
31
34
  }
32
35
 
33
36
  const createTx = ({ txData, method }) => {
@@ -49,6 +52,7 @@ const createTx = ({ txData, method }) => {
49
52
  tx = createMagicEdenInitializeEscrowTransaction(txData)
50
53
  break
51
54
  }
55
+
52
56
  case 'cancelEscrow':
53
57
  tx = createMagicEdenCancelEscrowTransaction(txData)
54
58
  break
@@ -63,6 +67,7 @@ const createTx = ({ txData, method }) => {
63
67
  tx = createTokenTransaction(txData)
64
68
  break
65
69
  }
70
+
66
71
  return tx
67
72
  }
68
73
 
@@ -150,6 +155,7 @@ const createMagicEdenExchangeTransaction = ({
150
155
  const createTokenTransaction = ({
151
156
  amount,
152
157
  destinationAddressType,
158
+ fee,
153
159
  feePayer,
154
160
  from,
155
161
  fromTokenAddresses,
@@ -164,6 +170,7 @@ const createTokenTransaction = ({
164
170
  new Transaction({
165
171
  amount,
166
172
  destinationAddressType,
173
+ fee,
167
174
  feePayer,
168
175
  from,
169
176
  fromTokenAddresses,
@@ -23,13 +23,11 @@ const signWithHardwareWallet = async ({ tx, hardwareDevice, accountIndex }) => {
23
23
  ? tx.message.serialize()
24
24
  : tx.compileMessage().serialize()
25
25
 
26
- const signatures = await hardwareDevice.signTransaction({
26
+ return hardwareDevice.signTransaction({
27
27
  assetName: 'solana',
28
28
  signableTransaction: Buffer.from(messageToSign),
29
29
  derivationPaths: [`m/44'/501'/${accountIndex}'`],
30
30
  })
31
-
32
- return signatures
33
31
  }
34
32
 
35
33
  const applySignatures = ({ tx, signatures }) => {
@@ -10,7 +10,7 @@ function isLegacyMessage(data) {
10
10
  const message = Message.from(data)
11
11
  message.serialize() // Invalid messages will throw on serialization.
12
12
  return true
13
- } catch (err) {
13
+ } catch {
14
14
  return false
15
15
  }
16
16
  }
@@ -22,9 +22,9 @@ function isVersionedMessage(data) {
22
22
  // so we ban all bytes starting at 0x80 and ending at 0xFE
23
23
  // 0xFF is allowed because that is used for offchain messages
24
24
  return data[0] >= 0x80 && data[0] !== 0xff
25
- } else {
26
- return false
27
25
  }
26
+
27
+ return false
28
28
  }
29
29
 
30
30
  export function isTransactionMessage(data) {
@@ -40,6 +40,5 @@ export default function signMessage({ message, privateKey }) {
40
40
  'attempted to sign transaction using message signing'
41
41
  )
42
42
  const signature = nacl.sign.detached(messageBuffer, secretKey)
43
- const bs58Signature = bs58.encode(signature)
44
- return bs58Signature
43
+ return bs58.encode(signature)
45
44
  }
@@ -20,19 +20,7 @@ export function getTransactionSimulationParams(transactionMessage) {
20
20
  transactionMessage.staticAccountKeys.forEach((account) =>
21
21
  accountAddresses.add(account.toString())
22
22
  )
23
- } else if (!transactionMessage.accountKeys) {
24
- const programIds = new Set(
25
- transactionMessage.instructions.map((instruction) => instruction.programId.toString())
26
- )
27
-
28
- transactionMessage.instructions.forEach((instruction) => {
29
- instruction.keys.forEach((key) => {
30
- if (!programIds.has(key.pubkey.toString())) {
31
- accountAddresses.add(key.pubkey.toString())
32
- }
33
- })
34
- })
35
- } else {
23
+ } else if (transactionMessage.accountKeys) {
36
24
  if (!indexToProgramIds) {
37
25
  indexToProgramIds = new Map()
38
26
  transactionMessage.instructions.forEach((instruction) =>
@@ -46,16 +34,28 @@ export function getTransactionSimulationParams(transactionMessage) {
46
34
  transactionMessage.accountKeys
47
35
  .filter((_, index) => !indexToProgramIds.has(index))
48
36
  .forEach((account) => accountAddresses.add(account.toString()))
37
+ } else {
38
+ const programIds = new Set(
39
+ transactionMessage.instructions.map((instruction) => instruction.programId.toString())
40
+ )
41
+
42
+ transactionMessage.instructions.forEach((instruction) => {
43
+ instruction.keys.forEach((key) => {
44
+ if (!programIds.has(key.pubkey.toString())) {
45
+ accountAddresses.add(key.pubkey.toString())
46
+ }
47
+ })
48
+ })
49
49
  }
50
50
 
51
51
  config['accounts'] = {
52
52
  encoding: 'base64',
53
- addresses: Array.from(accountAddresses),
53
+ addresses: [...accountAddresses],
54
54
  }
55
55
 
56
56
  return {
57
57
  config,
58
- accountAddresses: Array.from(accountAddresses),
58
+ accountAddresses: [...accountAddresses],
59
59
  }
60
60
  }
61
61
 
@@ -67,7 +67,7 @@ export function filterAccountsByOwner(futureAccountsState, accountAddresses, pub
67
67
 
68
68
  // Shouldn't happen
69
69
  if (futureAccountsState.length !== accountAddresses.length) {
70
- throw Error('Simulation returning wrong account length')
70
+ throw new Error('Simulation returning wrong account length')
71
71
  }
72
72
 
73
73
  futureAccountsState.forEach((futureAccount, index) => {
@@ -82,6 +82,7 @@ export function filterAccountsByOwner(futureAccountsState, accountAddresses, pub
82
82
  address: publicKey,
83
83
  })
84
84
  }
85
+
85
86
  return
86
87
  }
87
88
 
@@ -95,7 +96,7 @@ export function filterAccountsByOwner(futureAccountsState, accountAddresses, pub
95
96
  tokenAccounts.push({
96
97
  amount,
97
98
  mint: new PublicKey(token.mint).toString(),
98
- owner: owner,
99
+ owner,
99
100
  address: accountAddress.toString(),
100
101
  })
101
102
  }