@exodus/solana-api 2.5.34 → 3.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "2.5.34",
3
+ "version": "3.1.0",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -29,7 +29,7 @@
29
29
  "@exodus/models": "^10.1.0",
30
30
  "@exodus/nfts-core": "^0.5.0",
31
31
  "@exodus/simple-retry": "^0.0.6",
32
- "@exodus/solana-lib": "^1.7.5",
32
+ "@exodus/solana-lib": "^2.1.0",
33
33
  "@exodus/solana-meta": "^1.0.7",
34
34
  "bn.js": "^4.11.0",
35
35
  "debug": "^4.1.1",
@@ -44,5 +44,5 @@
44
44
  "devDependencies": {
45
45
  "@exodus/assets-testing": "^1.0.0"
46
46
  },
47
- "gitHead": "9da341db0c5c18d52690699d241675c1f7392d71"
47
+ "gitHead": "84f77ed9720dcdabb8057ed9170f45fcb4f9eff9"
48
48
  }
@@ -1,4 +1,3 @@
1
- import assetsList, { asset, tokens } from '@exodus/solana-meta'
2
1
  import { AccountState } from '@exodus/models'
3
2
  import { assetsListToObject } from '@exodus/assets'
4
3
  import { isString, reduce } from 'lodash'
@@ -7,41 +6,46 @@ import { isNumberUnit } from '@exodus/currency'
7
6
  const parseBalance = (balance, asset) =>
8
7
  !isNumberUnit(balance) && isString(balance) ? asset.currency.parse(balance) : balance
9
8
 
