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

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