@exodus/solana-api 2.5.29 → 2.5.30

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.29",
3
+ "version": "2.5.30",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -34,5 +34,5 @@
34
34
  "devDependencies": {
35
35
  "@exodus/assets-testing": "file:../../../__testing__"
36
36
  },
37
- "gitHead": "7ec211abf9ccbc6b8bc84440f3bc6da659aebaaf"
37
+ "gitHead": "5270d4cf5f0a0ae9486533f237e6abe6f026ed16"
38
38
  }
@@ -1,21 +1,30 @@
1
+ import { TxSet } from '@exodus/models'
2
+
1
3
  // staking may be a feature that may not be available for a given wallet.
2
4
  // In this case, The wallet should exclude the staking balance from the general balance
3
5
 
4
- export const getBalancesFactory = ({ stakingFeatureAvailable }) => ({ asset, accountState }) => {
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
+ })
5
20
  if (asset.baseAsset.name !== asset.name) {
6
- return { balance: accountState?.tokenBalances?.[asset.name], spendableBalance: null }
21
+ return { balance, spendableBalance: balance }
7
22
  }
8
23
 
9
- const zero = asset.currency.ZERO
10
-
11
- const balance = accountState.balance || zero
12
-
13
- const { locked, withdrawable, pending } = accountState.mem || Object.create(null)
14
-
15
24
  const balanceWithoutStaking = balance
16
- .sub(locked || zero)
17
- .sub(withdrawable || zero)
18
- .sub(pending || zero)
25
+ .sub(locked)
26
+ .sub(withdrawable)
27
+ .sub(pending)
19
28
  .clampLowerZero()
20
29
 
21
30
  return {
@@ -23,3 +32,46 @@ export const getBalancesFactory = ({ stakingFeatureAvailable }) => ({ asset, acc
23
32
  spendableBalance: balanceWithoutStaking.sub(asset.accountReserve || zero).clampLowerZero(),
24
33
  }
25
34
  }
35
+
36
+ const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pending, asset }) => {
37
+ for (const tx of txLog) {
38
+ if ((tx.sent || tx.data.staking) && tx.pending && !tx.error) {
39
+ if (tx.coinAmount.unitType.equals(tx.feeAmount.unitType)) {
40
+ balance = balance.sub(tx.feeAmount)
41
+ }
42
+
43
+ if (!tx.data.staking) {
44
+ // coinAmount is negative for sent tx
45
+ balance = balance.sub(tx.coinAmount.abs())
46
+ } else {
47
+ // staking tx
48
+ switch (tx.data.staking?.method) {
49
+ case 'delegate':
50
+ locked = locked.add(tx.coinAmount.abs())
51
+ break
52
+ case 'withdraw':
53
+ withdrawable = asset.currency.ZERO
54
+ break
55
+ case 'undelegate':
56
+ pending = pending.add(locked)
57
+ locked = asset.currency.ZERO
58
+ break
59
+ }
60
+ }
61
+ }
62
+ }
63
+ return {
64
+ balance: balance.clampLowerZero(),
65
+ locked: locked.clampLowerZero(),
66
+ withdrawable: withdrawable.clampLowerZero(),
67
+ pending: pending.clampLowerZero(),
68
+ }
69
+ }
70
+
71
+ const getBalanceFromAccountState = ({ asset, accountState }) => {
72
+ const isBase = asset.name === asset.baseAsset.name
73
+ return (
74
+ (isBase ? accountState?.balance : accountState?.tokenBalances?.[asset.name]) ||
75
+ asset.currency.ZERO
76
+ )
77
+ }
package/src/tx-send.js CHANGED
@@ -10,7 +10,6 @@ export const createAndBroadcastTXFactory = (api) => async (
10
10
 
11
11
  const {
12
12
  feeAmount,
13
- shouldLog = true,
14
13
  method,
15
14
  stakeAddresses,
16
15
  seed,
@@ -33,21 +32,37 @@ export const createAndBroadcastTXFactory = (api) => async (
33
32
  reference,
34
33
  memo,
35
34
  } = options
36
- let { recentBlockhash } = options
37
35
  const { baseAsset } = asset
38
36
  const from = await assetClientInterface.getReceiveAddress({
39
37
  assetName: baseAsset.name,
40
38
  walletAccount,
41
39
  })
42
- const currentAccountState = await assetClientInterface.getAccountState({
43
- assetName: baseAsset.name,
44
- walletAccount,
45
- })
46
40
 
47
- recentBlockhash = recentBlockhash || (await api.getRecentBlockHash())
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
51
+ }
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
59
+ }
60
+ }
61
+
62
+ const recentBlockhash = options.recentBlockhash || (await api.getRecentBlockHash())
48
63
 
