@exodus/ethereum-api 8.76.6 → 8.76.7
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 +20 -0
- package/package.json +2 -3
- package/src/create-asset-plugin-factory.js +1 -0
- package/src/create-asset-utils.js +39 -35
- package/src/create-asset.js +21 -14
- package/src/exodus-eth-server/api-coin-nodes.js +11 -84
- package/src/exodus-eth-server/clarity-v2.js +30 -51
- package/src/exodus-eth-server/clarity.js +2 -115
- package/src/exodus-eth-server/errors.js +5 -1
- package/src/exodus-eth-server/eth-like-server-base.js +123 -0
- package/src/exodus-eth-server/fetch-json.js +48 -0
- package/src/gas-estimation.js +19 -4
- package/src/get-balances.js +14 -0
- package/src/index.js +1 -0
- package/src/multicall3/index.js +169 -0
- package/src/simulation/common.js +34 -0
- package/src/simulation/create-simulate-message.js +49 -0
- package/src/simulation/create-simulate-transactions.js +106 -0
- package/src/simulation/estimate-fee.js +14 -0
- package/src/simulation/estimate-simple-transfer.js +15 -0
- package/src/simulation/get-message-type.js +18 -0
- package/src/simulation/simulate-message-api.js +68 -0
- package/src/simulation/simulate-transactions-api.js +265 -0
- package/src/simulation/simulate-transactions.js +16 -0
- package/src/simulation/transactions.js +52 -0
- package/src/simulation/try-estimating-changes-locally.js +26 -0
- package/src/staking/ethereum/staking-utils.js +3 -1
- package/src/staking/matic/matic-staking-utils.js +3 -1
- package/src/tx-log/clarity-truncated-history-monitor.js +34 -0
- package/src/tx-log/ethereum-no-history-monitor.js +2 -23
- package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +28 -0
- package/src/tx-send/broadcast-error-handler.js +7 -2
- package/src/tx-send/tx-send.js +1 -0
- package/src/web3/createSimulateMessage.js +2 -1
- package/src/web3/createSimulateTransactions.js +3 -9
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { makeSimulationAPICall } from '@exodus/web3-utils'
|
|
2
|
+
|
|
3
|
+
import { BLOWFISH_EVM_CHAINS } from './common.js'
|
|
4
|
+
import { MessageTypeEnum } from './get-message-type.js'
|
|
5
|
+
|
|
6
|
+
const convertToAPIPayload = ({ address, message, url }) => {
|
|
7
|
+
const restParameters = {
|
|
8
|
+
metadata: { origin: url.href },
|
|
9
|
+
userAccount: address,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (message.messageType === MessageTypeEnum.TypedData) {
|
|
13
|
+
return {
|
|
14
|
+
message: {
|
|
15
|
+
kind: 'SIGN_TYPED_DATA',
|
|
16
|
+
data: JSON.parse(message.message),
|
|
17
|
+
},
|
|
18
|
+
...restParameters,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
message: {
|
|
24
|
+
kind: 'SIGN_MESSAGE',
|
|
25
|
+
rawMessage: message.message,
|
|
26
|
+
},
|
|
27
|
+
...restParameters,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const handleActionResponse = ({ simulationResult, response }) => {
|
|
32
|
+
if (['BLOCK', 'WARN'].includes(response.action)) {
|
|
33
|
+
// eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
|
|
34
|
+
simulationResult.action = response.action
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const simulateMessageApi = async ({
|
|
39
|
+
address,
|
|
40
|
+
apiEndpoint,
|
|
41
|
+
message,
|
|
42
|
+
url,
|
|
43
|
+
headers,
|
|
44
|
+
simulationResult,
|
|
45
|
+
}) => {
|
|
46
|
+
const chainInfo = BLOWFISH_EVM_CHAINS[simulationResult.baseAssetName]
|
|
47
|
+
if (!chainInfo) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { network, chain } = chainInfo
|
|
52
|
+
|
|
53
|
+
const payload = convertToAPIPayload({ address, message, url })
|
|
54
|
+
|
|
55
|
+
const response = await makeSimulationAPICall({
|
|
56
|
+
url: apiEndpoint,
|
|
57
|
+
chain,
|
|
58
|
+
network,
|
|
59
|
+
payload,
|
|
60
|
+
headers,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if (!response) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
handleActionResponse({ simulationResult, response })
|
|
68
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { pick } from '@exodus/basic-utils'
|
|
2
|
+
import { createCurrency, makeSimulationAPICall } from '@exodus/web3-utils'
|
|
3
|
+
|
|
4
|
+
import { BLOWFISH_EVM_CHAINS, MAX_INT256_SOLIDITY } from './common.js'
|
|
5
|
+
|
|
6
|
+
const convertTransactionToApiPayload = (transactions, origin, blockNumber) => {
|
|
7
|
+
const payload = {
|
|
8
|
+
txObjects: transactions.map((transaction) => ({
|
|
9
|
+
from: transaction.from,
|
|
10
|
+
to: transaction.to,
|
|
11
|
+
data: transaction.data,
|
|
12
|
+
value: transaction.value,
|
|
13
|
+
gas: transaction.gas,
|
|
14
|
+
gas_price: transaction.gasPrice,
|
|
15
|
+
})),
|
|
16
|
+
metadata: {
|
|
17
|
+
origin,
|
|
18
|
+
},
|
|
19
|
+
userAccount: transactions[0].from,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (blockNumber !== undefined) {
|
|
23
|
+
payload.blockNumber = blockNumber
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return payload
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const createBalanceAmount = (amount, { decimals, symbol }) => {
|
|
30
|
+
const beforeAmount = createCurrency({
|
|
31
|
+
amount: amount.before,
|
|
32
|
+
symbol,
|
|
33
|
+
denominator: decimals,
|
|
34
|
+
})
|
|
35
|
+
const afterAmount = createCurrency({
|
|
36
|
+
amount: amount.after,
|
|
37
|
+
symbol,
|
|
38
|
+
denominator: decimals,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return beforeAmount.sub(afterAmount)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const handleExpectedBalanceChange = (expectedBalanceChange, simulationResult, { network }) => {
|
|
45
|
+
const { willApprove, willSend, willReceive } = simulationResult.balanceChanges
|
|
46
|
+
const { data, kind } = expectedBalanceChange.rawInfo
|
|
47
|
+
|
|
48
|
+
if (kind === 'FARCASTER_CHANGE_RECOVERY_ADDRESS' || kind === 'ERC20_PERMIT') {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { asset: simulationAsset } = data
|
|
53
|
+
|
|
54
|
+
const isNftTransfer =
|
|
55
|
+
kind === 'ERC721_TRANSFER' || (kind === 'ERC1155_TRANSFER' && !simulationAsset.decimals)
|
|
56
|
+
const isNftTransferApproval =
|
|
57
|
+
kind === 'ERC721_APPROVAL' ||
|
|
58
|
+
(kind === 'ERC721_APPROVAL_FOR_ALL' && !simulationAsset.decimals) ||
|
|
59
|
+
(kind === 'ERC1155_APPROVAL_FOR_ALL' && !simulationAsset.decimals)
|
|
60
|
+
const decimals =
|
|
61
|
+
isNftTransfer || isNftTransferApproval || !Number.isFinite(simulationAsset.decimals)
|
|
62
|
+
? 0
|
|
63
|
+
: simulationAsset.decimals
|
|
64
|
+
const simulationAssetInfo = pick(simulationAsset, [
|
|
65
|
+
'address',
|
|
66
|
+
'imageUrl',
|
|
67
|
+
'name',
|
|
68
|
+
'symbol',
|
|
69
|
+
'verified',
|
|
70
|
+
])
|
|
71
|
+
|
|
72
|
+
let symbol = simulationAsset.symbol || simulationAsset.name || 'NFT'
|
|
73
|
+
if (network === 'polygon' && simulationAsset.symbol === 'MATIC') {
|
|
74
|
+
symbol = 'POL'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isNftTransfer || isNftTransferApproval) {
|
|
78
|
+
simulationAssetInfo.imageUrl = data?.metadata?.rawImageUrl
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
kind === 'ERC20_APPROVAL' ||
|
|
83
|
+
kind === 'ERC721_APPROVAL_FOR_ALL' ||
|
|
84
|
+
kind === 'ERC1155_APPROVAL_FOR_ALL' ||
|
|
85
|
+
kind === 'ERC721_APPROVAL'
|
|
86
|
+
) {
|
|
87
|
+
const unitsToUse = symbol || simulationAsset.name || 'Units.'
|
|
88
|
+
|
|
89
|
+
const approvingBalance = createCurrency({
|
|
90
|
+
amount: data.amount.after,
|
|
91
|
+
symbol: unitsToUse,
|
|
92
|
+
denominator: decimals,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
willApprove.push({
|
|
96
|
+
asset: simulationAssetInfo,
|
|
97
|
+
spender: data.spender.address,
|
|
98
|
+
unitName: unitsToUse,
|
|
99
|
+
balance: approvingBalance,
|
|
100
|
+
isMaxApproval: approvingBalance._number.gte(MAX_INT256_SOLIDITY),
|
|
101
|
+
})
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { amount } = data
|
|
106
|
+
|
|
107
|
+
const balance = createBalanceAmount(amount, { decimals, symbol })
|
|
108
|
+
|
|
109
|
+
if (balance.isZero) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const balanceChange = {
|
|
114
|
+
asset: simulationAssetInfo,
|
|
115
|
+
balance,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isNftTransfer || isNftTransferApproval) {
|
|
119
|
+
const tokenId = data.tokenId
|
|
120
|
+
const compositeId = `${simulationAsset.address}/${tokenId}`
|
|
121
|
+
balanceChange.nft = {
|
|
122
|
+
id: `${network}:${compositeId}`,
|
|
123
|
+
compositeId,
|
|
124
|
+
title: simulationAsset.name,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (balance.isNegative) {
|
|
129
|
+
balanceChange.balance = balanceChange.balance.negate()
|
|
130
|
+
|
|
131
|
+
willReceive.push(balanceChange)
|
|
132
|
+
} else {
|
|
133
|
+
willSend.push(balanceChange)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const simulationResultHasAssets = (expectedStateChanges) => {
|
|
138
|
+
for (const expectedChange of expectedStateChanges) {
|
|
139
|
+
const { asset: simulationAsset } = expectedChange.rawInfo.data
|
|
140
|
+
if (!simulationAsset) {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const SimulationErrorMessages = {
|
|
149
|
+
APICallFailed: 0,
|
|
150
|
+
SimulationFailed: 1,
|
|
151
|
+
SimulationNotSupported: 2,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const simulateTransactionsApi = async ({
|
|
155
|
+
transactions,
|
|
156
|
+
apiEndpoint,
|
|
157
|
+
origin,
|
|
158
|
+
blockNumber,
|
|
159
|
+
simulationResult,
|
|
160
|
+
headers,
|
|
161
|
+
asset,
|
|
162
|
+
}) => {
|
|
163
|
+
const successDefaultResult = {
|
|
164
|
+
kind: 'success',
|
|
165
|
+
value: undefined,
|
|
166
|
+
}
|
|
167
|
+
if (!BLOWFISH_EVM_CHAINS[asset.name]) {
|
|
168
|
+
return {
|
|
169
|
+
kind: 'error',
|
|
170
|
+
error: SimulationErrorMessages.SimulationNotSupported,
|
|
171
|
+
errorMessage: 'Simulation is not supported for this asset.',
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const userAddress = transactions[0].from
|
|
176
|
+
|
|
177
|
+
const payload = convertTransactionToApiPayload(transactions, origin, blockNumber)
|
|
178
|
+
|
|
179
|
+
if (!Object.hasOwnProperty.call(BLOWFISH_EVM_CHAINS, asset.name)) {
|
|
180
|
+
return successDefaultResult
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const { network, chain } = BLOWFISH_EVM_CHAINS[asset.name]
|
|
184
|
+
|
|
185
|
+
const response = await makeSimulationAPICall({
|
|
186
|
+
url: apiEndpoint,
|
|
187
|
+
chain,
|
|
188
|
+
network,
|
|
189
|
+
payload,
|
|
190
|
+
headers,
|
|
191
|
+
})
|
|
192
|
+
if (!response) {
|
|
193
|
+
return {
|
|
194
|
+
kind: 'error',
|
|
195
|
+
error: SimulationErrorMessages.APICallFailed,
|
|
196
|
+
errorMessage: 'Simulation API call failed.',
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
|
|
201
|
+
simulationResult.metadata.simulatedLocally = false
|
|
202
|
+
|
|
203
|
+
const { expectedStateChanges = {}, error } = response.simulationResults.aggregated
|
|
204
|
+
|
|
205
|
+
if (error) {
|
|
206
|
+
return {
|
|
207
|
+
kind: 'error',
|
|
208
|
+
error: SimulationErrorMessages.SimulationFailed,
|
|
209
|
+
errorMessage: error?.humanReadableError,
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { action } = response
|
|
214
|
+
|
|
215
|
+
if (action === 'BLOCK') {
|
|
216
|
+
simulationResult.warnings.push({
|
|
217
|
+
kind: 'MALICIOUS_ACTION',
|
|
218
|
+
severity: 'CRITICAL',
|
|
219
|
+
message: 'This dApp could be malicious. Do not proceed unless you are certain this is safe.',
|
|
220
|
+
})
|
|
221
|
+
} else if (action === 'WARN') {
|
|
222
|
+
simulationResult.warnings.push({
|
|
223
|
+
kind: 'SUSPICIOUS_ACTION',
|
|
224
|
+
severity: 'MEDIUM',
|
|
225
|
+
message: 'This transaction may be suspicious. Proceed with caution.',
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const noExpectedStateChangesDetected = Object.keys(expectedStateChanges).length === 0
|
|
230
|
+
|
|
231
|
+
if (noExpectedStateChangesDetected) {
|
|
232
|
+
simulationResult.balanceChanges.willSend.push({
|
|
233
|
+
balance: createCurrency({
|
|
234
|
+
amount: 0,
|
|
235
|
+
symbol: asset.displayTicker,
|
|
236
|
+
denominator: asset.currency.defaultUnit.power,
|
|
237
|
+
}),
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
return successDefaultResult
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const userExpectedChanges = Object.keys(expectedStateChanges).reduce((prev, address) => {
|
|
244
|
+
if (userAddress.toLowerCase() !== address.toLowerCase()) {
|
|
245
|
+
return prev
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result =
|
|
249
|
+
(Object.prototype.hasOwnProperty.call(expectedStateChanges, address) &&
|
|
250
|
+
expectedStateChanges[address]) ||
|
|
251
|
+
[]
|
|
252
|
+
|
|
253
|
+
return [...prev, ...result]
|
|
254
|
+
}, [])
|
|
255
|
+
|
|
256
|
+
if (!simulationResultHasAssets(userExpectedChanges)) {
|
|
257
|
+
return successDefaultResult
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
userExpectedChanges.forEach((expectedChange) => {
|
|
261
|
+
handleExpectedBalanceChange(expectedChange, simulationResult, { network })
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return successDefaultResult
|
|
265
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
|
+
|
|
3
|
+
import { createSimulateTransactions } from './create-simulate-transactions.js'
|
|
4
|
+
|
|
5
|
+
const simulateEVMTransactions = createSimulateTransactions({
|
|
6
|
+
apiEndpoint: 'https://simulation.a.exodus.io/simulate',
|
|
7
|
+
headers: {
|
|
8
|
+
'X-Api-Version': '2023-06-05',
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export function simulateTransactions({ asset, transactions, ...restParameters }) {
|
|
13
|
+
assert(asset, '"asset" should be passed.')
|
|
14
|
+
|
|
15
|
+
return simulateEVMTransactions({ asset, transactions, ...restParameters })
|
|
16
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import SolidityContract from '@exodus/solidity-contract'
|
|
2
|
+
|
|
3
|
+
export function getDisplayDetails(willApprove) {
|
|
4
|
+
let title = 'Approve Transaction?'
|
|
5
|
+
const isApproveTransaction = willApprove.length
|
|
6
|
+
|
|
7
|
+
if (isApproveTransaction) {
|
|
8
|
+
const tokenName = willApprove[0].unitName
|
|
9
|
+
title = tokenName ? `Approve ${tokenName} Transactions?` : title
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return { title }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const isSimpleTransfer = (transaction) => {
|
|
16
|
+
return ['0x', null, undefined].includes(transaction.data)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const getTxFeeDetails = (transaction) => {
|
|
20
|
+
const maxFeePerGas = transaction.maxFeePerGas
|
|
21
|
+
const gasPrice = transaction.gasPrice
|
|
22
|
+
const gasLimit = transaction.gas
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
gasLimit,
|
|
26
|
+
maxFeePerGas,
|
|
27
|
+
gasPrice,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function decodeRecipientAddresses(transaction) {
|
|
32
|
+
try {
|
|
33
|
+
if (!transaction.to) {
|
|
34
|
+
return []
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (isSimpleTransfer(transaction)) {
|
|
38
|
+
return [transaction.to]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const token = SolidityContract.erc20(transaction.to)
|
|
42
|
+
const { method, values } = token.decodeInput(transaction.data)
|
|
43
|
+
|
|
44
|
+
if (method === 'transfer') {
|
|
45
|
+
return [values[0]]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return []
|
|
49
|
+
} catch {
|
|
50
|
+
return []
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { estimateSimpleTransfer, NotSimpleTransferError } from './estimate-simple-transfer.js'
|
|
2
|
+
|
|
3
|
+
export const tryEstimatingChangesLocally = ({ asset, simulationResult, transaction }) => {
|
|
4
|
+
if (typeof transaction.from !== 'string' || typeof transaction.to !== 'string') {
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const estimatedValue = estimateSimpleTransfer({ baseAsset: asset, transaction })
|
|
10
|
+
const actualTransferValue =
|
|
11
|
+
transaction.from.toLowerCase() === transaction.to.toLowerCase()
|
|
12
|
+
? asset.currency.ZERO
|
|
13
|
+
: estimatedValue
|
|
14
|
+
simulationResult.balanceChanges.willSend.push({
|
|
15
|
+
balance: actualTransferValue,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return true
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (!(err instanceof NotSimpleTransferError)) {
|
|
21
|
+
// TODO: accept a logger dep and log the unexpected local simulation error.
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -3,6 +3,7 @@ import { parseUnsignedTx } from '@exodus/ethereum-lib'
|
|
|
3
3
|
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
4
4
|
import assert from 'minimalistic-assert'
|
|
5
5
|
|
|
6
|
+
import { simulateTransactions as simulateEthereumTransactions } from '../../simulation/simulate-transactions.js'
|
|
6
7
|
import { EthereumStaking, UNSTAKE_DEFAULTS } from './api.js'
|
|
7
8
|
import { simulateEverstakeUnstake } from './everstake.js'
|
|
8
9
|
|
|
@@ -211,7 +212,8 @@ export async function simulateUndelegateTransactions({
|
|
|
211
212
|
|
|
212
213
|
if (transactions.length === 0) return
|
|
213
214
|
|
|
214
|
-
const simulationResponse = await
|
|
215
|
+
const simulationResponse = await simulateEthereumTransactions({
|
|
216
|
+
asset: asset.baseAsset,
|
|
215
217
|
baseAssetName: asset.baseAsset.name,
|
|
216
218
|
origin: 'exodus-staking',
|
|
217
219
|
senderAddress,
|
|
@@ -4,6 +4,7 @@ import assetsList, { asset as ethereum } from '@exodus/ethereum-meta'
|
|
|
4
4
|
import { Tx } from '@exodus/models'
|
|
5
5
|
import assert from 'minimalistic-assert'
|
|
6
6
|
|
|
7
|
+
import { simulateTransactions as simulateEthereumTransactions } from '../../simulation/simulate-transactions.js'
|
|
7
8
|
import { decodePolygonStakingTxInputAmount } from '../../tx-log-staking-processor/utils.js'
|
|
8
9
|
import { MaticStakingApi } from './api.js'
|
|
9
10
|
|
|
@@ -211,7 +212,8 @@ export async function maticDelegateSimulateTransactions({
|
|
|
211
212
|
senderAddress,
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
const simulationResponse = await
|
|
215
|
+
const simulationResponse = await simulateEthereumTransactions({
|
|
216
|
+
asset: asset.baseAsset,
|
|
215
217
|
...simulationTxData,
|
|
216
218
|
transactions: [approveTxRequest, delegateTxRequest],
|
|
217
219
|
})
|
|
@@ -75,6 +75,40 @@ export class ClarityTruncatedHistoryMonitor extends ClarityMonitor {
|
|
|
75
75
|
let clarityCursor
|
|
76
76
|
|
|
77
77
|
try {
|
|
78
|
+
// When refreshing, we need to clear out old asset
|
|
79
|
+
// balances in `accountState` since these can surface
|
|
80
|
+
// values populated from alternative monitors i.e.
|
|
81
|
+
// `no-history`, which can be written at arbitrary
|
|
82
|
+
// points in time.
|
|
83
|
+
//
|
|
84
|
+
// Since the `truncated-history-monitor` does not
|
|
85
|
+
// guarantee an `accountState` write for an asset,
|
|
86
|
+
// (these are only provided when the history is
|
|
87
|
+
// truncated), we must ensure older checkpoints cannot
|
|
88
|
+
// be inadvertently relied upon.
|
|
89
|
+
//
|
|
90
|
+
if (refresh) {
|
|
91
|
+
const defaultAccountState = this.asset.api.createAccountState().create()
|
|
92
|
+
|
|
93
|
+
const currentAccountState = derivedData.currentAccountState ?? defaultAccountState
|
|
94
|
+
|
|
95
|
+
const resetTokenBalances = {
|
|
96
|
+
balance: defaultAccountState.balance,
|
|
97
|
+
tokenBalances: defaultAccountState.tokenBalances,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Optimistically update the `derivedData` for downstream.
|
|
101
|
+
derivedData.currentAccountState = currentAccountState.merge(resetTokenBalances)
|
|
102
|
+
|
|
103
|
+
this.aci.updateAccountStateBatch({
|
|
104
|
+
assetName,
|
|
105
|
+
walletAccount,
|
|
106
|
+
accountState: currentAccountState,
|
|
107
|
+
newData: resetTokenBalances,
|
|
108
|
+
batch,
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
78
112
|
const response = await this.getHistoryFromServer({ walletAccount, derivedData, refresh })
|
|
79
113
|
|
|
80
114
|
;({ allTxs } = await normalizeTransactionsResponse({
|
|
@@ -3,7 +3,7 @@ import { SynchronizedTime } from '@exodus/basic-utils'
|
|
|
3
3
|
import { Tx } from '@exodus/models'
|
|
4
4
|
import lodash from 'lodash'
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { getBatchedRpcBalances } from './monitor-utils/get-batched-rpc-balances.js'
|
|
7
7
|
import { UNCONFIRMED_TX_LIMIT } from './monitor-utils/get-derive-transactions-to-check.js'
|
|
8
8
|
import {
|
|
9
9
|
excludeUnchangedTokenBalances,
|
|
@@ -41,28 +41,7 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
async getBalances({ tokens, ourWalletAddress }) {
|
|
44
|
-
|
|
45
|
-
const request = this.server.getBalanceRequest(ourWalletAddress)
|
|
46
|
-
batch[this.asset.name] = request
|
|
47
|
-
for (const token of tokens) {
|
|
48
|
-
const request = this.server.balanceOfRequest(ourWalletAddress, token.contract.address)
|
|
49
|
-
batch[token.name] = request
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const pairs = Object.entries(batch)
|
|
53
|
-
if (pairs.length === 0) {
|
|
54
|
-
return {}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const requests = pairs.map((pair) => pair[1])
|
|
58
|
-
const responses = await this.server.sendBatchRequest(requests)
|
|
59
|
-
const entries = pairs.map((pair, idx) => {
|
|
60
|
-
const balanceHex = responses[idx]
|
|
61
|
-
const name = pair[0]
|
|
62
|
-
const balance = fromHexToString(balanceHex)
|
|
63
|
-
return [name, balance]
|
|
64
|
-
})
|
|
65
|
-
return Object.fromEntries(entries)
|
|
44
|
+
return getBatchedRpcBalances({ baseAsset: this.asset, ourWalletAddress, tokens })
|
|
66
45
|
}
|
|
67
46
|
|
|
68
47
|
async getNewAccountState({ tokens, currentTokenBalances, ourWalletAddress }) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
|
+
|
|
3
|
+
import { fromHexToString } from '../../number-utils.js'
|
|
4
|
+
|
|
5
|
+
export const getBatchedRpcBalances = async ({ baseAsset, tokens, ourWalletAddress }) => {
|
|
6
|
+
assert(baseAsset, 'expected baseAsset')
|
|
7
|
+
assert(Array.isArray(tokens), 'expected array tokens')
|
|
8
|
+
assert(ourWalletAddress, 'expected ourWalletAddress')
|
|
9
|
+
|
|
10
|
+
const [balance, ...tokenBalances] = await tokens
|
|
11
|
+
.reduce(
|
|
12
|
+
(acc, { contract: { address: tokenAddress } }) =>
|
|
13
|
+
acc.balanceOfRequest(ourWalletAddress, tokenAddress),
|
|
14
|
+
baseAsset.createRpcRequestAccumulator().getBalanceRequest(ourWalletAddress)
|
|
15
|
+
)
|
|
16
|
+
.flush(baseAsset.server)
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
[baseAsset.name]: fromHexToString(balance),
|
|
20
|
+
...Object.fromEntries(
|
|
21
|
+
tokens.map(({ name, contract: { address: tokenAddress } }, i) => {
|
|
22
|
+
const tokenBalance = tokenBalances[i]?.confirmed?.[tokenAddress]
|
|
23
|
+
assert(tokenBalance != null, `missing token balance for ${name} (${tokenAddress})`)
|
|
24
|
+
return [name, tokenBalance]
|
|
25
|
+
})
|
|
26
|
+
),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -11,10 +11,15 @@ import { transactionExists } from '../eth-like-util.js'
|
|
|
11
11
|
* @param {string} options.txId - The transaction ID.
|
|
12
12
|
* @param {boolean} options.isHardware - Whether this is a hardware wallet.
|
|
13
13
|
* @param {string} options.hint - Hint for the error.
|
|
14
|
+
* @param {boolean} options.isBumpTx - Whether the occur occured whilst bumping a Tx.
|
|
15
|
+
* @param {boolean} options.isRetryNonceTx - If the error occurred during a nonce bump.
|
|
14
16
|
* @returns {Promise<{ shouldRetry: boolean }>} - Returns if nonce too low and can retry.
|
|
15
17
|
* @throws {EthLikeError} - Throws for all other error cases.
|
|
16
18
|
*/
|
|
17
|
-
export const handleBroadcastError = async (
|
|
19
|
+
export const handleBroadcastError = async (
|
|
20
|
+
err,
|
|
21
|
+
{ asset, txId, isHardware, hint, isBumpTx, isRetryNonceTx }
|
|
22
|
+
) => {
|
|
18
23
|
const message = err.message
|
|
19
24
|
|
|
20
25
|
const errorInfo = getEvmErrorReason(message) || EVM_ERROR_REASONS.broadcastTxFailed
|
|
@@ -46,7 +51,7 @@ export const handleBroadcastError = async (err, { asset, txId, isHardware, hint,
|
|
|
46
51
|
// NOTE: Don't auto-retry nonce repair for bump/replacement txs.
|
|
47
52
|
// A replacement must keep the *same nonce* as the tx it's replacing.
|
|
48
53
|
// If we "fix" a bump tx by advancing the nonce, we create a brand-new tx instead of replacing the pending one.
|
|
49
|
-
if (isNonceTooLow && !isHardware && !isBumpTx) return { shouldRetry: true }
|
|
54
|
+
if (isNonceTooLow && !isHardware && !isBumpTx && !isRetryNonceTx) return { shouldRetry: true }
|
|
50
55
|
|
|
51
56
|
throw new EthLikeError({
|
|
52
57
|
message: err.message,
|
package/src/tx-send/tx-send.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createSimulateMessage as createSimulateEVMMessage } from '@exodus/web3-ethereum-utils'
|
|
2
1
|
import assert from 'minimalistic-assert'
|
|
3
2
|
|
|
3
|
+
import { createSimulateMessage as createSimulateEVMMessage } from '../simulation/create-simulate-message.js'
|
|
4
|
+
|
|
4
5
|
export const createSimulateMessage = ({ asset }) => {
|
|
5
6
|
assert(asset, '"asset" should be passed.')
|
|
6
7
|
|
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { createSimulateTransactions as createSimulateEVMTransactions } from '@exodus/web3-ethereum-utils'
|
|
2
1
|
import assert from 'minimalistic-assert'
|
|
3
2
|
|
|
3
|
+
import { simulateTransactions as simulateEthereumTransactions } from '../simulation/simulate-transactions.js'
|
|
4
|
+
|
|
4
5
|
export const createSimulateTransactions = ({ asset }) => {
|
|
5
6
|
assert(asset, '"asset" should be passed.')
|
|
6
7
|
|
|
7
|
-
const simulateEVMTransactions = createSimulateEVMTransactions({
|
|
8
|
-
apiEndpoint: 'https://simulation.a.exodus.io/simulate',
|
|
9
|
-
headers: {
|
|
10
|
-
'X-Api-Version': '2023-06-05',
|
|
11
|
-
},
|
|
12
|
-
})
|
|
13
|
-
|
|
14
8
|
return function simulateTransaction({ transactions, ...restParameters }) {
|
|
15
|
-
return
|
|
9
|
+
return simulateEthereumTransactions({ asset, transactions, ...restParameters })
|
|
16
10
|
}
|
|
17
11
|
}
|