@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 +3 -3
- package/src/account-state.js +37 -33
- package/src/api.js +53 -15
- package/src/index.js +1 -1
- package/src/tx-log/solana-monitor.js +1 -1
- package/src/tx-send.js +19 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-api",
|
|
3
|
-
"version": "
|
|
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.
|
|
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": "
|
|
47
|
+
"gitHead": "84f77ed9720dcdabb8057ed9170f45fcb4f9eff9"
|
|
48
48
|
}
|
package/src/account-state.js
CHANGED
|
@@ -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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
+
static _tokens = [asset, ...tokens] // deprecated - will be removed
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 &&
|
|
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
|
|
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
|
|
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:
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
|
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,
|