@exodus/ethereum-api 7.2.1 → 7.3.1
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 +9 -4
- package/src/address-has-history.js +6 -0
- package/src/create-asset.js +216 -0
- package/src/create-token-factory.js +72 -0
- package/src/eth-like-util.js +2 -3
- package/src/exodus-eth-server/api-coin-nodes.js +2 -3
- package/src/exodus-eth-server/api.js +2 -3
- package/src/exodus-eth-server/clarity.js +6 -4
- package/src/get-balances.js +35 -13
- package/src/get-fee.js +97 -0
- package/src/hooks/index.js +1 -0
- package/src/hooks/monitor.js +88 -0
- package/src/index.js +3 -0
- package/src/number-utils.js +8 -0
- package/src/optimism-gas/index.js +4 -4
- package/src/staking/ethereum/service.js +4 -4
- package/src/staking-api.js +5 -0
- package/src/tx-log/clarity-monitor.js +4 -9
- package/src/tx-log/ethereum-monitor.js +2 -3
- package/src/tx-log/ethereum-no-history-monitor.js +2 -3
- package/src/tx-log-staking-processor/asset-staking-tx-data.js +48 -0
- package/src/tx-log-staking-processor/derive-txs.js +59 -0
- package/src/tx-log-staking-processor/get-asset-tx-amount.js +26 -0
- package/src/tx-log-staking-processor/index.js +36 -0
- package/src/tx-log-staking-processor/utils.js +70 -0
- package/src/tx-send/get-fee-info.js +41 -0
- package/src/tx-send/index.js +2 -0
- package/src/tx-send/tx-send.js +300 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.1",
|
|
4
4
|
"description": "Ethereum Api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -20,12 +20,17 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@exodus/asset-lib": "^4.1.0",
|
|
23
|
+
"@exodus/assets": "^9.1.1",
|
|
24
|
+
"@exodus/basic-utils": "^2.1.0",
|
|
25
|
+
"@exodus/bip44-constants": "^195.0.0",
|
|
23
26
|
"@exodus/crypto": "^1.0.0-rc.0",
|
|
24
27
|
"@exodus/currency": "^2.1.3",
|
|
25
28
|
"@exodus/ethereum-lib": "^4.2.7",
|
|
26
29
|
"@exodus/ethereum-meta": "^1.2.0",
|
|
30
|
+
"@exodus/ethereumholesky-meta": "^1.0.1",
|
|
27
31
|
"@exodus/ethereumjs-util": "^7.1.0-exodus.7",
|
|
28
|
-
"@exodus/fetch": "^1.3.0
|
|
32
|
+
"@exodus/fetch": "^1.3.0",
|
|
33
|
+
"@exodus/key-utils": "^3.1.0",
|
|
29
34
|
"@exodus/models": "^11.0.0",
|
|
30
35
|
"@exodus/simple-retry": "^0.0.6",
|
|
31
36
|
"@exodus/solidity-contract": "^1.1.3",
|
|
@@ -46,11 +51,11 @@
|
|
|
46
51
|
"@exodus/bsc-meta": "^1.1.2",
|
|
47
52
|
"@exodus/ethereumarbone-meta": "^1.1.5",
|
|
48
53
|
"@exodus/ethereumgoerli-meta": "^1.0.3",
|
|
54
|
+
"@exodus/ethereumsepolia-meta": "^1.0.1",
|
|
49
55
|
"@exodus/fantommainnet-meta": "^1.0.5",
|
|
50
56
|
"@exodus/rootstock-meta": "^1.0.5",
|
|
51
|
-
"bignumber.js": "9.0.1",
|
|
52
57
|
"cross-fetch": "^3.1.5",
|
|
53
58
|
"delay": "4.0.1"
|
|
54
59
|
},
|
|
55
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "c09304326cb0fa6a3acd22d87c38553e05e43569"
|
|
56
61
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { connectAssetsList } from '@exodus/assets'
|
|
2
|
+
import { pick } from '@exodus/basic-utils'
|
|
3
|
+
import bip44Constants from '@exodus/bip44-constants/by-ticker'
|
|
4
|
+
import { getBalances } from './get-balances'
|
|
5
|
+
import { ClarityMonitor } from './tx-log/clarity-monitor'
|
|
6
|
+
import { EthereumNoHistoryMonitor } from './tx-log/ethereum-no-history-monitor'
|
|
7
|
+
import { EthereumMonitor } from './tx-log/ethereum-monitor'
|
|
8
|
+
|
|
9
|
+
import { getServer } from './exodus-eth-server'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
CONFIRMATIONS_NUMBER,
|
|
13
|
+
createEthereumLikeAccountState,
|
|
14
|
+
createGetKeyIdentifier as defaultCreateGetKeyIdentifier,
|
|
15
|
+
createUnsignedTx,
|
|
16
|
+
encodePrivate,
|
|
17
|
+
encodePublic,
|
|
18
|
+
hasChecksum,
|
|
19
|
+
parseUnsignedTx,
|
|
20
|
+
signUnsignedTx,
|
|
21
|
+
signUnsignedTxWithSigner,
|
|
22
|
+
validate,
|
|
23
|
+
signMessage,
|
|
24
|
+
signHardwareFactory,
|
|
25
|
+
DEFAULT_FEE_MONITOR_INTERVAL,
|
|
26
|
+
} from '@exodus/ethereum-lib'
|
|
27
|
+
import { ETHEREUM_LIKE_MONITOR_TYPES } from '@exodus/ethereum-lib/src/constants'
|
|
28
|
+
import assert from 'minimalistic-assert'
|
|
29
|
+
import ms from 'ms'
|
|
30
|
+
|
|
31
|
+
import { addressHasHistoryFactory } from './address-has-history'
|
|
32
|
+
import { createTokenFactory } from './create-token-factory'
|
|
33
|
+
import { getEffectiveGasPrice, getFeeFactory } from './get-fee'
|
|
34
|
+
import { createEthereumHooks } from './hooks'
|
|
35
|
+
import { createStakingApi } from './staking-api'
|
|
36
|
+
import { txSendFactory } from './tx-send'
|
|
37
|
+
import { signMessageWithSigner } from '@exodus/ethereum-lib/src/sign-message'
|
|
38
|
+
import { serverBasedFeeMonitorFactoryFactory } from './fee-monitor'
|
|
39
|
+
|
|
40
|
+
export const createAssetFactory = ({
|
|
41
|
+
assetsList,
|
|
42
|
+
feeData,
|
|
43
|
+
AccountState: CustomAccountState,
|
|
44
|
+
monitorInterval,
|
|
45
|
+
nfts = false,
|
|
46
|
+
getFee: customGetFee,
|
|
47
|
+
useEip1191ChainIdChecksum = false,
|
|
48
|
+
customTokens = true,
|
|
49
|
+
isTestnet = false,
|
|
50
|
+
isMaxFeeAsset = false,
|
|
51
|
+
erc20FuelBuffer,
|
|
52
|
+
fuelThreshold,
|
|
53
|
+
customBip44,
|
|
54
|
+
customCreateGetKeyIdentifier,
|
|
55
|
+
feeMonitorInterval,
|
|
56
|
+
}) => {
|
|
57
|
+
assert(assetsList, 'assetsList is required')
|
|
58
|
+
assert(feeData, 'feeData is required')
|
|
59
|
+
|
|
60
|
+
const base = assetsList.find((asset) => asset.name === asset.baseAssetName)
|
|
61
|
+
assert(base, 'base is required')
|
|
62
|
+
|
|
63
|
+
const createGetKeyIdentifier = customCreateGetKeyIdentifier || defaultCreateGetKeyIdentifier
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
{
|
|
67
|
+
assetClientInterface,
|
|
68
|
+
config = {
|
|
69
|
+
allowMetaMaskCompat: false,
|
|
70
|
+
supportsStaking: false,
|
|
71
|
+
},
|
|
72
|
+
overrideCallback = ({ asset }) => asset,
|
|
73
|
+
} = Object.create(null)
|
|
74
|
+
) => {
|
|
75
|
+
const assets = connectAssetsList(assetsList)
|
|
76
|
+
|
|
77
|
+
const { allowMetaMaskCompat, supportsStaking } = config
|
|
78
|
+
|
|
79
|
+
const asset = assets[base.name]
|
|
80
|
+
|
|
81
|
+
const server = getServer(asset)
|
|
82
|
+
|
|
83
|
+
const gasLimit = 21e3 // 21 KGas, enough only for sending ether to normal address
|
|
84
|
+
|
|
85
|
+
const contractGasLimit = 1e6 // used when estimateGas fail
|
|
86
|
+
|
|
87
|
+
const address = {
|
|
88
|
+
validate: useEip1191ChainIdChecksum
|
|
89
|
+
? (address) => validate(address, { baseAssetName: asset.name })
|
|
90
|
+
: validate,
|
|
91
|
+
hasChecksum,
|
|
92
|
+
isContract: server.isContract,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const bip44 = customBip44 || bip44Constants['ETH']
|
|
96
|
+
|
|
97
|
+
const keys = {
|
|
98
|
+
encodePrivate,
|
|
99
|
+
encodePublic: useEip1191ChainIdChecksum
|
|
100
|
+
? (pubKey) => encodePublic(pubKey, { baseAssetName: asset.name })
|
|
101
|
+
: encodePublic,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { createToken, getTokens } = createTokenFactory({ address, bip44, keys }, assets)
|
|
105
|
+
|
|
106
|
+
const addressHasHistory = addressHasHistoryFactory({ server })
|
|
107
|
+
|
|
108
|
+
const monitorType = ETHEREUM_LIKE_MONITOR_TYPES[asset.name]
|
|
109
|
+
|
|
110
|
+
assert(monitorType, `ETHEREUM_LIKE_MONITOR_TYPES for asset ${asset.name} is not defined`)
|
|
111
|
+
|
|
112
|
+
const feeMonitor = monitorType === 'no-history'
|
|
113
|
+
|
|
114
|
+
const createFeeMonitor = serverBasedFeeMonitorFactoryFactory({
|
|
115
|
+
assetName: base.name,
|
|
116
|
+
interval: config.feeMonitorInterval || feeMonitorInterval || DEFAULT_FEE_MONITOR_INTERVAL,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const features = {
|
|
120
|
+
accountState: true,
|
|
121
|
+
customTokens,
|
|
122
|
+
feeMonitor,
|
|
123
|
+
feesApi: true,
|
|
124
|
+
isMaxFeeAsset,
|
|
125
|
+
isTestnet,
|
|
126
|
+
nfts,
|
|
127
|
+
noHistory: monitorType === 'no-history',
|
|
128
|
+
signWithSigner: true,
|
|
129
|
+
...(supportsStaking && { staking: {} }),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const confirmationNumber = CONFIRMATIONS_NUMBER[asset.name] || 1
|
|
133
|
+
|
|
134
|
+
const accountStateClass =
|
|
135
|
+
CustomAccountState || createEthereumLikeAccountState({ asset: base, assets })
|
|
136
|
+
|
|
137
|
+
const createHistoryMonitor = (args) => {
|
|
138
|
+
let monitor
|
|
139
|
+
switch (monitorType) {
|
|
140
|
+
case 'clarity':
|
|
141
|
+
monitor = new ClarityMonitor({ interval: ms(monitorInterval || '5m'), server, ...args })
|
|
142
|
+
break
|
|
143
|
+
case 'no-history':
|
|
144
|
+
monitor = new EthereumNoHistoryMonitor({
|
|
145
|
+
interval: ms(monitorInterval || '15s'),
|
|
146
|
+
server,
|
|
147
|
+
...args,
|
|
148
|
+
})
|
|
149
|
+
break
|
|
150
|
+
case 'magnifier':
|
|
151
|
+
monitor = new EthereumMonitor({ interval: ms(monitorInterval || '15s'), server, ...args })
|
|
152
|
+
break
|
|
153
|
+
default:
|
|
154
|
+
throw new Error(`Monitor type ${monitorType} of evm asset ${asset.name} is unknown`)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (supportsStaking) {
|
|
158
|
+
const afterTickHook = createEthereumHooks({ assetClientInterface })['after-tick']
|
|
159
|
+
monitor.addHook('after-tick', afterTickHook)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return monitor
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const defaultAddressPath = 'm/0/0'
|
|
166
|
+
|
|
167
|
+
const sendTx = txSendFactory({ assetClientInterface })
|
|
168
|
+
const api = {
|
|
169
|
+
addressHasHistory,
|
|
170
|
+
broadcastTx: (...args) => server.sendRawTransaction(...args),
|
|
171
|
+
createAccountState: () => accountStateClass,
|
|
172
|
+
createFeeMonitor,
|
|
173
|
+
createHistoryMonitor,
|
|
174
|
+
createToken,
|
|
175
|
+
createUnsignedTx,
|
|
176
|
+
defaultAddressPath,
|
|
177
|
+
features,
|
|
178
|
+
getBalances,
|
|
179
|
+
getConfirmationsNumber: () => confirmationNumber,
|
|
180
|
+
getDefaultAddressPath: () => defaultAddressPath,
|
|
181
|
+
getFee: customGetFee || getFeeFactory({ gasLimit }),
|
|
182
|
+
getFeeData: () => feeData,
|
|
183
|
+
getKeyIdentifier: createGetKeyIdentifier({ bip44, allowMetaMaskCompat }),
|
|
184
|
+
getSupportedPurposes: () => [44],
|
|
185
|
+
getTokens,
|
|
186
|
+
hasFeature: (feature) => !!features[feature], // @deprecated use api.features instead
|
|
187
|
+
parseUnsignedTx: (unsignedTx, { asset }) => parseUnsignedTx(asset, unsignedTx),
|
|
188
|
+
sendTx,
|
|
189
|
+
signTx: ({ unsignedTx, privateKey, signer }) =>
|
|
190
|
+
signer
|
|
191
|
+
? signUnsignedTxWithSigner(unsignedTx, signer)
|
|
192
|
+
: signUnsignedTx(unsignedTx, privateKey),
|
|
193
|
+
signUnsignedTx,
|
|
194
|
+
signHardware: signHardwareFactory({ baseAssetName: asset.name }),
|
|
195
|
+
signMessage: ({ message, privateKey, signer }) =>
|
|
196
|
+
signer ? signMessageWithSigner({ message, signer }) : signMessage({ privateKey, message }),
|
|
197
|
+
...(supportsStaking && { staking: createStakingApi({ network: asset.name }) }),
|
|
198
|
+
validateAssetId: address.validate,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const fullAsset = {
|
|
202
|
+
...asset,
|
|
203
|
+
...pick(config, ['accountReserve']),
|
|
204
|
+
gasLimit,
|
|
205
|
+
contractGasLimit,
|
|
206
|
+
bip44,
|
|
207
|
+
keys,
|
|
208
|
+
address,
|
|
209
|
+
api,
|
|
210
|
+
...(erc20FuelBuffer && { erc20FuelBuffer }),
|
|
211
|
+
...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
|
|
212
|
+
getEffectiveGasPrice,
|
|
213
|
+
}
|
|
214
|
+
return overrideCallback({ asset: fullAsset })
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isPolygonClaimUndelegate, isPolygonDelegate, isPolygonUndelegate } from './staking/matic'
|
|
2
|
+
import { createContract } from '@exodus/ethereum-lib'
|
|
3
|
+
|
|
4
|
+
import { getBalances } from './get-balances'
|
|
5
|
+
|
|
6
|
+
import { createStakingApi } from './staking-api'
|
|
7
|
+
|
|
8
|
+
const defaultTokenFeatures = {
|
|
9
|
+
isMaxFeeAsset: true,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Hide on both deposit and withdraw sides
|
|
13
|
+
const smallbalanceTx = (tx) => Math.abs(tx.coinAmount.toBaseNumber()) === 0
|
|
14
|
+
|
|
15
|
+
const getTxLogFilter = () => (tx) => !smallbalanceTx(tx)
|
|
16
|
+
|
|
17
|
+
const getPolygonTxLogFilter = () => (tx) =>
|
|
18
|
+
isPolygonDelegate(tx) ||
|
|
19
|
+
isPolygonClaimUndelegate(tx) ||
|
|
20
|
+
isPolygonUndelegate(tx) ||
|
|
21
|
+
getTxLogFilter()(tx)
|
|
22
|
+
const getPolygonActivityTxs = ({ txs }) =>
|
|
23
|
+
txs.filter(
|
|
24
|
+
(tx) =>
|
|
25
|
+
isPolygonDelegate(tx) ||
|
|
26
|
+
isPolygonClaimUndelegate(tx) ||
|
|
27
|
+
isPolygonUndelegate(tx) ||
|
|
28
|
+
!smallbalanceTx(tx)
|
|
29
|
+
)
|
|
30
|
+
const getActivityTxs = ({ txs }) => txs.filter((tx) => !smallbalanceTx(tx))
|
|
31
|
+
|
|
32
|
+
const getCreateBaseToken =
|
|
33
|
+
(props) =>
|
|
34
|
+
({ name, contract, features, ...tokenDef }) => {
|
|
35
|
+
const tokenSpecificFeatures = name === 'polygon' ? { staking: {} } : {}
|
|
36
|
+
const tokenSpecificApiFunctions =
|
|
37
|
+
name === 'polygon' ? { staking: createStakingApi({ network: name }) } : {}
|
|
38
|
+
|
|
39
|
+
const tokenFeatures = { ...defaultTokenFeatures, ...features, ...tokenSpecificFeatures }
|
|
40
|
+
return {
|
|
41
|
+
...tokenDef,
|
|
42
|
+
addresses: contract,
|
|
43
|
+
assetId: contract.current,
|
|
44
|
+
contract: createContract(contract.current, name),
|
|
45
|
+
gasLimit: 120_000,
|
|
46
|
+
name,
|
|
47
|
+
api: {
|
|
48
|
+
getActivityTxs: name === 'polygon' ? getPolygonActivityTxs : getActivityTxs,
|
|
49
|
+
features: tokenFeatures,
|
|
50
|
+
hasFeature: (feature) => !!tokenFeatures[feature], // @deprecated use api.features instead
|
|
51
|
+
getBalances,
|
|
52
|
+
getTxLogFilter: name === 'polygon' ? getPolygonTxLogFilter : getTxLogFilter,
|
|
53
|
+
...tokenSpecificApiFunctions,
|
|
54
|
+
},
|
|
55
|
+
...props, // override props above, add new props
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const createTokenFactory = (props, assets) => {
|
|
60
|
+
const createBaseToken = getCreateBaseToken(props)
|
|
61
|
+
const createCustomToken = ({ assetId, assetName, ...rest }) =>
|
|
62
|
+
createBaseToken({ ...rest, name: assetName, contract: { current: assetId } })
|
|
63
|
+
const createToken = (tokenDef) =>
|
|
64
|
+
tokenDef.isBuiltIn ? createBaseToken(tokenDef) : createCustomToken(tokenDef)
|
|
65
|
+
return {
|
|
66
|
+
createToken,
|
|
67
|
+
getTokens: () =>
|
|
68
|
+
Object.values(assets)
|
|
69
|
+
.filter((asset) => asset.name !== asset.baseAsset.name) // eslint-disable-next-line unicorn/no-array-callback-reference -- TODO: Fix this the next time the file is edited.
|
|
70
|
+
.map(createToken),
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/eth-like-util.js
CHANGED
|
@@ -3,7 +3,7 @@ import { normalizeTxId, isEthereumLikeAsset, isEthereumLikeToken, ABI } from '@e
|
|
|
3
3
|
import { memoizeLruCache } from '@exodus/asset-lib'
|
|
4
4
|
import SolidityContract from '@exodus/solidity-contract'
|
|
5
5
|
import { getServerByName, getServer } from './exodus-eth-server'
|
|
6
|
-
import
|
|
6
|
+
import { fromHexToString } from './number-utils'
|
|
7
7
|
|
|
8
8
|
export async function isContract(baseAssetName, address) {
|
|
9
9
|
return getServerByName(baseAssetName).isContract(address)
|
|
@@ -43,8 +43,7 @@ export async function getBalance({ asset, address }) {
|
|
|
43
43
|
export async function getBalanceProxied({ asset, address, tag = 'latest' }) {
|
|
44
44
|
if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
|
|
45
45
|
const result = await getServer(asset).getBalanceProxied(address)
|
|
46
|
-
|
|
47
|
-
return new BN(hex, 'hex').toString()
|
|
46
|
+
return fromHexToString(result)
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
// Only ETH-like assets with token support
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import BN from 'bn.js'
|
|
2
1
|
import { bufferToHex } from '@exodus/ethereumjs-util'
|
|
3
2
|
import SolidityContract from '@exodus/solidity-contract'
|
|
4
3
|
import EventEmitter from 'events'
|
|
5
4
|
import { isEmpty } from 'lodash'
|
|
6
5
|
import { fetch } from '@exodus/fetch'
|
|
6
|
+
import { fromHexToString } from '../number-utils'
|
|
7
7
|
|
|
8
8
|
export default class ApiCoinNodesServer extends EventEmitter {
|
|
9
9
|
constructor({ uri }) {
|
|
@@ -135,8 +135,7 @@ export default class ApiCoinNodesServer extends EventEmitter {
|
|
|
135
135
|
async balanceOf(address, tokenAddress, tag = 'latest') {
|
|
136
136
|
const request = this.balanceOfRequest(address, tokenAddress, tag)
|
|
137
137
|
const result = await this.sendRequest(request)
|
|
138
|
-
const
|
|
139
|
-
const balance = new BN(hex, 'hex').toString()
|
|
138
|
+
const balance = fromHexToString(result)
|
|
140
139
|
return {
|
|
141
140
|
confirmed: {
|
|
142
141
|
[tokenAddress]: balance,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import ms from 'ms'
|
|
2
|
-
import BN from 'bn.js'
|
|
3
2
|
import createWebSocket from './ws'
|
|
4
3
|
import fetchival from '@exodus/fetch/experimental/fetchival'
|
|
5
4
|
import { retry } from '@exodus/simple-retry'
|
|
6
5
|
import SolidityContract from '@exodus/solidity-contract'
|
|
7
6
|
import { bufferToHex } from '@exodus/ethereumjs-util'
|
|
8
7
|
import { randomUUID } from '@exodus/crypto/randomUUID'
|
|
8
|
+
import { fromHexToString } from '../number-utils'
|
|
9
9
|
|
|
10
10
|
const RETRY_DELAYS = ['10s']
|
|
11
11
|
|
|
@@ -88,8 +88,7 @@ export function create(defaultURL, ensAssetName) {
|
|
|
88
88
|
tag,
|
|
89
89
|
}
|
|
90
90
|
const result = await retry(this.ethCall, { delayTimesMs: RETRY_DELAYS })(data)
|
|
91
|
-
const
|
|
92
|
-
const balance = new BN(hex, 'hex').toString()
|
|
91
|
+
const balance = fromHexToString(result)
|
|
93
92
|
return {
|
|
94
93
|
confirmed: {
|
|
95
94
|
[tokenAddress]: balance,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import BN from 'bn.js'
|
|
2
1
|
import { bufferToHex } from '@exodus/ethereumjs-util'
|
|
3
2
|
import SolidityContract from '@exodus/solidity-contract'
|
|
4
3
|
import EventEmitter from 'events'
|
|
5
4
|
import io from 'socket.io-client'
|
|
5
|
+
import { fromHexToString } from '../number-utils'
|
|
6
6
|
|
|
7
7
|
export default class ClarityServer extends EventEmitter {
|
|
8
8
|
constructor({ baseAssetName, uri }) {
|
|
@@ -51,7 +51,10 @@ export default class ClarityServer extends EventEmitter {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
createSocket(namespace) {
|
|
54
|
-
return io(`${this.uri}${namespace}`, {
|
|
54
|
+
return io(`${this.uri}${namespace}`, {
|
|
55
|
+
transports: ['websocket', 'polling'],
|
|
56
|
+
extraHeaders: { 'User-Agent': 'exodus' },
|
|
57
|
+
})
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
disconnectTransactions(address) {
|
|
@@ -296,8 +299,7 @@ export default class ClarityServer extends EventEmitter {
|
|
|
296
299
|
async balanceOf(address, tokenAddress, tag = 'latest') {
|
|
297
300
|
const request = this.balanceOfRequest(address, tokenAddress, tag)
|
|
298
301
|
const result = await this.sendRequest(request)
|
|
299
|
-
const
|
|
300
|
-
const balance = new BN(hex, 'hex').toString()
|
|
302
|
+
const balance = fromHexToString(result)
|
|
301
303
|
return {
|
|
302
304
|
confirmed: {
|
|
303
305
|
[tokenAddress]: balance,
|
package/src/get-balances.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isRpcBalanceAsset } from '@exodus/ethereum-lib'
|
|
2
2
|
|
|
3
3
|
import { get } from 'lodash'
|
|
4
4
|
|
|
@@ -30,22 +30,27 @@ const getBalanceFromTxLog = ({ txLog, asset }) => {
|
|
|
30
30
|
return txLog.size > 0 ? txLog.getMutations().slice(-1)[0].balance : asset.currency.ZERO
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const
|
|
33
|
+
const getStaked = ({ accountState, asset }) => {
|
|
34
|
+
return get(accountState, ['staking', asset.name, 'delegatedBalance']) || asset.currency.ZERO
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getUnstaking = ({ accountState, asset }) => {
|
|
38
|
+
return get(accountState, ['staking', asset.name, 'undelegatedBalance']) || asset.currency.ZERO
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const getUnstaked = ({ accountState, asset }) => {
|
|
42
|
+
return (
|
|
43
|
+
get(accountState, ['staking', asset.name, 'unclaimedUndelegatedBalance']) || asset.currency.ZERO
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getBasicSpendable({ asset, accountState, txLog }) {
|
|
34
48
|
const balance = isRpcBalanceAsset(asset)
|
|
35
49
|
? getBalanceFromAccountState({ asset, accountState })
|
|
36
50
|
: getBalanceFromTxLog({ txLog, asset })
|
|
37
51
|
|
|
38
52
|
const shouldFixBalance = isRpcBalanceAsset(asset)
|
|
39
53
|
|
|
40
|
-
if (['ethereum', 'ethereumgoerli', 'ethereumholesky', 'ethereumsepolia'].includes(asset.name)) {
|
|
41
|
-
const { balance: ethereumBalance } = getEthereumBalances({
|
|
42
|
-
asset,
|
|
43
|
-
liquidBalance: balance,
|
|
44
|
-
accountState,
|
|
45
|
-
})
|
|
46
|
-
return ethereumBalance
|
|
47
|
-
}
|
|
48
|
-
|
|
49
54
|
return shouldFixBalance ? fixBalance({ txLog, balance }) : balance
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -58,6 +63,23 @@ const getBalance = ({ asset, accountState, txLog }) => {
|
|
|
58
63
|
* @returns {{balance}|null} an object with the balance or null if the balance is unknown
|
|
59
64
|
*/
|
|
60
65
|
export const getBalances = ({ asset, txLog, accountState }) => {
|
|
61
|
-
const
|
|
62
|
-
|
|
66
|
+
const spendable = getBasicSpendable({ asset, accountState, txLog })
|
|
67
|
+
|
|
68
|
+
const staked = getStaked({ asset, accountState })
|
|
69
|
+
const unstaking = getUnstaking({ asset, accountState })
|
|
70
|
+
const unstaked = getUnstaked({ asset, accountState })
|
|
71
|
+
|
|
72
|
+
const total = spendable.add(staked).add(unstaking).add(unstaked)
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
// new
|
|
76
|
+
spendable,
|
|
77
|
+
total,
|
|
78
|
+
staked,
|
|
79
|
+
unstaking,
|
|
80
|
+
unstaked,
|
|
81
|
+
// legacy
|
|
82
|
+
balance: total,
|
|
83
|
+
spendableBalance: spendable,
|
|
84
|
+
}
|
|
63
85
|
}
|
package/src/get-fee.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { calculateBumpedGasPrice, calculateExtraEth, isEthereumLike } from '@exodus/ethereum-lib'
|
|
2
|
+
|
|
3
|
+
// Move to meta?
|
|
4
|
+
const taxes = {
|
|
5
|
+
paxgold: 0.0002,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const getGasPriceMultiplier = ({ asset, feeData, isExchange, isSendAll }) => {
|
|
9
|
+
// exchanges quotes expire, do not risk having a stuck tx
|
|
10
|
+
if (isExchange) return 1
|
|
11
|
+
|
|
12
|
+
// if eip1559 enabled, do not risk not leaving enough ETH to cover base fee (applies only for native asset)
|
|
13
|
+
// (gasPrice difference will be reimbursed anyway: users do not overpay)
|
|
14
|
+
if (isSendAll && isEthereumLike(asset) && feeData.eip1559Enabled) return 1
|
|
15
|
+
|
|
16
|
+
// do not risk having a stuck tx if we're not able to accelerate it
|
|
17
|
+
if (!feeData.rbfEnabled) return 1
|
|
18
|
+
|
|
19
|
+
return feeData.gasPriceEconomicalRate
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getExtraFeeData = ({ asset, amount }) => {
|
|
23
|
+
const tax = taxes[asset.name]
|
|
24
|
+
if (!amount || !tax || amount.isZero) {
|
|
25
|
+
return {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const extraFee = amount.mul(tax)
|
|
29
|
+
return {
|
|
30
|
+
type: 'tax',
|
|
31
|
+
tax,
|
|
32
|
+
extraFee,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const getFeeFactory =
|
|
37
|
+
({ gasLimit: defaultGasLimit }) =>
|
|
38
|
+
({
|
|
39
|
+
asset,
|
|
40
|
+
feeData,
|
|
41
|
+
customFee,
|
|
42
|
+
gasLimit = defaultGasLimit,
|
|
43
|
+
isExchange,
|
|
44
|
+
isSendAll,
|
|
45
|
+
amount,
|
|
46
|
+
calculateEffectiveFee, // BE
|
|
47
|
+
customFee: customGasPrice, // BE
|
|
48
|
+
}) => {
|
|
49
|
+
const { gasPrice, eip1559Enabled, baseFeePerGas, tipGasPrice } = feeData
|
|
50
|
+
const gasPriceMultiplier = getGasPriceMultiplier({
|
|
51
|
+
asset,
|
|
52
|
+
feeData,
|
|
53
|
+
isExchange,
|
|
54
|
+
isSendAll,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const extraFeeData = getExtraFeeData({ asset, amount })
|
|
58
|
+
if (calculateEffectiveFee && eip1559Enabled) {
|
|
59
|
+
const maxFeePerGas = customGasPrice || gasPrice
|
|
60
|
+
// effective_gas_price = min(base_fee_per_gas + tip_gas_price, max_fee_per_gas)
|
|
61
|
+
const feePerGas = baseFeePerGas.add(tipGasPrice)
|
|
62
|
+
const effectiveGasPrice = feePerGas.lt(maxFeePerGas) ? feePerGas : maxFeePerGas
|
|
63
|
+
|
|
64
|
+
return { fee: effectiveGasPrice.mul(gasLimit), extraFeeData }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const fee = (customFee || gasPrice.mul(gasPriceMultiplier)).mul(gasLimit || asset.gasLimit)
|
|
68
|
+
return { fee, extraFeeData }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Used in BE
|
|
72
|
+
export const getEffectiveGasPrice = ({ feeData }) => {
|
|
73
|
+
const { baseFeePerGas, tipGasPrice, gasPrice: maxFeePerGas, eip1559Enabled } = feeData
|
|
74
|
+
|
|
75
|
+
if (!eip1559Enabled) {
|
|
76
|
+
return maxFeePerGas
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const gasPrice = baseFeePerGas.add(tipGasPrice)
|
|
80
|
+
return gasPrice.lt(maxFeePerGas) ? gasPrice : maxFeePerGas
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Used in Mobile
|
|
84
|
+
export const getExtraFeeForBump = ({ tx, feeData, balance, unconfirmedBalance }) => {
|
|
85
|
+
if (!balance || !unconfirmedBalance) return null
|
|
86
|
+
const { gasPrice: currentGasPrice, eip1559Enabled } = feeData
|
|
87
|
+
const { bumpedGasPrice } = calculateBumpedGasPrice({
|
|
88
|
+
baseAsset: 'ethereum',
|
|
89
|
+
tx,
|
|
90
|
+
currentGasPrice,
|
|
91
|
+
eip1559Enabled,
|
|
92
|
+
})
|
|
93
|
+
return calculateExtraEth({
|
|
94
|
+
fee: bumpedGasPrice.mul(tx.data.gasLimit),
|
|
95
|
+
balance: balance.sub(unconfirmedBalance),
|
|
96
|
+
})
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createEthereumHooks } from './monitor'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getEthereumStakingInfo, getPolygonStakingInfo } from '../staking'
|
|
2
|
+
import { isEmpty } from 'lodash'
|
|
3
|
+
|
|
4
|
+
import processTxLog from '../tx-log-staking-processor'
|
|
5
|
+
|
|
6
|
+
export const createEthereumHooks = ({ assetClientInterface }) => {
|
|
7
|
+
const afterTickHook = async ({ walletAccount }) => {
|
|
8
|
+
// args passed from monitor tick method ({ monitor, error })
|
|
9
|
+
const { ethereum, polygon } = await assetClientInterface.getAssetsForNetwork({
|
|
10
|
+
baseAssetName: 'ethereum',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const assetName = ethereum.name
|
|
14
|
+
|
|
15
|
+
const userAddress = await assetClientInterface.getReceiveAddress({
|
|
16
|
+
assetName,
|
|
17
|
+
walletAccount,
|
|
18
|
+
useCache: true,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const ethereumStakingInfo = await getEthereumStakingInfo({
|
|
22
|
+
address: userAddress.toString(),
|
|
23
|
+
asset: ethereum,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const polygonStakingInfo = await getPolygonStakingInfo({
|
|
27
|
+
address: userAddress.toString(),
|
|
28
|
+
asset: polygon,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const stakingInfo = {
|
|
32
|
+
staking: {
|
|
33
|
+
ethereum: ethereumStakingInfo,
|
|
34
|
+
polygon: polygonStakingInfo,
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const batch = assetClientInterface.createOperationsBatch()
|
|
39
|
+
assetClientInterface.updateAccountStateBatch({
|
|
40
|
+
assetName: ethereum.name,
|
|
41
|
+
walletAccount,
|
|
42
|
+
newData: stakingInfo,
|
|
43
|
+
batch,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const processTxLogsPromises = []
|
|
47
|
+
|
|
48
|
+
const holeskyAssets = await assetClientInterface.getAssetsForNetwork({
|
|
49
|
+
baseAssetName: 'ethereumholesky',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (!isEmpty(holeskyAssets)) {
|
|
53
|
+
const { ethereumholesky } = holeskyAssets
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.log('ethereum-hooks updating holesky state')
|
|
56
|
+
|
|
57
|
+
const ethereumHoleskyInfo = await getEthereumStakingInfo({
|
|
58
|
+
address: userAddress.toString(),
|
|
59
|
+
asset: ethereumholesky,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
assetClientInterface.updateAccountStateBatch({
|
|
63
|
+
assetName: ethereumholesky.name,
|
|
64
|
+
walletAccount,
|
|
65
|
+
newData: {
|
|
66
|
+
staking: {
|
|
67
|
+
ethereumholesky: ethereumHoleskyInfo,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
batch,
|
|
71
|
+
})
|
|
72
|
+
processTxLogsPromises.push(
|
|
73
|
+
processTxLog({ asset: ethereumholesky, assetClientInterface, walletAccount, batch })
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
processTxLogsPromises.push(
|
|
78
|
+
processTxLog({ asset: polygon, assetClientInterface, walletAccount, batch }),
|
|
79
|
+
processTxLog({ asset: ethereum, assetClientInterface, walletAccount, batch })
|
|
80
|
+
)
|
|
81
|
+
await Promise.all(processTxLogsPromises)
|
|
82
|
+
await assetClientInterface.executeOperationsBatch(batch)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
'after-tick': afterTickHook,
|
|
87
|
+
}
|
|
88
|
+
}
|