10
- export class SolanaAccountState extends AccountState {
11
- static defaults = {
12
- cursor: '',
13
- balance: asset.currency.ZERO,
14
- tokenBalances: {},
15
- mem: {
16
- loaded: false,
17
- staking: {
18
- // remote-config data
19
- enabled: true,
20
- pool: null,
9
+ export const createAccountState = ({ assetList }) => {
10
+ const asset = assetList.find((asset) => asset.baseAssetName === asset.name)
11
+ const tokens = assetList.filter((asset) => asset.baseAssetName !== asset.name)
12
+
13
+ return class SolanaAccountState extends AccountState {
14
+ static defaults = {
15
+ cursor: '',
16
+ balance: asset.currency.ZERO,
17
+ tokenBalances: {},
18
+ mem: {
19
+ loaded: false,
20
+ staking: {
21
+ // remote-config data
22
+ enabled: true,
23
+ pool: null,
24
+ },
25
+ isDelegating: false,
26
+ locked: asset.currency.defaultUnit(0),
27
+ withdrawable: asset.currency.defaultUnit(0),
28
+ pending: asset.currency.defaultUnit(0),
29
+ earned: asset.currency.defaultUnit(0),
30
+ accounts: {}, // stake accounts
21
31
  },
22
- isDelegating: false,
23
- locked: asset.currency.defaultUnit(0),
24
- withdrawable: asset.currency.defaultUnit(0),
25
- pending: asset.currency.defaultUnit(0),
26
- earned: asset.currency.defaultUnit(0),
27
- accounts: {}, // stake accounts
28
- },
29
- }
32
+ }
30
33
 
31
- static _tokens = [asset, ...tokens] // deprecated - will be removed
34
+ static _tokens = [asset, ...tokens] // deprecated - will be removed
32
35
 
33
- static _postParse(data) {
34
- const assets = assetsListToObject(assetsList)
35
- return {
36
- ...data,
37
- tokenBalances: reduce(
38
- data.tokenBalances,
39
- (r, tokenBalance, assetName) =>
40
- assets[assetName]
41
- ? Object.assign(r, { [assetName]: parseBalance(tokenBalance, assets[assetName]) })
42
- : r,
43
- {}
44
- ),
36
+ static _postParse(data) {
37
+ const assets = assetsListToObject(assetList)
38
+ return {
39
+ ...data,
40
+ tokenBalances: reduce(
41
+ data.tokenBalances,
42
+ (r, tokenBalance, assetName) =>
43
+ assets[assetName]
44
+ ? Object.assign(r, { [assetName]: parseBalance(tokenBalance, assets[assetName]) })
45
+ : r,
46
+ {}
47
+ ),
48
+ }
45
49
  }
46
50
  }
47
51
  }
package/src/api.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  SYSTEM_PROGRAM_ID,
10
10
  STAKE_PROGRAM_ID,
11
11
  TOKEN_PROGRAM_ID,
12
+ TOKEN_2022_PROGRAM_ID,
12
13
  SOL_DECIMAL,
13
14
  computeBalance,
14
15
  buildRawTransaction,
@@ -342,7 +343,9 @@ export class Api {
342
343
  .map((ix) => {
343
344
  const type = lodash.get(ix, 'parsed.type')
344
345
  const isTransferTx =
345
- ix.parsed && ix.program === 'spl-token' && ['transfer', 'transferChecked'].includes(type)
346
+ ix.parsed &&
347
+ ix.program === 'spl-token' &&
348
+ ['transfer', 'transferChecked', 'transferCheckedWithFee'].includes(type)
346
349
  const source = lodash.get(ix, 'parsed.info.source')
347
350
  const destination = lodash.get(ix, 'parsed.info.destination')
348
351
  const amount = Number(
@@ -449,7 +452,10 @@ export class Api {
449
452
  )
450
453
  const tokenTxs = lodash
451
454
  .filter(instructions, ({ program, type }) => {
452
- return program === 'spl-token' && ['transfer', 'transferChecked'].includes(type)
455
+ return (
456
+ program === 'spl-token' &&
457
+ ['transfer', 'transferChecked', 'transferCheckedWithFee'].includes(type)
458
+ )
453
459
  }) // get Token transfer: could have more than 1 instructions
454
460
  .map((ix) => {
455
461
  // add token details based on source/destination address
@@ -469,7 +475,7 @@ export class Api {
469
475
  token: tokenAccount,
470
476
  from: ix.source,
471
477
  to: ix.destination,
472
- amount: Number(ix.amount || lodash.get(ix, 'tokenAmount.amount', 0)), // supporting both types: transfer and transferChecked
478
+ amount: Number(ix.amount || lodash.get(ix, 'tokenAmount.amount', 0)), // supporting types: transfer, transferChecked, transferCheckedWithFee
473
479
  fee: isSending ? fee : 0, // in lamports
474
480
  }
475
481
  })
@@ -594,11 +600,21 @@ export class Api {
594
600
  }
595
601
 
596
602
  async getTokenAccountsByOwner(address, tokenTicker) {
597
- const { value: accountsList } = await this.rpcCall(
598
- 'getTokenAccountsByOwner',
599
- [address, { programId: TOKEN_PROGRAM_ID.toBase58() }, { encoding: 'jsonParsed' }],
600
- { address }
601
- )
603
+ const [{ value: standardTokenAccounts }, { value: token2022Accounts }] = await Promise.all([
604
+ this.rpcCall(
605
+ 'getTokenAccountsByOwner',
606
+ [address, { programId: TOKEN_PROGRAM_ID.toBase58() }, { encoding: 'jsonParsed' }],
607
+ { address }
608
+ ),
609
+ this.rpcCall(
610
+ 'getTokenAccountsByOwner',
611
+ [address, { programId: TOKEN_2022_PROGRAM_ID.toBase58() }, { encoding: 'jsonParsed' }],
612
+ { address }
613
+ ),
614
+ ])
615
+
616
+ // merge regular token and token2022 program tokens
617
+ const accountsList = [...standardTokenAccounts, ...token2022Accounts]
602
618
 
603
619
  const tokenAccounts = []
604
620
  for (const entry of accountsList) {
@@ -608,8 +624,15 @@ export class Api {
608
624
  const token = this.getTokenByAddress(mint) || {
609
625
  name: 'unknown',
610
626
  ticker: 'UNKNOWN',
627
+ decimals: 0,
611
628
  }
612
629
  const balance = lodash.get(account, 'data.parsed.info.tokenAmount.amount', '0')
630
+ const tokenProgram = lodash.get(account, 'owner', null) // TOKEN_PROGRAM_ID or TOKEN_2022_PROGRAM_ID
631
+ const { feeBasisPoints = 0, maximumFee = 0 } =
632
+ tokenProgram === TOKEN_2022_PROGRAM_ID.toBase58()
633
+ ? await this.getTokenFeeBasisPoints(mint)
634
+ : {}
635
+
613
636
  tokenAccounts.push({
614
637
  tokenAccountAddress: pubkey,
615
638
  owner: address,
@@ -617,6 +640,10 @@ export class Api {
617
640
  ticker: token.ticker,
618
641
  balance,
619
642
  mintAddress: mint,
643
+ tokenProgram,
644
+ decimals: token.decimals,
645
+ feeBasisPoints,
646
+ maximumFee,
620
647
  })
621
648
  }
622
649
 
@@ -674,7 +701,19 @@ export class Api {
674
701
 
675
702
  async isSpl(address) {
676
703
  const { owner } = await this.getAccountInfo(address)
677
- return owner === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
704
+ return [TOKEN_PROGRAM_ID.toBase58(), TOKEN_2022_PROGRAM_ID.toBase58()].includes(owner)
705
+ }
706
+
707
+ async getTokenFeeBasisPoints(address) {
708
+ // only for token-2022
709
+ const value = await this.getAccountInfo(address)
710
+ const { transferFeeBasisPoints, maximumFee } = lodash.get(
711
+ value,
712
+ 'data.parsed.info.extensions[0].state.newerTransferFee',
713
+ { transferFeeBasisPoints: 0, maximumFee: 0 }
714
+ )
715
+
716
+ return { feeBasisPoints: transferFeeBasisPoints, maximumFee }
678
717
  }
679
718
 
680
719
  async getMetaplexMetadata(tokenMintAddress) {
@@ -702,11 +741,10 @@ export class Api {
702
741
  lamports: value.lamports,
703
742
  }
704
743
 
705
- return account.owner === SYSTEM_PROGRAM_ID.toBase58()
706
- ? 'solana'
707
- : account.owner === TOKEN_PROGRAM_ID.toBase58()
708
- ? 'token'
709
- : null
744
+ if (account.owner === SYSTEM_PROGRAM_ID.toBase58()) return 'solana'
745
+ if (account.owner === TOKEN_PROGRAM_ID.toBase58()) return 'token'
746
+ if (account.owner === TOKEN_2022_PROGRAM_ID.toBase58()) return 'token-2022'
747
+ return null
710
748
  }
711
749
 
712
750
  async getTokenAddressOwner(address) {
@@ -722,7 +760,7 @@ export class Api {
722
760
 
723
761
  async isTokenAddress(address) {
724
762
  const type = await this.getAddressType(address)
725
- return type === 'token'
763
+ return ['token', 'token-2022'].includes(type)
726
764
  }
727
765
 
728
766
  async isSOLaddress(address) {
package/src/index.js CHANGED
@@ -6,7 +6,7 @@ import { Api } from './api'
6
6
 
7
7
  export { default as SolanaFeeMonitor } from './fee-monitor'
8
8
  export { SolanaMonitor } from './tx-log'
9
- export { SolanaAccountState } from './account-state'
9
+ export { createAccountState } from './account-state'
10
10
  export { getSolStakedFee, getStakingInfo, getUnstakingFee } from './staking-utils'
11
11
  export {
12
12
  isSolanaStaking,
@@ -116,7 +116,7 @@ export class SolanaMonitor extends BaseMonitor {
116
116
  )
117
117
  }
118
118
 
119
- async markStaleTransactions({ walletAccount, logItemsByAsset = {} }) {
119
+ async markStaleTransactions({ walletAccount, logItemsByAsset = Object.create(null) }) {
120
120
  // mark stale txs as dropped in logItemsByAsset
121
121
  const clearedLogItems = logItemsByAsset
122
122
  const tokenNames = [...this.api.tokens.values()].map(({ name }) => name)
package/src/tx-send.js CHANGED
@@ -1,4 +1,9 @@
1
- import { createUnsignedTx, findAssociatedTokenAddress } from '@exodus/solana-lib'
1
+ import {
2
+ createUnsignedTx,
3
+ findAssociatedTokenAddress,
4
+ TOKEN_PROGRAM_ID,
5
+ TOKEN_2022_PROGRAM_ID,
6
+ } from '@exodus/solana-lib'
2
7
  import assert from 'minimalistic-assert'
3
8
 
4
9
  export const createAndBroadcastTXFactory =
@@ -60,10 +65,20 @@ export const createAndBroadcastTXFactory =
60
65
 
61
66
  const recentBlockhash = options.recentBlockhash || (await api.getRecentBlockHash())
62
67
 
68
+ const feeData = await assetClientInterface.getFeeData({ assetName })
69
+
63
70
  let tokenParams = Object.create(null)
64
71
  if (isToken || customMintAddress) {
65
72
  const tokenMintAddress = customMintAddress || asset.mintAddress
66
- const tokenAddress = findAssociatedTokenAddress(address, tokenMintAddress)
73
+ const tokenProgram =
74
+ (await api.getAddressType(tokenMintAddress)) === 'token-2022'
75
+ ? TOKEN_2022_PROGRAM_ID
76
+ : TOKEN_PROGRAM_ID
77
+ const tokenAddress = findAssociatedTokenAddress(
78
+ address,
79
+ tokenMintAddress,
80
+ tokenProgram.toBase58()
81
+ )
67
82
  const [destinationAddressType, isAssociatedTokenAccountActive, fromTokenAccountAddresses] =
68
83
  await Promise.all([
69
84
  api.getAddressType(address),
@@ -81,6 +96,7 @@ export const createAndBroadcastTXFactory =
81
96
  isAssociatedTokenAccountActive,
82
97
  fromTokenAddresses,
83
98
  tokenStandard,
99
+ tokenProgram,
84
100
  }
85
101
  }
86
102
 
@@ -113,6 +129,7 @@ export const createAndBroadcastTXFactory =
113
129
  amount,
114
130
  fee: feeAmount,
115
131
  recentBlockhash,
132
+ feeData,
116
133
  reference,
117
134
  memo,
118
135
  ...tokenParams,