49
64
  let tokenParams = Object.create(null)
50
- if (asset.assetType === 'SOLANA_TOKEN' || customMintAddress) {
65
+ if (isToken || customMintAddress) {
51
66
  const tokenMintAddress = customMintAddress || asset.mintAddress
52
67
  const tokenAddress = findAssociatedTokenAddress(address, tokenMintAddress)
53
68
  const [
@@ -95,7 +110,7 @@ export const createAndBroadcastTXFactory = (api) => async (
95
110
  creators,
96
111
  }
97
112
 
98
- const unsignedTransaction = await createUnsignedTx({
113
+ const unsignedTransaction = createUnsignedTx({
99
114
  asset,
100
115
  from,
101
116
  to: address,
@@ -118,38 +133,33 @@ export const createAndBroadcastTXFactory = (api) => async (
118
133
  await baseAsset.api.broadcastTx(rawTx)
119
134
 
120
135
  const selfSend = from === address
121
- const coinAmount = selfSend ? asset.currency.ZERO : amount.abs().negate()
122
-
123
- if (shouldLog) {
124
- const tx = {
125
- txId,
126
- confirmations: 0,
127
- coinName: assetName,
128
- coinAmount,
129
- feeAmount,
130
- feeCoinName: asset.feeAsset.name,
131
- selfSend,
132
- to: address,
133
- data: {
134
- // note
135
- },
136
- currencies: { [assetName]: asset.currency, [asset.feeAsset.name]: asset.feeAsset.currency },
137
- }
138
- await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
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 },
139
155
  }
156
+ await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
140
157
 
141
- const changes = Object.create(null)
142
- if (asset.assetType === 'SOLANA_TOKEN') {
143
- Object.assign(changes, {
144
- balance: currentAccountState.balance.sub(feeAmount), // solana balance
145
- tokenBalances: {
146
- ...currentAccountState.tokenBalances,
147
- [assetName]: currentAccountState.tokenBalances[assetName].sub(coinAmount.abs()),
148
- }, // SPL token balance
149
- })
158
+ if (isToken) {
150
159
  // write tx entry in solana for token fee
151
- const tx = {
160
+ const txForFee = {
152
161
  txId,
162
+ confirmations: 0,
153
163
  coinName: baseAsset.name,
154
164
  coinAmount: baseAsset.currency.ZERO,
155
165
  tokens: [assetName],
@@ -162,45 +172,12 @@ export const createAndBroadcastTXFactory = (api) => async (
162
172
  [baseAsset.feeAsset.name]: baseAsset.feeAsset.currency,
163
173
  },
164
174
  }
165
- await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
166
- } else if (customMintAddress) {
167
- Object.assign(changes, {
168
- balance: currentAccountState.balance.sub(feeAmount), // solana balance
169
- })
170
- } else if (method) {
171
- // staking: no changes
172
- } else {
173
- // SOL transfer
174
- Object.assign(changes, {
175
- balance: currentAccountState.balance.sub(coinAmount.abs()).sub(feeAmount),
175
+ await assetClientInterface.updateTxLogAndNotify({
176
+ assetName: baseAsset.name,
177
+ walletAccount,
178
+ txs: [txForFee],
176
179
  })
177
180
  }
178
181
 
179
- if (method) {
180
- const stakingData = { ...currentAccountState.mem }
181
- switch (method) {
182
- case 'delegate':
183
- stakingData.isDelegating = true
184
- stakingData.locked = stakingData.locked.add(amount)
185
- break
186
- case 'undelegate':
187
- stakingData.isDelegating = false
188
- stakingData.pending = stakingData.pending.add(stakingData.locked)
189
- stakingData.locked = asset.currency.ZERO
190
- break
191
- case 'withdraw':
192
- stakingData.withdrawable = asset.currency.ZERO
193
- break
194
- }
195
-
196
- Object.assign(changes, { mem: stakingData })
197
- }
198
-
199
- await assetClientInterface.updateAccountState({
200
- assetName: baseAsset.name,
201
- walletAccount,
202
- newData: changes,
203
- })
204
-
205
182
  return { txId }
206
183
  }