@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 +13 -1
- package/package.json +2 -2
- package/src/get-balances.js +51 -12
- package/src/tx-log-staking-processor/asset-staking-tx-data.js +43 -17
- package/src/tx-log-staking-processor/index.js +5 -7
- package/src/tx-log-staking-processor/utils.js +2 -6
- package/src/tx-log-staking-processor/get-asset-tx-amount.js +0 -26
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
|
-
|
|
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.
|
|
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": "
|
|
69
|
+
"gitHead": "aeda2367cf089f8ac0c1a98fc104d04d995328b9"
|
|
70
70
|
}
|
package/src/get-balances.js
CHANGED
|
@@ -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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 {
|
|
10
|
+
import {
|
|
11
|
+
decodePolygonStakingTxInputAmount,
|
|
12
|
+
decodeEthLikeStakingTxInputAmount,
|
|
13
|
+
calculateRewardsFromStakeTx,
|
|
14
|
+
} from './utils.js'
|
|
11
15
|
|
|
12
|
-
const
|
|
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:
|
|
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
|
|
38
|
+
undelegatePending,
|
|
39
|
+
txAmount,
|
|
29
40
|
}
|
|
41
|
+
}
|
|
30
42
|
|
|
31
43
|
if (isEthereumUndelegate(tx)) {
|
|
32
|
-
|
|
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:
|
|
51
|
+
return { claimUndelegate: txAmount, txAmount }
|
|
39
52
|
}
|
|
40
53
|
}
|
|
41
54
|
|
|
42
|
-
const
|
|
43
|
-
if (tx.
|
|
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
|
|
47
|
-
|
|
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 (
|
|
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:
|
|
55
|
-
ethereum:
|
|
56
|
-
ethereumholesky:
|
|
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
|
|
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
|
|
15
|
-
if (
|
|
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:
|
|
20
|
-
data: { ...tx.data, ...
|
|
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
|
|
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
|
-
|
|
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
|
-
}
|