@exodus/solana-api 2.5.31-alpha.2 → 2.5.31-alpha.3

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-api",
3
- "version": "2.5.31-alpha.2",
3
+ "version": "2.5.31-alpha.3",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -14,11 +14,6 @@
14
14
  "publishConfig": {
15
15
  "access": "restricted"
16
16
  },
17
- "scripts": {
18
- "test": "run -T jest",
19
- "lint": "run -T eslint ./src",
20
- "lint:fix": "yarn lint --fix"
21
- },
22
17
  "dependencies": {
23
18
  "@exodus/asset-json-rpc": "^1.0.0",
24
19
  "@exodus/asset-lib": "^4.0.0",
@@ -37,7 +32,7 @@
37
32
  "wretch": "^1.5.2"
38
33
  },
39
34
  "devDependencies": {
40
- "@exodus/assets-testing": "^1.0.0"
35
+ "@exodus/assets-testing": "file:../../../__testing__"
41
36
  },
42
37
  "gitHead": "9ec7084bcdfe14f1c6f11df393351885773046a6"
43
38
  }
@@ -27,7 +27,6 @@ export class SolanaAccountState extends AccountState {
27
27
  accounts: {}, // stake accounts
28
28
  },
29
29
  }
30
-
31
30
  static _tokens = [asset, ...tokens] // deprecated - will be removed
32
31
 
