@exodus/ethereum-api 8.9.1 → 8.9.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/CHANGELOG.md CHANGED
@@ -3,11 +3,23 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [8.9.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.9.1...@exodus/ethereum-api@8.9.2) (2024-07-03)
7
+
8
+ ### Code Refactoring
9
+
10
+ * **ethereum:** EVM balances ([#2598](https://github.com/ExodusMovement/assets/issues/2598)) ([fb6b937](https://github.com/ExodusMovement/assets/commit/fb6b9375675494592b42a9c7ce1ccfa505bc8946))
11
+
12
+
6
13
  ## [8.9.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.9.0...@exodus/ethereum-api@8.9.1) (2024-07-03)
7
14
 
8
- **Note:** Version bump only for package @exodus/ethereum-api
15
+ ### Features
16
+
17
+ * patterns for filtering sensitive hint in EthLikeError ([#2721](https://github.com/ExodusMovement/assets/issues/2721)) ([7de766c](https://github.com/ExodusMovement/assets/commit/7de766c20a85168bb11117234ceab4475b26af10))
9
18
 
10
19
 
20
+ ### Bug Fixes
21
+
22
+ * update SOL staking info on balance change ([#2672](https://github.com/ExodusMovement/assets/issues/2672)) ([bc2043c](https://github.com/ExodusMovement/assets/commit/bc2043ce226128d3e321937a325e20382f12f874))
11
23
 
12
24
 
13
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.9.1",
3
+ "version": "8.9.2",
4
4
  "description": "Ethereum Api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -66,5 +66,5 @@
66
66
  "type": "git",
67
67
  "url": "git+https://github.com/ExodusMovement/assets.git"
68
68
  },
69
- "gitHead": "d52d315260d32e7a2fce15e32e7393813a02bc90"
69
+ "gitHead": "aeda2367cf089f8ac0c1a98fc104d04d995328b9"
70
70
  }
@@ -24,6 +24,38 @@ const getUnstaked = ({ accountState, asset }) => {
24
24
  )
25
25
  }
26
26
 
27
+ /**
28
+ * Calculates amount received in staking txs from txLog
29
+ * Staking txs are counted based in the amount received in the tx (tx.coinAmount)
30
+ * It can be different from staked / unstaked / claimed requested amount)
31
+ *
32
+ * Unstaked tx.coinAmount - Staked tx.coinAmount + Claimed tx.coinAmount
33
+ *
34
+ * Depending on the asset, is what tx.coinAmount is:
35
+ *
36
+ * Ethereum: for unstake txs, it's unknown how much of it are rewards and unstaked received (full or partial)
37
+ * Polygon: rewards are transferred in stake and unstake txs
38
+ */
39
+ export const getStakingHistoryBalance = ({ asset, txLog }) => {
40
+ let stakingHistoryBalance = asset.currency.ZERO
41
+ for (const tx of txLog) {
42
+ const successfulTx = tx.confirmations && !tx.failed
43
+ if (successfulTx && tx.data?.txAmount) {
44
+ // only staking txs have tx.data.txAmount set
45
+ // tx.data.txAmount is negative for stake tx type
46
+ const txAmount = asset.currency.defaultUnit(tx.data.txAmount)
47
+ stakingHistoryBalance = stakingHistoryBalance.add(txAmount)
48
+ }
49
+ }
50
+
51
+ return stakingHistoryBalance
52
+ }
53
+
54
+ const getSpendable = ({ asset, balance, txLog, unconfirmedReceived }) => {
55
+ const stakingHistoryBalance = getStakingHistoryBalance({ asset, txLog })
56
+ return balance.add(stakingHistoryBalance).sub(unconfirmedReceived)
57
+ }
58
+
27
59
  /**
28
60
  * Api method to return the balance based on either account state balances or tx history.
29
61
  *
@@ -38,21 +70,28 @@ export const getBalancesFactory = ({ monitorType }) => {
38
70
  const unconfirmedReceived = getUnconfirmedReceivedBalance({ asset, txLog })
39
71
  const unconfirmedSent = getUnconfirmedSentBalance({ asset, txLog })
40
72
 
41
- // balance from txLog / rpc is considered to be total
42
- const balanceWithoutUnconfirmedSent =
43
- monitorType === 'no-history' || isRpcBalanceAsset(asset)
44
- ? getBalanceFromAccountState({ asset, accountState }).sub(unconfirmedSent)
45
- : getBalanceFromTxLog({ txLog, asset })
46
-
47
73
  const staked = getStaked({ asset, accountState })
48
74
  const unstaking = getUnstaking({ asset, accountState })
49
75
  const unstaked = getUnstaked({ asset, accountState })
50
- const total = balanceWithoutUnconfirmedSent
51
- const spendable = balanceWithoutUnconfirmedSent
52
- .sub(unconfirmedReceived)
53
- .sub(staked)
54
- .sub(unstaking)
55
- .sub(unstaked)
76
+
77
+ let total
78
+ let spendable
79
+
80
+ // Balance from accountState is considered total b/c is fetched from rpc
81
+ if (isRpcBalanceAsset(asset) || monitorType === 'no-history') {
82
+ total = getBalanceFromAccountState({ asset, accountState }).sub(unconfirmedSent)
83
+ spendable = total.sub(staked).sub(unstaking).sub(unstaked).sub(unconfirmedReceived)
84
+ } else {
85
+ // Balance from txLog does not include staking rewards
86
+ // spendable and total are calculated differently based on staking txs
87
+ spendable = getSpendable({
88
+ asset,
89
+ balance: getBalanceFromTxLog({ txLog, asset }),
90
+ txLog,
91
+ unconfirmedReceived,
92
+ })
93
+ total = spendable.add(unconfirmedReceived).add(staked).add(unstaking).add(unstaked)
94
+ }
56
95
 
57
96
  return {
58
97
  // new
@@ -5,53 +5,79 @@ import {
5
5
  isEthereumUndelegatePending,
6
6
  } from '../staking/ethereum/staking-utils'
7
7
 
8
- import { isPolygonClaimUndelegate, isPolygonDelegate } from '../staking/matic'
8
+ import { isPolygonClaimUndelegate, isPolygonDelegate, isPolygonUndelegate } from '../staking/matic'
9
9
 
10
- import { decodePolygonStakingTxInputAmount } from './utils.js'
10
+ import {
11
+ decodePolygonStakingTxInputAmount,
12
+ decodeEthLikeStakingTxInputAmount,
13
+ calculateRewardsFromStakeTx,
14
+ } from './utils.js'
11
15
 
12
- const getEthereumStakeTxType = ({ tx }) => {
16
+ const getEthereumStakingTxData = ({ tx, currency }) => {
13
17
  if (
14
18
  ['delegate', 'undelegatePending', 'undelegate', 'claimUndelegate'].some(
15
19
  (stakeTx) => tx.data?.[stakeTx]
16
- )
20
+ ) &&
21
+ tx.coinAmount.isZero
17
22
  )
18
23
  return
19
24
 
25
+ const txAmount = tx.coinAmount.toDefaultString()
26
+
20
27
  if (isEthereumDelegate(tx)) {
21
- return { delegate: tx.coinAmount.toDefaultString() }
28
+ return { delegate: txAmount, txAmount }
22
29
  }
23
30
 
24
31
  // undelegate must be taken in consideration, if unstaked ETH is still
25
32
  // in the pool queue, undelgate transfers staked funds back inmediatly to the user
26
- if (isEthereumUndelegatePending(tx))
33
+ if (isEthereumUndelegatePending(tx)) {
34
+ const undelegatePending = currency
35
+ .baseUnit(decodeEthLikeStakingTxInputAmount(tx))
36
+ .toDefaultString()
27
37
  return {
28
- undelegatePending: tx.coinAmount.toDefaultString(),
38
+ undelegatePending,
39
+ txAmount,
29
40
  }
41
+ }
30
42
 
31
43
  if (isEthereumUndelegate(tx)) {
32
- return { undelegate: tx.coinAmount.toDefaultString() }
44
+ const undelegate = currency.baseUnit(decodeEthLikeStakingTxInputAmount(tx)).toDefaultString()
45
+ return { undelegate, txAmount }
33
46
  }
34
47
 
35
48
  // In the case of the ETH being actually staked and earning,
36
49
  // unstake has a withdraw period, after that, unstaked can be claimed.
37
50
  if (isEthereumClaimUndelegate(tx)) {
38
- return { claimUndelegate: tx.coinAmount.toDefaultString() }
51
+ return { claimUndelegate: txAmount, txAmount }
39
52
  }
40
53
  }
41
54
 
42
- const getPolygonStakeTxType = ({ tx, currency }) => {
43
- if (tx.coinAmount.isZero) return
55
+ const getPolygonStakingTxData = ({ tx, currency }) => {
56
+ if (['delegate', 'undelegate', 'claimUndelegate'].some((stakeTx) => tx.data?.[stakeTx])) return
57
+
58
+ const txAmount = tx.coinAmount.toDefaultString()
44
59
 
45
60
  if (isPolygonDelegate(tx)) {
46
- const stakeTxAmount = currency.baseUnit(decodePolygonStakingTxInputAmount(tx)).toDefaultString()
47
- return { delegate: stakeTxAmount }
61
+ const delegate = currency.baseUnit(decodePolygonStakingTxInputAmount(tx)).toDefaultString()
62
+ // MATIC returned in unstake tx is always reward
63
+ const rewards = calculateRewardsFromStakeTx({ tx, currency })
64
+ return { delegate, txAmount, ...(rewards ? { rewards } : {}) }
48
65
  }
49
66
 
50
- if (isPolygonClaimUndelegate(tx)) return { undelegate: tx.coinAmount.toDefaultString() }
67
+ if (isPolygonUndelegate(tx)) {
68
+ const undelegate = currency.baseUnit(decodePolygonStakingTxInputAmount(tx)).toDefaultString()
69
+ // MATIC returned in unstake tx is always reward
70
+ const rewards = txAmount
71
+ return { undelegate, txAmount, rewards }
72
+ }
73
+
74
+ if (isPolygonClaimUndelegate(tx)) {
75
+ return { claimUndelegate: txAmount, txAmount }
76
+ }
51
77
  }
52
78
 
53
79
  export const assetStakingTxData = {
54
- polygon: getPolygonStakeTxType,
55
- ethereum: getEthereumStakeTxType,
56
- ethereumholesky: getEthereumStakeTxType,
80
+ polygon: getPolygonStakingTxData,
81
+ ethereum: getEthereumStakingTxData,
82
+ ethereumholesky: getEthereumStakingTxData,
57
83
  }
@@ -1,7 +1,6 @@
1
1
  import { assetStakingTxData } from './asset-staking-tx-data'
2
- import { getStakeTxAmount } from './get-asset-tx-amount'
3
2
 
4
- const getTxStakeData = ({ assetName, currency, tx }) => {
3
+ const getTxStakingData = ({ assetName, currency, tx }) => {
5
4
  return assetStakingTxData[assetName]({ tx, currency })
6
5
  }
7
6
 
@@ -11,13 +10,12 @@ const processTxLog = async ({ asset, assetClientInterface: aci, walletAccount, b
11
10
 
12
11
  const newTxs = []
13
12
  for (const tx of txs) {
14
- const txStakeData = getTxStakeData({ assetName, currency, tx })
15
- if (txStakeData) {
16
- const txAmount = getStakeTxAmount[assetName]({ tx, currency, type: txStakeData })
13
+ const stakingData = getTxStakingData({ assetName, currency, tx })
14
+ if (stakingData) {
17
15
  newTxs.push({
18
16
  ...tx,
19
- coinAmount: txAmount,
20
- data: { ...tx.data, ...txStakeData },
17
+ coinAmount: currency.ZERO,
18
+ data: { ...tx.data, ...stakingData },
21
19
  })
22
20
  }
23
21
  }
@@ -18,7 +18,7 @@ export const decodePolygonStakingTxInputAmount = (tx) => {
18
18
  return amount
19
19
  }
20
20
 
21
- export const calculateTxAmountAndRewardFromStakeTx = ({ tx, currency }) => {
21
+ export const calculateRewardsFromStakeTx = ({ tx, currency }) => {
22
22
  const stakedAmount = currency.baseUnit(decodePolygonStakingTxInputAmount(tx))
23
23
  const { reward } = tx.data
24
24
  // stake tx might have rewards in it,
@@ -35,12 +35,8 @@ export const calculateTxAmountAndRewardFromStakeTx = ({ tx, currency }) => {
35
35
  if (stakeTxContainsReward) {
36
36
  const txAmount = stakedAmount.sub(tx.coinAmount.abs()).abs()
37
37
  // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only -- TODO: Fix this the next time the file is edited.
38
- tx.data.reward = txAmount.toBaseString()
39
- return txAmount
38
+ return txAmount.toBaseString()
40
39
  }
41
-
42
- // no rewards, set stake tx amount to ZERO
43
- return currency.ZERO
44
40
  }
45
41
 
46
42
  export const decodeEthLikeStakingTxInputAmount = (tx) => {
@@ -1,26 +0,0 @@
1
- import { calculateTxAmountAndRewardFromStakeTx } from './utils.js'
2
-
3
- const getPolygonTxAmount = ({ currency, tx, type }) => {
4
- if ('delegate' in type) {
5
- return calculateTxAmountAndRewardFromStakeTx({ tx, currency })
6
- }
7
-
8
- return currency.ZERO
9
- }
10
-
11
- const getEthereumTxAmount = ({ currency, tx, type }) => {
12
- const isStakingTx = ['delegate', 'undelegatePending', 'undelegate', 'claimUndelegate'].some(
13
- (stakingType) => type[stakingType]
14
- )
15
- if (isStakingTx) {
16
- return currency.ZERO
17
- }
18
-
19
- return tx.coinAmount
20
- }
21
-
22
- export const getStakeTxAmount = {
23
- polygon: getPolygonTxAmount,
24
- ethereum: getEthereumTxAmount,
25
- ethereumholesky: getEthereumTxAmount,
26
- }