33
32
  static _postParse(data) {
package/src/api.js CHANGED
@@ -98,7 +98,6 @@ export class Api {
98
98
  if (lodash.get(connection, 'isOpen') && !lodash.get(connection, 'shutdown') && !forceHttp) {
99
99
  return connection.sendMessage(method, params)
100
100
  }
101
-
102
101
  // http fallback
103
102
  return this.api.post({ method, params })
104
103
  }
@@ -121,7 +120,7 @@ export class Api {
121
120
  return state
122
121
  }
123
122
 
124
- async getRecentBlockHash(commitment) {
123
+ async getRecentBlockHash(commitment?) {
125
124
  const result = await this.rpcCall(
126
125
  'getLatestBlockhash',
127
126
  [{ commitment: commitment || 'finalized', encoding: 'jsonParsed' }],
@@ -170,7 +169,7 @@ export class Api {
170
169
  // cursor is a txHash
171
170
 
172
171
  try {
173
- const until = cursor
172
+ let until = cursor
174
173
 
175
174
  const tokenAccountsByOwner = await this.getTokenAccountsByOwner(address) // Array
176
175
  const tokenAccountAddresses = tokenAccountsByOwner
@@ -187,7 +186,7 @@ export class Api {
187
186
  })
188
187
  )
189
188
  )
190
- let txsId = txsResultsByAccount.flat().slice(0, TXS_LIMIT) // merge arrays
189
+ let txsId = txsResultsByAccount.reduce((arr, row) => arr.concat(row), []).slice(0, TXS_LIMIT) // merge arrays
191
190
  txsId = lodash.uniqBy(txsId, 'signature')
192
191
 
193
192
  // get txs details in parallel
@@ -237,8 +236,14 @@ export class Api {
237
236
  tokenAccountsByOwner,
238
237
  { includeUnparsed = false } = {}
239
238
  ) {
240
- let { fee, preBalances, postBalances, preTokenBalances, postTokenBalances, innerInstructions } =
241
- txDetails.meta
239
+ let {
240
+ fee,
241
+ preBalances,
242
+ postBalances,
243
+ preTokenBalances,
244
+ postTokenBalances,
245
+ innerInstructions,
246
+ } = txDetails.meta
242
247
  preBalances = preBalances || []
243
248
  postBalances = postBalances || []
244
249
  preTokenBalances = preTokenBalances || []
@@ -377,7 +382,7 @@ export class Api {
377
382
  const stakeTx = lodash.find(instructions, { program: 'system', type: 'createAccountWithSeed' })
378
383
  const stakeWithdraw = lodash.find(instructions, { program: 'stake', type: 'withdraw' })
379
384
  const stakeUndelegate = lodash.find(instructions, { program: 'stake', type: 'deactivate' })
380
- const hasSolanaTx = solanaTx && preTokenBalances.length === 0 && postTokenBalances.length === 0 // only SOL moved and no tokens movements
385
+ const hasSolanaTx = solanaTx && !preTokenBalances.length && !postTokenBalances.length // only SOL moved and no tokens movements
381
386
 
382
387
  let tx = {}
383
388
  if (hasSolanaTx) {
@@ -444,7 +449,7 @@ export class Api {
444
449
  Array.isArray(tokenAccountsByOwner),
445
450
  'tokenAccountsByOwner is required when parsing token tx'
446
451
  )
447
- const tokenTxs = lodash
452
+ let tokenTxs = lodash
448
453
  .filter(instructions, ({ program, type }) => {
449
454
  return program === 'spl-token' && ['transfer', 'transferChecked'].includes(type)
450
455
  }) // get Token transfer: could have more than 1 instructions
@@ -471,7 +476,7 @@ export class Api {
471
476
  }
472
477
  })
473
478
 
474
- if (tokenTxs.length > 0) {
479
+ if (tokenTxs.length) {
475
480
  // found spl-token simple transfer/transferChecked instruction
476
481
  // .reduce to sum/sub (based on isSending) all the same tokens amount (From instructions -> 1 single tx)
477
482
  tx = tokenTxs.reduce((finalTx, ix) => {
@@ -504,10 +509,10 @@ export class Api {
504
509
  )
505
510
  })
506
511
 
507
- if (preBalances.length > 0 || postBalances.length > 0) {
512
+ if (preBalances.length || postBalances.length) {
508
513
  tx = {}
509
514
 
510
- if (includeUnparsed && innerInstructions.length > 0) {
515
+ if (includeUnparsed && innerInstructions.length) {
511
516
  // when using includeUnparsed for DEX tx we want to keep SOL tx as "unparsed"
512
517
  // 1. we want to treat all SOL dex transactions as "Contract transaction", not "Sent SOL"
513
518
  // 2. default behavior is not perfect. For example it doesn't see SOL-side tx in
@@ -527,7 +532,7 @@ export class Api {
527
532
  }
528
533
 
529
534
  // If it has inner instructions then it's a DEX tx that moved SPL -> SPL
530
- if (innerInstructions.length > 0) {
535
+ if (innerInstructions.length) {
531
536
  tx.dexTxs = innerInstructions
532
537
  // if tx involves only SPL swaps. Expand DEX ix (first element as tx base and the other kept there)
533
538
  if (!tx.from && !solanaTx) {
@@ -567,7 +572,7 @@ export class Api {
567
572
 
568
573
  async getWalletTokensList({ tokenAccounts }) {
569
574
  const tokensMint = []
570
- for (const account of tokenAccounts) {
575
+ for (let account of tokenAccounts) {
571
576
  const mint = account.mintAddress
572
577
 
573
578
  // skip cached NFT
@@ -580,7 +585,6 @@ export class Api {
580
585
  this.tokensToSkip[mint] = true
581
586
  continue
582
587
  }
583
-
584
588
  // OK
585
589
  tokensMint.push(mint)
586
590
  }
@@ -596,7 +600,7 @@ export class Api {
596
600
  )
597
601
 
598
602
  const tokenAccounts = []
599
- for (const entry of accountsList) {
603
+ for (let entry of accountsList) {
600
604
  const { pubkey, account } = entry
601
605
 
602
606
  const mint = lodash.get(account, 'data.parsed.info.mint')
@@ -614,7 +618,6 @@ export class Api {
614
618
  mintAddress: mint,
615
619
  })
616
620
  }
617
-
618
621
  // eventually filter by token
619
622
  return tokenTicker
620
623
  ? tokenAccounts.filter(({ ticker }) => ticker === tokenTicker)
@@ -622,24 +625,18 @@ export class Api {
622
625
  }
623
626
 
624
627
  async getTokensBalance({ address, filterByTokens = [], tokenAccounts }) {
625
- const accounts = tokenAccounts || (await this.getTokenAccountsByOwner(address))
628
+ let accounts = tokenAccounts || (await this.getTokenAccountsByOwner(address))
626
629
 
627
- return accounts.reduce((acc, { tokenName, balance }) => {
628
- if (
629
- tokenName === 'unknown' ||
630
- (filterByTokens.length > 0 && !filterByTokens.includes(tokenName))
631
- )
630
+ const tokensBalance = accounts.reduce((acc, { tokenName, balance }) => {
631
+ if (tokenName === 'unknown' || (filterByTokens.length && !filterByTokens.includes(tokenName)))
632
632
  return acc // filter by supported tokens only
633
- if (acc[tokenName]) {
634
- acc[tokenName] += Number(balance)
635
- }
633
+ if (!acc[tokenName]) acc[tokenName] = Number(balance)
636
634
  // e.g { 'serum': 123 }
637
- else {
638
- acc[tokenName] = Number(balance)
639
- } // merge same token account balance
640
-
635
+ else acc[tokenName] += Number(balance) // merge same token account balance
641
636
  return acc
642
637
  }, {})
638
+
639
+ return tokensBalance
643
640
  }
644
641
 
645
642
  async isAssociatedTokenAccountActive(tokenAddress) {
@@ -647,7 +644,7 @@ export class Api {
647
644
  try {
648
645
  await this.rpcCall('getTokenAccountBalance', [tokenAddress])
649
646
  return true
650
- } catch {
647
+ } catch (e) {
651
648
  return false
652
649
  }
653
650
  }
@@ -700,19 +697,20 @@ export class Api {
700
697
  return account.owner === SYSTEM_PROGRAM_ID.toBase58()
701
698
  ? 'solana'
702
699
  : account.owner === TOKEN_PROGRAM_ID.toBase58()
703
- ? 'token'
704
- : null
700
+ ? 'token'
701
+ : null
705
702
  }
706
703
 
707
704
  async getTokenAddressOwner(address) {
708
705
  const value = await this.getAccountInfo(address)
709
- return lodash.get(value, 'data.parsed.info.owner', null)
706
+ const owner = lodash.get(value, 'data.parsed.info.owner', null)
707
+ return owner
710
708
  }
711
709
 
712
710
  async getAddressMint(address) {
713
711
  const value = await this.getAccountInfo(address)
714
- // token mint
715
- return lodash.get(value, 'data.parsed.info.mint', null)
712
+ const mintAddress = lodash.get(value, 'data.parsed.info.mint', null) // token mint
713
+ return mintAddress
716
714
  }
717
715
 
718
716
  async isTokenAddress(address) {
@@ -747,7 +745,7 @@ export class Api {
747
745
  let locked = 0
748
746
  let withdrawable = 0
749
747
  let pending = 0
750
- for (const entry of res) {
748
+ for (let entry of res) {
751
749
  const addr = entry.pubkey
752
750
  const lamports = lodash.get(entry, 'account.lamports', 0)
753
751
  const delegation = lodash.get(entry, 'account.data.parsed.info.stake.delegation', {})
@@ -768,12 +766,11 @@ export class Api {
768
766
  withdrawable += accounts[addr].canWithdraw ? lamports : 0
769
767
  pending += accounts[addr].isDeactivating ? lamports : 0
770
768
  }
771
-
772
769
  return { accounts, totalStake, locked, withdrawable, pending }
773
770
  }
774
771
 
775
772
  async getRewards(stakingAddresses = []) {
776
- if (stakingAddresses.length === 0) return 0
773
+ if (!stakingAddresses.length) return 0
777
774
 
778
775
  // custom endpoint!
779
776
  const rewards = await this.request('rewards')
@@ -783,9 +780,11 @@ export class Api {
783
780
  .json()
784
781
 
785
782
  // sum rewards for all addresses
786
- return Object.values(rewards).reduce((total, x) => {
783
+ const earnings = Object.values(rewards).reduce((total, x) => {
787
784
  return total + x
788
785
  }, 0)
786
+
787
+ return earnings
789
788
  }
790
789
 
791
790
  async getMinimumBalanceForRentExemption(size) {
@@ -869,7 +868,6 @@ export class Api {
869
868
  if (error.message && error.message.includes('could not find account')) {
870
869
  return defaultValue
871
870
  }
872
-
873
871
  throw error
874
872
  }
875
873
  }
@@ -961,7 +959,7 @@ export class Api {
961
959
  simulateAndRetrieveSideEffects = async (
962
960
  message,
963
961
  publicKey,
964
- transactionMessage // decompiled TransactionMessage
962
+ transactionMessage? // decompiled TransactionMessage
965
963
  ) => {
966
964
  const { config, accountAddresses } = getTransactionSimulationParams(
967
965
  transactionMessage || message
package/src/connection.js CHANGED
@@ -67,8 +67,8 @@ export class Connection {
67
67
  debug('Opening WS to:', reqUrl)
68
68
  const ws = new WebSocket(`${reqUrl}`)
69
69
  ws.onmessage = this.onMessage.bind(this)
70
- ws.addEventListener('open', this.onOpen.bind(this))
71
- ws.addEventListener('close', this.onClose.bind(this))
70
+ ws.onopen = this.onOpen.bind(this)
71
+ ws.onclose = this.onClose.bind(this)
72
72
  ws.onerror = this.onError.bind(this)
73
73
  return ws
74
74
  }
@@ -90,14 +90,14 @@ export class Connection {
90
90
  }
91
91
 
92
92
  get running() {
93
- return !!(!this.isClosed || this.inProcessMessages || this.messageQueue.length > 0)
93
+ return !!(!this.isClosed || this.inProcessMessages || this.messageQueue.length)
94
94
  }
95
95
 
96
96
  get connectionState() {
97
97
  if (this.isConnecting) return 'CONNECTING'
98
- if (this.isOpen) return 'OPEN'
99
- if (this.isClosing) return 'CLOSING'
100
- if (this.isClosed) return 'CLOSED'
98
+ else if (this.isOpen) return 'OPEN'
99
+ else if (this.isClosing) return 'CLOSING'
100
+ else if (this.isClosed) return 'CLOSED'
101
101
  return 'NONE'
102
102
  }
103
103
 
@@ -125,13 +125,7 @@ export class Connection {
125
125
  try {
126
126
  const json = JSON.parse(evt.data)
127
127
  debug('new ws msg:', json)
128
- if (json.error) {
129
- if (lodash.get(this.rpcQueue, json.id)) {
130
- this.rpcQueue[json.id].reject(new Error(json.error.message))
131
- clearTimeout(this.rpcQueue[json.id].timeout)
132
- delete this.rpcQueue[json.id]
133
- } else debug('Unsupported WS message:', json.error.message)
134
- } else {
128
+ if (!json.error) {
135
129
  if (lodash.get(this.rpcQueue, json.id)) {
136
130
  // json-rpc reply
137
131
  clearTimeout(this.rpcQueue[json.id].timeout)
@@ -142,8 +136,13 @@ export class Connection {
142
136
  debug('pushing msg to queue', msg)
143
137
  this.messageQueue.push(msg) // sub results
144
138
  }
145
-
146
139
  this.processMessages()
140
+ } else {
141
+ if (lodash.get(this.rpcQueue, json.id)) {
142
+ this.rpcQueue[json.id].reject(new Error(json.error.message))
143
+ clearTimeout(this.rpcQueue[json.id].timeout)
144
+ delete this.rpcQueue[json.id]
145
+ } else debug('Unsupported WS message:', json.error.message)
147
146
  }
148
147
  } catch (e) {
149
148
  debug(e)
@@ -200,7 +199,7 @@ export class Connection {
200
199
  if (this.inProcessMessages) return null
201
200
  this.inProcessMessages = true
202
201
  try {
203
- while (this.messageQueue.length > 0) {
202
+ while (this.messageQueue.length) {
204
203
  const items = this.messageQueue.splice(0, this.messageQueue.length)
205
204
  await this.callback(items)
206
205
  }
@@ -3,33 +3,35 @@ import { TxSet } from '@exodus/models'
3
3
  // staking may be a feature that may not be available for a given wallet.
4
4
  // In this case, The wallet should exclude the staking balance from the general balance
5
5
 
6
- export const getBalancesFactory =
7
- ({ stakingFeatureAvailable }) =>
8
- ({ asset, accountState, txLog }) => {
9
- const zero = asset.currency.ZERO
10
- const { balance, locked, withdrawable, pending } = fixBalances({
11
- txLog,
12
- balance: getBalanceFromAccountState({ asset, accountState }),
13
- locked: accountState.mem?.locked || zero,
14
- withdrawable: accountState.mem?.withdrawable || zero,
15
- pending: accountState.mem?.pending || zero,
16
- asset,
17
- })
18
- if (asset.baseAsset.name !== asset.name) {
19
- return { balance, spendableBalance: balance }
20
- }
6
+ export const getBalancesFactory = ({ stakingFeatureAvailable }) => ({
7
+ asset,
8
+ accountState,
9
+ txLog,
10
+ }) => {
11
+ const zero = asset.currency.ZERO
12
+ const { balance, locked, withdrawable, pending } = fixBalances({
13
+ txLog,
14
+ balance: getBalanceFromAccountState({ asset, accountState }),
15
+ locked: accountState.mem?.locked || zero,
16
+ withdrawable: accountState.mem?.withdrawable || zero,
17
+ pending: accountState.mem?.pending || zero,
18
+ asset,
19
+ })
20
+ if (asset.baseAsset.name !== asset.name) {
21
+ return { balance, spendableBalance: balance }
22
+ }
21
23
 
22
- const balanceWithoutStaking = balance
23
- .sub(locked)
24
- .sub(withdrawable)
25
- .sub(pending)
26
- .clampLowerZero()
24
+ const balanceWithoutStaking = balance
25
+ .sub(locked)
26
+ .sub(withdrawable)
27
+ .sub(pending)
28
+ .clampLowerZero()
27
29
 
28
- return {
29
- balance: stakingFeatureAvailable ? balance : balanceWithoutStaking,
30
- spendableBalance: balanceWithoutStaking.sub(asset.accountReserve || zero).clampLowerZero(),
31
- }
30
+ return {
31
+ balance: stakingFeatureAvailable ? balance : balanceWithoutStaking,
32
+ spendableBalance: balanceWithoutStaking.sub(asset.accountReserve || zero).clampLowerZero(),
32
33
  }
34
+ }
33
35
 
34
36
  const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pending, asset }) => {
35
37
  for (const tx of txLog) {
@@ -38,7 +40,10 @@ const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pendi
38
40
  balance = balance.sub(tx.feeAmount)
39
41
  }
40
42
 
41
- if (tx.data.staking) {
43
+ if (!tx.data.staking) {
44
+ // coinAmount is negative for sent tx
45
+ balance = balance.sub(tx.coinAmount.abs())
46
+ } else {
42
47
  // staking tx
43
48
  switch (tx.data.staking?.method) {
44
49
  case 'delegate':
@@ -52,13 +57,9 @@ const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pendi
52
57
  locked = asset.currency.ZERO
53
58
  break
54
59
  }
55
- } else {
56
- // coinAmount is negative for sent tx
57
- balance = balance.sub(tx.coinAmount.abs())
58
60
  }
59
61
  }
60
62
  }
61
-
62
63
  return {
63
64
  balance: balance.clampLowerZero(),
64
65
  locked: locked.clampLowerZero(),
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ import assetsList from '@exodus/solana-meta'
4
4
 
5
5
  import { Api } from './api'
6
6
 
7
+ export { Api }
7
8
  export { default as SolanaFeeMonitor } from './fee-monitor'
8
9
  export { SolanaMonitor } from './tx-log'
9
10
  export { SolanaAccountState } from './account-state'
@@ -25,5 +26,3 @@ const assets = connectAssets(keyBy(assetsList, (asset) => asset.name))
25
26
  // Clients should not call an specific server api directly.
26
27
  const serverApi = new Api({ assets })
27
28
  export default serverApi
28
-
29
- export { Api } from './api'
@@ -9,7 +9,9 @@ export const getSolStakedFee = ({ asset, stakingInfo, fee }) => {
9
9
  const { accounts } = stakingInfo
10
10
 
11
11
  const allPending = Object.entries(accounts).length
12
- return allPending > 0 ? fee.mul(allPending) : currency.ZERO
12
+ const pendingFee = allPending > 0 ? fee.mul(allPending) : currency.ZERO
13
+
14
+ return pendingFee
13
15
  }
14
16
 
15
17
  export const getStakingInfo = (accountMem) => {
@@ -97,10 +97,10 @@ export class SolanaMonitor extends BaseMonitor {
97
97
 
98
98
  async getStakingAddressesFromTxLog({ assetName, walletAccount }) {
99
99
  const txLog = await this.aci.getTxLog({ assetName: this.asset.name, walletAccount })
100
- const stakingAddresses = [...txLog]
100
+ const stakingAddresses = Array.from(txLog)
101
101
  .filter((tx) => _.get(tx, 'data.staking.stakeAddresses'))
102
102
  .map((tx) => tx.data.staking.stakeAddresses)
103
- return _.uniq(stakingAddresses.flat())
103
+ return _.uniq(_.flatten(stakingAddresses))
104
104
  }
105
105
 
106
106
  balanceChanged({ account, newAccount }) {
@@ -173,7 +173,7 @@ export class SolanaMonitor extends BaseMonitor {
173
173
  }
174
174
 
175
175
  async getHistory({ address, accountState, refresh } = {}) {
176
- const cursor = refresh ? '' : accountState.cursor
176
+ let cursor = refresh ? '' : accountState.cursor
177
177
  const baseAsset = this.asset
178
178
 
179
179
  const { transactions, newCursor } = await this.api.getTransactions(address, {
@@ -226,7 +226,6 @@ export class SolanaMonitor extends BaseMonitor {
226
226
 
227
227
  item.data.meta = tx.data.meta
228
228
  }
229
-
230
229
  if (asset.assetType === 'SOLANA_TOKEN' && item.feeAmount && item.feeAmount.isPositive) {
231
230
  const feeItem = {
232
231
  ..._.clone(item),
@@ -236,7 +235,6 @@ export class SolanaMonitor extends BaseMonitor {
236
235
  }
237
236
  mappedTransactions.push(feeItem)
238
237
  }
239
-
240
238
  mappedTransactions.push(item)
241
239
  }
242
240
 
package/src/tx-send.js CHANGED
@@ -1,179 +1,183 @@
1
1
  import { createUnsignedTx, findAssociatedTokenAddress } from '@exodus/solana-lib'
2
2
  import assert from 'minimalistic-assert'
3
3
 
4
- export const createAndBroadcastTXFactory =
5
- (api) =>
6
- async ({ asset, walletAccount, address, amount, options = {} }, { assetClientInterface }) => {
7
- const assetName = asset.name
8
- assert(assetClientInterface, `assetClientInterface must be supplied in sendTx for ${assetName}`)
9
-
10
- const {
11
- feeAmount,
12
- method,
13
- stakeAddresses,
14
- seed,
15
- pool,
16
- customMintAddress,
17
- tokenStandard,
18
- // <MagicEden>
19
- initializerAddress,
20
- initializerDepositTokenAddress,
21
- takerAmount,
22
- escrowAddress,
23
- escrowBump,
24
- pdaAddress,
25
- takerAddress,
26
- expectedTakerAmount,
27
- expectedMintAddress,
28
- metadataAddress,
29
- creators,
30
- // </MagicEden>
31
- reference,
32
- memo,
33
- } = options
34
- const { baseAsset } = asset
35
- const from = await assetClientInterface.getReceiveAddress({
36
- assetName: baseAsset.name,
37
- walletAccount,
38
- })
39
-
40
- const isToken = asset.assetType === 'SOLANA_TOKEN'
41
-
42
- // Check if receiver has address active when sending tokens.
43
- if (isToken) {
44
- // check address mint is the same
45
- const targetMint = await api.getAddressMint(address) // null if it's a SOL address
46
- if (targetMint && targetMint !== asset.mintAddress) {
47
- const err = new Error('Wrong Destination Wallet')
48
- err.reason = { mintAddressMismatch: true }
49
- throw err
50
- }
51
- } else {
52
- // sending SOL
53
- const addressType = await api.getAddressType(address)
54
- if (addressType === 'token') {
55
- const err = new Error('Destination Wallet is a Token address')
56
- err.reason = { wrongAddressType: true }
57
- throw err
58
- }
4
+ export const createAndBroadcastTXFactory = (api) => async (
5
+ { asset, walletAccount, address, amount, options = {} },
6
+ { assetClientInterface }
7
+ ) => {
8
+ const assetName = asset.name
9
+ assert(assetClientInterface, `assetClientInterface must be supplied in sendTx for ${assetName}`)
10
+
11
+ const {
12
+ feeAmount,
13
+ method,
14
+ stakeAddresses,
15
+ seed,
16
+ pool,
17
+ customMintAddress,
18
+ tokenStandard,
19
+ // <MagicEden>
20
+ initializerAddress,
21
+ initializerDepositTokenAddress,
22
+ takerAmount,
23
+ escrowAddress,
24
+ escrowBump,
25
+ pdaAddress,
26
+ takerAddress,
27
+ expectedTakerAmount,
28
+ expectedMintAddress,
29
+ metadataAddress,
30
+ creators,
31
+ // </MagicEden>
32
+ reference,
33
+ memo,
34
+ } = options
35
+ const { baseAsset } = asset
36
+ const from = await assetClientInterface.getReceiveAddress({
37
+ assetName: baseAsset.name,
38
+ walletAccount,
39
+ })
40
+
41
+ const isToken = asset.assetType === 'SOLANA_TOKEN'
42
+
43
+ // Check if receiver has address active when sending tokens.
44
+ if (isToken) {
45
+ // check address mint is the same
46
+ const targetMint = await api.getAddressMint(address) // null if it's a SOL address
47
+ if (targetMint && targetMint !== asset.mintAddress) {
48
+ const err = new Error('Wrong Destination Wallet')
49
+ err.reason = { mintAddressMismatch: true }
50
+ throw err
59
51
  }
60
-
61
- const recentBlockhash = options.recentBlockhash || (await api.getRecentBlockHash())
62
-
63
- let tokenParams = Object.create(null)
64
- if (isToken || customMintAddress) {
65
- const tokenMintAddress = customMintAddress || asset.mintAddress
66
- const tokenAddress = findAssociatedTokenAddress(address, tokenMintAddress)
67
- const [destinationAddressType, isAssociatedTokenAccountActive, fromTokenAccountAddresses] =
68
- await Promise.all([
69
- api.getAddressType(address),
70
- api.isAssociatedTokenAccountActive(tokenAddress),
71
- api.getTokenAccountsByOwner(from),
72
- ])
73
-
74
- const fromTokenAddresses = fromTokenAccountAddresses.filter(
75
- ({ mintAddress }) => mintAddress === tokenMintAddress
76
- )
77
-
78
- tokenParams = {
79
- tokenMintAddress,
80
- destinationAddressType,
81
- isAssociatedTokenAccountActive,
82
- fromTokenAddresses,
83
- tokenStandard,
84
- }
85
- }
86
-
87
- const stakingParams = {
88
- method,
89
- stakeAddresses,
90
- seed,
91
- pool,
52
+ } else {
53
+ // sending SOL
54
+ const addressType = await api.getAddressType(address)
55
+ if (addressType === 'token') {
56
+ const err = new Error('Destination Wallet is a Token address')
57
+ err.reason = { wrongAddressType: true }
58
+ throw err
92
59
  }
60
+ }
93
61
 
94
- const magicEdenParams = {
95
- method,
96
- initializerAddress,
97
- initializerDepositTokenAddress,
98
- takerAmount,
99
- escrowAddress,
100
- escrowBump,
101
- pdaAddress,
102
- takerAddress,
103
- expectedTakerAmount,
104
- expectedMintAddress,
105
- metadataAddress,
106
- creators,
62
+ const recentBlockhash = options.recentBlockhash || (await api.getRecentBlockHash())
63
+
64
+ let tokenParams = Object.create(null)
65
+ if (isToken || customMintAddress) {
66
+ const tokenMintAddress = customMintAddress || asset.mintAddress
67
+ const tokenAddress = findAssociatedTokenAddress(address, tokenMintAddress)
68
+ const [
69
+ destinationAddressType,
70
+ isAssociatedTokenAccountActive,
71
+ fromTokenAccountAddresses,
72
+ ] = await Promise.all([
73
+ api.getAddressType(address),
74
+ api.isAssociatedTokenAccountActive(tokenAddress),
75
+ api.getTokenAccountsByOwner(from),
76
+ ])
77
+
78
+ const fromTokenAddresses = fromTokenAccountAddresses.filter(
79
+ ({ mintAddress }) => mintAddress === tokenMintAddress
80
+ )
81
+
82
+ tokenParams = {
83
+ tokenMintAddress,
84
+ destinationAddressType,
85
+ isAssociatedTokenAccountActive,
86
+ fromTokenAddresses,
87
+ tokenStandard,
107
88
  }
89
+ }
108
90
 
109
- const unsignedTransaction = createUnsignedTx({
110
- asset,
111
- from,
112
- to: address,
113
- amount,
114
- fee: feeAmount,
115
- recentBlockhash,
116
- reference,
117
- memo,
118
- ...tokenParams,
119
- ...stakingParams,
120
- ...magicEdenParams,
121
- })
122
-
123
- const { txId, rawTx } = await assetClientInterface.signTransaction({
124
- assetName: baseAsset.name,
125
- unsignedTx: unsignedTransaction,
126
- walletAccount,
127
- })
91
+ const stakingParams = {
92
+ method,
93
+ stakeAddresses,
94
+ seed,
95
+ pool,
96
+ }
128
97
 
129
- await baseAsset.api.broadcastTx(rawTx)
98
+ const magicEdenParams = {
99
+ method,
100
+ initializerAddress,
101
+ initializerDepositTokenAddress,
102
+ takerAmount,
103
+ escrowAddress,
104
+ escrowBump,
105
+ pdaAddress,
106
+ takerAddress,
107
+ expectedTakerAmount,
108
+ expectedMintAddress,
109
+ metadataAddress,
110
+ creators,
111
+ }
130
112
 
131
- const selfSend = from === address
132
- const isStakingTx = ['delegate', 'undelegate', 'withdraw'].includes(method)
133
- const coinAmount = isStakingTx
134
- ? amount.abs()
135
- : selfSend
136
- ? asset.currency.ZERO
137
- : amount.abs().negate()
113
+ const unsignedTransaction = createUnsignedTx({
114
+ asset,
115
+ from,
116
+ to: address,
117
+ amount,
118
+ fee: feeAmount,
119
+ recentBlockhash,
120
+ reference,
121
+ memo,
122
+ ...tokenParams,
123
+ ...stakingParams,
124
+ ...magicEdenParams,
125
+ })
126
+
127
+ const { txId, rawTx } = await assetClientInterface.signTransaction({
128
+ assetName: baseAsset.name,
129
+ unsignedTx: unsignedTransaction,
130
+ walletAccount,
131
+ })
132
+
133
+ await baseAsset.api.broadcastTx(rawTx)
134
+
135
+ const selfSend = from === address
136
+ const isStakingTx = ['delegate', 'undelegate', 'withdraw'].includes(method)
137
+ const coinAmount = isStakingTx
138
+ ? amount.abs()
139
+ : selfSend
140
+ ? asset.currency.ZERO
141
+ : amount.abs().negate()
142
+
143
+ const data = isStakingTx ? { staking: stakingParams } : Object.create(null)
144
+ const tx = {
145
+ txId,
146
+ confirmations: 0,
147
+ coinName: assetName,
148
+ coinAmount,
149
+ feeAmount,
150
+ feeCoinName: asset.feeAsset.name,
151
+ selfSend,
152
+ to: address,
153
+ data,
154
+ currencies: { [assetName]: asset.currency, [asset.feeAsset.name]: asset.feeAsset.currency },
155
+ }
156
+ await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
138
157
 
139
- const data = isStakingTx ? { staking: stakingParams } : Object.create(null)
140
- const tx = {
158
+ if (isToken) {
159
+ // write tx entry in solana for token fee
160
+ const txForFee = {
141
161
  txId,
142
162
  confirmations: 0,
143
- coinName: assetName,
144
- coinAmount,
163
+ coinName: baseAsset.name,
164
+ coinAmount: baseAsset.currency.ZERO,
165
+ tokens: [assetName],
145
166
  feeAmount,
146
- feeCoinName: asset.feeAsset.name,
147
- selfSend,
167
+ feeCoinName: baseAsset.feeAsset.name,
148
168
  to: address,
149
- data,
150
- currencies: { [assetName]: asset.currency, [asset.feeAsset.name]: asset.feeAsset.currency },
151
- }
152
- await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
153
-
154
- if (isToken) {
155
- // write tx entry in solana for token fee
156
- const txForFee = {
157
- txId,
158
- confirmations: 0,
159
- coinName: baseAsset.name,
160
- coinAmount: baseAsset.currency.ZERO,
161
- tokens: [assetName],
162
- feeAmount,
163
- feeCoinName: baseAsset.feeAsset.name,
164
- to: address,
165
- selfSend,
166
- currencies: {
167
- [baseAsset.name]: baseAsset.currency,
168
- [baseAsset.feeAsset.name]: baseAsset.feeAsset.currency,
169
- },
170
- }
171
- await assetClientInterface.updateTxLogAndNotify({
172
- assetName: baseAsset.name,
173
- walletAccount,
174
- txs: [txForFee],
175
- })
169
+ selfSend,
170
+ currencies: {
171
+ [baseAsset.name]: baseAsset.currency,
172
+ [baseAsset.feeAsset.name]: baseAsset.feeAsset.currency,
173
+ },
176
174
  }
177
-
178
- return { txId }
175
+ await assetClientInterface.updateTxLogAndNotify({
176
+ assetName: baseAsset.name,
177
+ walletAccount,
178
+ txs: [txForFee],
179
+ })
179
180
  }
181
+
182
+ return { txId }
183
+ }