@exodus/ethereum-api 9.0.0-alpha.2 → 9.0.0
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 +2121 -0
- package/LICENSE +7 -0
- package/README.md +124 -0
- package/package.json +34 -33
- package/src/address-has-history.js +6 -2
- package/src/allowance/constants.js +4 -5
- package/src/allowance/index.js +159 -40
- package/src/check-tx/create-assess-transaction.js +23 -0
- package/src/check-tx/create-check-tx.js +104 -0
- package/src/check-tx/index.js +1 -0
- package/src/create-asset-plugin-factory.js +115 -0
- package/src/create-asset-utils.js +520 -0
- package/src/create-asset.js +272 -97
- package/src/create-token-factory.js +47 -17
- package/src/custom-fees.js +57 -0
- package/src/eip7702-whitelist.js +62 -0
- package/src/ens/index.js +12 -17
- package/src/error-wrapper.js +383 -0
- package/src/eth-like-util.js +172 -33
- package/src/exodus-eth-server/api-coin-nodes.js +27 -196
- package/src/exodus-eth-server/api.js +23 -14
- package/src/exodus-eth-server/clarity-v2.js +255 -0
- package/src/exodus-eth-server/clarity.js +67 -265
- package/src/exodus-eth-server/errors.js +40 -0
- package/src/exodus-eth-server/eth-like-server.js +385 -0
- package/src/exodus-eth-server/fetch-json.js +48 -0
- package/src/exodus-eth-server/index.js +77 -0
- package/src/exodus-eth-server/utils.js +31 -0
- package/src/exodus-eth-server/ws-gateway.js +306 -0
- package/src/exodus-eth-server/ws.js +1 -2
- package/src/fee-data/EthLikeFeeData.js +81 -0
- package/src/fee-data/index.js +54 -0
- package/src/fee-utils.js +229 -0
- package/src/gas-estimation.js +183 -66
- package/src/get-balance-for-address.js +1 -1
- package/src/get-balances.js +285 -27
- package/src/get-fee.js +92 -56
- package/src/get-historical-balance.js +58 -0
- package/src/hooks/index.js +1 -1
- package/src/hooks/monitor.js +182 -43
- package/src/hooks/staking-freshness.js +58 -0
- package/src/index.js +118 -12
- package/src/move-funds.js +153 -0
- package/src/multicall3/index.js +212 -0
- package/src/multisig-address.js +146 -0
- package/src/nft-utils.js +60 -0
- package/src/number-utils.js +34 -2
- package/src/optimism-gas/index.js +13 -5
- package/src/send-validations.js +58 -0
- package/src/server-based-fee-monitor.js +12 -19
- package/src/simulate-tx/fetch-tx-preview.js +0 -2
- package/src/simulate-tx/index.js +2 -2
- package/src/simulate-tx/simulate-eth-tx.js +24 -11
- package/src/simulation/common.js +34 -0
- package/src/simulation/create-simulate-message.js +49 -0
- package/src/simulation/create-simulate-transactions.js +108 -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 +266 -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/api/index.js +43 -0
- package/src/staking/cached-read.js +33 -0
- package/src/staking/ethereum/api.js +166 -34
- package/src/staking/ethereum/deps.js +37 -0
- package/src/staking/ethereum/everstake.js +113 -0
- package/src/staking/ethereum/index.js +4 -3
- package/src/staking/ethereum/service.js +682 -181
- package/src/staking/ethereum/staking-utils.js +275 -4
- package/src/staking/fantom-staking.js +9 -9
- package/src/staking/index.js +4 -4
- package/src/staking/matic/addresses.js +2 -0
- package/src/staking/matic/api.js +69 -49
- package/src/staking/matic/index.js +3 -3
- package/src/staking/matic/matic-staking-utils.js +243 -5
- package/src/staking/matic/matic-staking.md +53 -0
- package/src/staking/staking-provider-client.js +1 -1
- package/src/staking/utils/index.js +39 -0
- package/src/tx-create.js +666 -0
- package/src/tx-log/clarity-monitor.js +273 -103
- package/src/tx-log/clarity-truncated-history-monitor.js +109 -0
- package/src/tx-log/clarity-truncated-history-monitor.md +219 -0
- package/src/tx-log/clarity-utils/absolute.js +96 -0
- package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +94 -31
- package/src/tx-log/clarity-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
- package/src/tx-log/clarity-utils/index.js +9 -3
- package/src/tx-log/clarity-utils/is-spam-pending-tx.js +13 -0
- package/src/tx-log/clarity-utils/normalize-transactions-response.js +65 -0
- package/src/tx-log/ethereum-monitor.js +93 -47
- package/src/tx-log/ethereum-no-history-monitor.js +52 -41
- package/src/tx-log/get-optimistic-txlog-effects.js +379 -0
- package/src/tx-log/index.js +5 -3
- package/src/tx-log/monitor-utils/check-pending-transactions.js +14 -5
- package/src/tx-log/monitor-utils/exclude-unchanged-token-balances.js +1 -1
- package/src/tx-log/monitor-utils/extract-balance-from-clarity-account-info.js +108 -0
- package/src/tx-log/monitor-utils/get-balance-updates.js +44 -0
- package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +47 -0
- package/src/tx-log/monitor-utils/get-current-blacklist-status.js +38 -0
- package/src/tx-log/monitor-utils/get-current-eip7702-delegation.js +79 -0
- package/src/tx-log/monitor-utils/get-derive-transactions-to-check.js +10 -5
- package/src/tx-log/monitor-utils/get-fee-amount.js +1 -1
- package/src/tx-log/monitor-utils/get-history-from-server.js +1 -1
- package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +75 -33
- package/src/tx-log/monitor-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
- package/src/tx-log/monitor-utils/get-raw-transfer-amount.js +29 -0
- package/src/tx-log/monitor-utils/get-transfers-by-token-name.js +3 -2
- package/src/tx-log/monitor-utils/get-value-of-transfers.js +10 -12
- package/src/tx-log/monitor-utils/index.js +12 -7
- package/src/tx-log/monitor-utils/is-unverifiable-pending-tx-expired.js +19 -0
- package/src/tx-log/monitor-utils/verify-pending-tx-status-rpc.js +90 -0
- package/src/tx-log-staking-processor/asset-staking-tx-data.js +82 -24
- package/src/tx-log-staking-processor/index.js +28 -14
- package/src/tx-log-staking-processor/utils.js +21 -20
- package/src/tx-send/broadcast-error-handler.js +63 -0
- package/src/tx-send/index.js +1 -2
- package/src/tx-send/nonce-utils.js +97 -0
- package/src/tx-send/tx-send.js +94 -288
- package/src/tx-sign/index.js +17 -0
- package/src/tx-type/index.js +280 -0
- package/src/watch-tx.js +36 -0
- package/src/web3/createSimulateMessage.js +3 -2
- package/src/web3/createSimulateTransactions.js +11 -0
- package/src/web3/index.js +3 -1
- package/src/etherscan/account.js +0 -49
- package/src/etherscan/index.js +0 -28
- package/src/etherscan/logs.js +0 -16
- package/src/etherscan/proxy.js +0 -47
- package/src/etherscan/request.js +0 -26
- package/src/etherscan/ws.js +0 -88
- package/src/exodus-eth-server/server-factory.js +0 -28
- package/src/exodus-eth-server/server-map.js +0 -21
- package/src/get-fee-async.js +0 -97
- package/src/staking/matic/service.js +0 -437
- package/src/staking-api.js +0 -5
- package/src/tx-log/clarity-utils/get-derive-data-needed-for-tick.js +0 -12
- package/src/tx-log-staking-processor/get-asset-tx-amount.js +0 -26
- package/src/tx-send/get-fee-info.js +0 -41
- package/src/websocket/index.android.js +0 -2
- package/src/websocket/index.ios.js +0 -2
- package/src/websocket/index.js +0 -21
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import NumberUnit from '@exodus/currency'
|
|
2
|
+
import { assertValidTxValue, isEthereumLikeToken } from '@exodus/ethereum-lib'
|
|
3
|
+
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
4
|
+
import assert from 'minimalistic-assert'
|
|
5
|
+
|
|
6
|
+
import { isContractAddressCached } from '../eth-like-util.js'
|
|
7
|
+
|
|
8
|
+
export const TX_TYPE_TRANSFER = 'transfer'
|
|
9
|
+
export const TX_TYPE_CREATE_CONTRACT = 'create-contract'
|
|
10
|
+
|
|
11
|
+
const VALID_TX_TYPES = new Set([TX_TYPE_TRANSFER, TX_TYPE_CREATE_CONTRACT])
|
|
12
|
+
|
|
13
|
+
export const isValidTxType = (txType) => VALID_TX_TYPES.has(txType)
|
|
14
|
+
|
|
15
|
+
// TODO: Remove this.
|
|
16
|
+
// HACK: If a recipient address is not defined, we usually fall back to
|
|
17
|
+
// default address so gas estimation can still complete successfully
|
|
18
|
+
// without knowledge of which accounts are involved.
|
|
19
|
+
//
|
|
20
|
+
// However, we must be careful to select addresses which are unlikely
|
|
21
|
+
// to have existing obligations, such as popular dead addresses or the
|
|
22
|
+
// reserved addresses of precompiles, since these can influence gas
|
|
23
|
+
// estimation.
|
|
24
|
+
//
|
|
25
|
+
// Here, we use an address which is mostly all `1`s to make sure we can
|
|
26
|
+
// exaggerate the worst-case calldata cost (which is priced per high bit)
|
|
27
|
+
// whilst being unlikely to have any token balances.
|
|
28
|
+
//
|
|
29
|
+
// Unfortunately, we can't use `0xffffffffffffffffffffffffffffffffffffffff`,
|
|
30
|
+
// since this address is a whale.
|
|
31
|
+
export const ARBITRARY_ADDRESS = '0xfffFfFfFfFfFFFFFfeFfFFFffFffFFFFfFFFFFFF'.toLowerCase()
|
|
32
|
+
|
|
33
|
+
export const assertCriticalTxAttributes = (criticalTxAttributes) => {
|
|
34
|
+
const { amount, toAddress, txInput, txToAddress, txType, txValue } = criticalTxAttributes
|
|
35
|
+
|
|
36
|
+
assert(amount instanceof NumberUnit, 'expected NumberUnit amount')
|
|
37
|
+
assert(
|
|
38
|
+
typeof txInput === 'string' && txInput.startsWith('0x'),
|
|
39
|
+
'expected hexadecimal string txInput'
|
|
40
|
+
)
|
|
41
|
+
assert(isValidTxType(txType), 'expected valid txType')
|
|
42
|
+
assert(txValue instanceof NumberUnit, 'expected NumberUnit txValue')
|
|
43
|
+
|
|
44
|
+
if (txType === TX_TYPE_CREATE_CONTRACT) {
|
|
45
|
+
assert(toAddress === null, 'expected null toAddress')
|
|
46
|
+
assert(txToAddress === null, 'expected null txToAddress')
|
|
47
|
+
assert(txInput !== '0x', 'expected non-empty txInput for contract creation')
|
|
48
|
+
} else {
|
|
49
|
+
assert(typeof toAddress === 'string', 'expected string toAddress')
|
|
50
|
+
assert(typeof txToAddress === 'string', 'expected string txToAddress')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return criticalTxAttributes
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const assertTxAttributes = (txAttributes) => {
|
|
57
|
+
// TODO: Note that the distinction between "critical" and "non-critical"
|
|
58
|
+
// only exists to satisfy `fetchGasLimit` needing to know some props.
|
|
59
|
+
// Once we encapsulate `gasLimit`, all properties will be critical.
|
|
60
|
+
const {
|
|
61
|
+
/* critical */
|
|
62
|
+
amount,
|
|
63
|
+
toAddress,
|
|
64
|
+
txInput,
|
|
65
|
+
txToAddress,
|
|
66
|
+
txType,
|
|
67
|
+
txValue,
|
|
68
|
+
/* non-critical */
|
|
69
|
+
fromAddress,
|
|
70
|
+
nonce,
|
|
71
|
+
isContractTxToAddress,
|
|
72
|
+
} = txAttributes
|
|
73
|
+
|
|
74
|
+
assertCriticalTxAttributes({ amount, toAddress, txInput, txToAddress, txType, txValue })
|
|
75
|
+
|
|
76
|
+
assert(typeof fromAddress === 'string', 'expected fromAddress')
|
|
77
|
+
assert(Number.isInteger(nonce), 'expected integer nonce')
|
|
78
|
+
assert(typeof isContractTxToAddress === 'boolean', 'expected boolean isContractTxToAddress')
|
|
79
|
+
|
|
80
|
+
return txAttributes
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const createResolvedTxAttributes = async ({
|
|
84
|
+
amount,
|
|
85
|
+
asset,
|
|
86
|
+
fromAddress,
|
|
87
|
+
nonce,
|
|
88
|
+
toAddress,
|
|
89
|
+
txInput,
|
|
90
|
+
txToAddress,
|
|
91
|
+
txValue,
|
|
92
|
+
txType,
|
|
93
|
+
}) =>
|
|
94
|
+
assertTxAttributes({
|
|
95
|
+
amount,
|
|
96
|
+
fromAddress,
|
|
97
|
+
nonce,
|
|
98
|
+
toAddress,
|
|
99
|
+
txInput,
|
|
100
|
+
txToAddress,
|
|
101
|
+
txValue,
|
|
102
|
+
txType,
|
|
103
|
+
isContractTxToAddress:
|
|
104
|
+
Boolean(txToAddress) && (await isContractAddressCached({ asset, address: txToAddress })),
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
export const resolveTxFromAddress = async ({
|
|
108
|
+
asset,
|
|
109
|
+
assetClientInterface,
|
|
110
|
+
fromAddress: providedFromAddress,
|
|
111
|
+
walletAccount,
|
|
112
|
+
}) => {
|
|
113
|
+
assert(asset, 'expected asset')
|
|
114
|
+
assert(assetClientInterface, 'expected assetClientInterface')
|
|
115
|
+
assert(walletAccount, 'expected walletAccount')
|
|
116
|
+
|
|
117
|
+
if (providedFromAddress) return providedFromAddress
|
|
118
|
+
|
|
119
|
+
return assetClientInterface.getReceiveAddress({
|
|
120
|
+
assetName: asset.baseAsset.name,
|
|
121
|
+
walletAccount,
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const resolveTxNonce = async ({
|
|
126
|
+
asset,
|
|
127
|
+
fromAddress,
|
|
128
|
+
nonce: providedNonce,
|
|
129
|
+
walletAccount,
|
|
130
|
+
}) => {
|
|
131
|
+
assert(asset, 'expected asset')
|
|
132
|
+
assert(typeof fromAddress === 'string', 'expected string fromAddress')
|
|
133
|
+
assert(walletAccount, 'expected walletAccount')
|
|
134
|
+
|
|
135
|
+
if (Number.isInteger(providedNonce)) return providedNonce
|
|
136
|
+
|
|
137
|
+
return asset.baseAsset.getNonce({ asset, fromAddress, walletAccount })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const resolveTxInput = ({ asset, toAddress, amount, txInput: providedTxInput }) => {
|
|
141
|
+
assert(asset, 'expected asset')
|
|
142
|
+
assert(typeof toAddress === 'string', 'expected string toAddress')
|
|
143
|
+
assert(amount instanceof NumberUnit, 'expected NumberUnit amount')
|
|
144
|
+
|
|
145
|
+
if (providedTxInput) return bufferToHex(providedTxInput)
|
|
146
|
+
|
|
147
|
+
if (!isEthereumLikeToken(asset)) return '0x'
|
|
148
|
+
|
|
149
|
+
return bufferToHex(asset.contract.transfer.build(toAddress.toLowerCase(), amount?.toBaseString()))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const resolveCriticalTxAttributes = ({
|
|
153
|
+
asset,
|
|
154
|
+
amount: providedAmount,
|
|
155
|
+
toAddress: providedToAddress,
|
|
156
|
+
txToAddress: providedTxToAddress,
|
|
157
|
+
txInput: providedTxInput,
|
|
158
|
+
txValue: providedTxValue,
|
|
159
|
+
txType,
|
|
160
|
+
}) => {
|
|
161
|
+
assert(asset, 'expected asset')
|
|
162
|
+
assert(isValidTxType(txType), 'expected valid txType')
|
|
163
|
+
|
|
164
|
+
const amount = providedAmount ?? asset.currency.ZERO
|
|
165
|
+
assert(amount instanceof NumberUnit, 'expected providedAmount')
|
|
166
|
+
|
|
167
|
+
const txValue = assertValidTxValue({ asset, amount, txValue: providedTxValue })
|
|
168
|
+
|
|
169
|
+
if (txType === TX_TYPE_CREATE_CONTRACT) {
|
|
170
|
+
assert(asset.name === asset.baseAsset.name, 'must use baseAsset for contract deployments')
|
|
171
|
+
assert(!providedToAddress, 'toAddress must be falsy when creating a contract')
|
|
172
|
+
assert(!providedTxToAddress, 'txToAddress must be falsy when creating a contract')
|
|
173
|
+
|
|
174
|
+
return assertCriticalTxAttributes({
|
|
175
|
+
amount,
|
|
176
|
+
toAddress: null,
|
|
177
|
+
txInput: providedTxInput,
|
|
178
|
+
txToAddress: null,
|
|
179
|
+
txType,
|
|
180
|
+
txValue,
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
assert(txType === TX_TYPE_TRANSFER, 'expected TX_TYPE_TRANSFER')
|
|
185
|
+
|
|
186
|
+
// NOTE: As a helper, if the `toAddress` is omitted, we
|
|
187
|
+
// will automatically assume the recipient of the
|
|
188
|
+
// transaction value to be the `providedTxToAddress`.
|
|
189
|
+
//
|
|
190
|
+
// This means we'll assume the high-level recipient
|
|
191
|
+
// of the assets transferred will be the
|
|
192
|
+
// `providedTxToAddress`, as opposed to another account
|
|
193
|
+
// which would be credited as a side effect.
|
|
194
|
+
//
|
|
195
|
+
// HACK: If a `toAddress` hasn't been defined, then we
|
|
196
|
+
// fall back to the `ARBITRARY_ADDRESS`. Note that
|
|
197
|
+
// this should only be used to help determine the
|
|
198
|
+
// fee of a transaction where we don't yet know the
|
|
199
|
+
// intended recipient. Attempts to send to this
|
|
200
|
+
// sentinel address will ultimately `throw`.
|
|
201
|
+
const toAddress = providedToAddress || providedTxToAddress || ARBITRARY_ADDRESS
|
|
202
|
+
const txInput = resolveTxInput({ amount, asset, txInput: providedTxInput, toAddress })
|
|
203
|
+
|
|
204
|
+
const baseProps = {
|
|
205
|
+
amount,
|
|
206
|
+
toAddress,
|
|
207
|
+
txInput,
|
|
208
|
+
txType,
|
|
209
|
+
txValue,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// When CEX swapping a main asset, the exchange may provide a txInput and an address. Use this address as the tx 'to' address.
|
|
213
|
+
// When DEX swapping a main asset, the exchange provides a txInput and a DEX address. Use the DEX address as the tx 'to' address.
|
|
214
|
+
// When sending a main asset, the transaction (tx) 'to' address is the receiver's address. No tx input is provided.
|
|
215
|
+
if (!isEthereumLikeToken(asset)) {
|
|
216
|
+
return assertCriticalTxAttributes({
|
|
217
|
+
...baseProps,
|
|
218
|
+
txToAddress: providedTxToAddress || toAddress,
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (providedTxInput) {
|
|
223
|
+
// When CEX swapping a token, the exchange may provide a txInput but not an address. In this case, the tx 'to' address is the token address.
|
|
224
|
+
// When DEX swapping a token, the exchange provides a txInput and a DEX address. Use the DEX address as the tx 'to' address.
|
|
225
|
+
const txToAddress = providedTxToAddress || providedToAddress || asset.contract?.address
|
|
226
|
+
return assertCriticalTxAttributes({ ...baseProps, txToAddress })
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// When sending a token, the tx 'to' address is the asset.contract?.address. No tx input is provided; it's resolved locally.
|
|
230
|
+
const txToAddress = asset.contract?.address
|
|
231
|
+
return assertCriticalTxAttributes({ ...baseProps, txToAddress })
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Normalizes the properties of transactions that can
|
|
235
|
+
// vary depending upon the `txType` and `asset` to
|
|
236
|
+
// ensure these evaluate consistently.
|
|
237
|
+
export const resolveTxAttributesByTxType = async ({
|
|
238
|
+
asset,
|
|
239
|
+
assetClientInterface,
|
|
240
|
+
amount: providedAmount,
|
|
241
|
+
fromAddress: providedFromAddress,
|
|
242
|
+
nonce: providedNonce,
|
|
243
|
+
txInput: providedTxInput,
|
|
244
|
+
txToAddress: providedTxToAddress,
|
|
245
|
+
toAddress: providedToAddress,
|
|
246
|
+
txValue: providedTxValue,
|
|
247
|
+
txType,
|
|
248
|
+
walletAccount,
|
|
249
|
+
}) => {
|
|
250
|
+
assert(isValidTxType(txType), 'expected valid txType')
|
|
251
|
+
|
|
252
|
+
const fromAddress = await resolveTxFromAddress({
|
|
253
|
+
asset,
|
|
254
|
+
assetClientInterface,
|
|
255
|
+
fromAddress: providedFromAddress,
|
|
256
|
+
walletAccount,
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const nonce = await resolveTxNonce({
|
|
260
|
+
asset,
|
|
261
|
+
fromAddress,
|
|
262
|
+
nonce: providedNonce,
|
|
263
|
+
walletAccount,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
return createResolvedTxAttributes({
|
|
267
|
+
asset,
|
|
268
|
+
fromAddress,
|
|
269
|
+
nonce,
|
|
270
|
+
...resolveCriticalTxAttributes({
|
|
271
|
+
asset,
|
|
272
|
+
amount: providedAmount,
|
|
273
|
+
toAddress: providedToAddress,
|
|
274
|
+
txInput: providedTxInput,
|
|
275
|
+
txToAddress: providedTxToAddress,
|
|
276
|
+
txValue: providedTxValue,
|
|
277
|
+
txType,
|
|
278
|
+
}),
|
|
279
|
+
})
|
|
280
|
+
}
|
package/src/watch-tx.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import delay from 'delay'
|
|
2
|
+
import ms from 'ms'
|
|
3
|
+
|
|
4
|
+
const WATCH_TX_DELAY = ms('5s')
|
|
5
|
+
const WATCH_TX_TIMEOUT = ms('5m')
|
|
6
|
+
|
|
7
|
+
// this could go to asset-lib
|
|
8
|
+
export const createWatchTx =
|
|
9
|
+
({
|
|
10
|
+
walletAccount,
|
|
11
|
+
getTxLog,
|
|
12
|
+
baseAsset,
|
|
13
|
+
watchTxDelay = WATCH_TX_DELAY,
|
|
14
|
+
watchTxTimeout = WATCH_TX_TIMEOUT,
|
|
15
|
+
}) =>
|
|
16
|
+
async (txId) => {
|
|
17
|
+
const start = Date.now()
|
|
18
|
+
let txConfirmed = false
|
|
19
|
+
let hasTimedOut = false
|
|
20
|
+
|
|
21
|
+
while (!hasTimedOut && !txConfirmed) {
|
|
22
|
+
await delay(watchTxDelay)
|
|
23
|
+
const baseAssetTxLog = await getTxLog({
|
|
24
|
+
assetName: baseAsset.name,
|
|
25
|
+
walletAccount,
|
|
26
|
+
})
|
|
27
|
+
txConfirmed = baseAssetTxLog.get(txId)?.confirmed
|
|
28
|
+
hasTimedOut = Date.now() - start > watchTxTimeout
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!txConfirmed) {
|
|
32
|
+
throw new Error("The transaction wasn't confirmed in time.")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { txId }
|
|
36
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
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
|
-
assert(
|
|
6
|
+
assert(asset, '"asset" should be passed.')
|
|
6
7
|
|
|
7
8
|
const simulateEVMMessage = createSimulateEVMMessage()
|
|
8
9
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
|
+
|
|
3
|
+
import { simulateTransactions as simulateEthereumTransactions } from '../simulation/simulate-transactions.js'
|
|
4
|
+
|
|
5
|
+
export const createSimulateTransactions = ({ asset }) => {
|
|
6
|
+
assert(asset, '"asset" should be passed.')
|
|
7
|
+
|
|
8
|
+
return function simulateTransaction({ transactions, ...restParameters }) {
|
|
9
|
+
return simulateEthereumTransactions({ asset, transactions, ...restParameters })
|
|
10
|
+
}
|
|
11
|
+
}
|
package/src/web3/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { createSimulateMessage } from './createSimulateMessage'
|
|
1
|
+
import { createSimulateMessage } from './createSimulateMessage.js'
|
|
2
|
+
import { createSimulateTransactions } from './createSimulateTransactions.js'
|
|
2
3
|
|
|
3
4
|
export const createWeb3API = (deps) => {
|
|
4
5
|
return {
|
|
5
6
|
simulateMessage: createSimulateMessage(deps),
|
|
7
|
+
simulateTransactions: createSimulateTransactions(deps),
|
|
6
8
|
}
|
|
7
9
|
}
|
package/src/etherscan/account.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import assert from 'minimalistic-assert'
|
|
2
|
-
import request from './request'
|
|
3
|
-
|
|
4
|
-
const isValidResponseCheck = (x) =>
|
|
5
|
-
(x.status === '1' && x.message === 'OK') || x.message === 'No transactions found'
|
|
6
|
-
const _request = async (...args) => request(isValidResponseCheck, 'account', ...args)
|
|
7
|
-
|
|
8
|
-
export async function fetchBalance(address) {
|
|
9
|
-
const balance = await _request('balance', { address })
|
|
10
|
-
|
|
11
|
-
const isValid = /^\d+$/.test(balance)
|
|
12
|
-
if (!isValid) throw new RangeError(`Invalid balance: ${balance}`)
|
|
13
|
-
|
|
14
|
-
return balance
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function fetchTxlist(address, options) {
|
|
18
|
-
const params = { startblock: 0, endblock: 'latest', ...options, address }
|
|
19
|
-
const txlist = await _request('txlist', params)
|
|
20
|
-
|
|
21
|
-
// simple check
|
|
22
|
-
assert(Array.isArray(txlist), `Invalid transactions: ${txlist}`)
|
|
23
|
-
|
|
24
|
-
return txlist
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function fetchTxlistinternal(address, options) {
|
|
28
|
-
const params = { startblock: 0, endblock: 'latest', ...options, address }
|
|
29
|
-
const txlist = await _request('txlistinternal', params)
|
|
30
|
-
|
|
31
|
-
// simple check
|
|
32
|
-
assert(Array.isArray(txlist), `Invalid transactions: ${txlist}`)
|
|
33
|
-
|
|
34
|
-
return txlist
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export async function tokenBalance(token, address) {
|
|
38
|
-
const params = {
|
|
39
|
-
[token.length === 42 ? 'contractaddress' : 'tokenname']: token,
|
|
40
|
-
address,
|
|
41
|
-
tag: 'latest',
|
|
42
|
-
}
|
|
43
|
-
const balance = await _request('tokenbalance', params)
|
|
44
|
-
|
|
45
|
-
const isValid = /^\d+$/.test(balance)
|
|
46
|
-
if (!isValid) throw new RangeError(`Invalid balance: ${balance}`)
|
|
47
|
-
|
|
48
|
-
return balance
|
|
49
|
-
}
|
package/src/etherscan/index.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import createWebSocket from './ws'
|
|
2
|
-
|
|
3
|
-
export const ETHERSCAN_WS_URL = 'wss://socket.etherscan.io/wshandler'
|
|
4
|
-
|
|
5
|
-
export const ws = createWebSocket(ETHERSCAN_WS_URL)
|
|
6
|
-
|
|
7
|
-
export function filterTxsSent(addr, etherscanTxs) {
|
|
8
|
-
return etherscanTxs.filter((tx) => tx.from.toLowerCase() === addr.toLowerCase())
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function filterTxsReceived(addr, etherscanTxs) {
|
|
12
|
-
return etherscanTxs.filter((tx) => tx.to.toLowerCase() === addr.toLowerCase())
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export { fetchBalance, fetchTxlistinternal, fetchTxlist, tokenBalance } from './account'
|
|
16
|
-
export {
|
|
17
|
-
sendRawTransaction,
|
|
18
|
-
getTransactionCount,
|
|
19
|
-
estimateGas,
|
|
20
|
-
getTransactionReceipt,
|
|
21
|
-
getCode,
|
|
22
|
-
ethCall,
|
|
23
|
-
gasPrice,
|
|
24
|
-
} from './proxy'
|
|
25
|
-
|
|
26
|
-
export { setEtherscanApiKey as setApiKey } from './request'
|
|
27
|
-
|
|
28
|
-
export { getLogs } from './logs'
|
package/src/etherscan/logs.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import assert from 'minimalistic-assert'
|
|
2
|
-
import request from './request'
|
|
3
|
-
|
|
4
|
-
const isValidResponseCheck = (x) =>
|
|
5
|
-
(x.status === '1' && x.message === 'OK') || x.message === 'No records found'
|
|
6
|
-
const _request = async (...args) => request(isValidResponseCheck, 'logs', ...args)
|
|
7
|
-
|
|
8
|
-
export async function getLogs(address, fromBlock, toBlock, options) {
|
|
9
|
-
const params = { ...options, address, fromBlock, toBlock }
|
|
10
|
-
const events = await _request('getLogs', params)
|
|
11
|
-
|
|
12
|
-
// simple check
|
|
13
|
-
assert(Array.isArray(events), `Invalid transactions: ${events}`)
|
|
14
|
-
|
|
15
|
-
return events
|
|
16
|
-
}
|
package/src/etherscan/proxy.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import request from './request'
|
|
2
|
-
|
|
3
|
-
const isValidResponseCheck = (x) => x.result !== undefined
|
|
4
|
-
const _request = async (...args) => request(isValidResponseCheck, 'proxy', ...args)
|
|
5
|
-
|
|
6
|
-
export async function sendRawTransaction(data) {
|
|
7
|
-
const txhash = await _request('eth_sendRawTransaction', { hex: '0x' + data })
|
|
8
|
-
|
|
9
|
-
const isValidTxHash = /^0x[\dA-Fa-f]{64}$/.test(txhash)
|
|
10
|
-
if (!isValidTxHash) throw new Error(`Invalid tx hash: ${txhash}`)
|
|
11
|
-
|
|
12
|
-
return txhash.slice(2)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function getTransactionCount(address, tag = 'latest') {
|
|
16
|
-
return _request('eth_getTransactionCount', { address, tag })
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function getTransactionReceipt(txhash) {
|
|
20
|
-
return _request('eth_getTransactionReceipt', { txhash })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function estimateGas(data) {
|
|
24
|
-
return _request('eth_estimateGas', data)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function getCode(address) {
|
|
28
|
-
const code = await _request('eth_getCode', { address })
|
|
29
|
-
|
|
30
|
-
const isValidCode = /^0x[\dA-Fa-f]*$/.test(code) && code.length % 2 === 0
|
|
31
|
-
if (!isValidCode) throw new Error(`Invalid address code: ${code}`)
|
|
32
|
-
|
|
33
|
-
return code
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function gasPrice() {
|
|
37
|
-
const price = await _request('eth_gasPrice')
|
|
38
|
-
|
|
39
|
-
const isValidPrice = /^0x[\dA-Fa-f]+$/.test(price)
|
|
40
|
-
if (!isValidPrice) throw new Error(`Invalid price: ${price}`)
|
|
41
|
-
|
|
42
|
-
return price
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function ethCall(data) {
|
|
46
|
-
return _request('eth_call', data)
|
|
47
|
-
}
|
package/src/etherscan/request.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import ms from 'ms'
|
|
2
|
-
import makeConcurrent from 'make-concurrent'
|
|
3
|
-
import fetchival from '@exodus/fetch/experimental/fetchival'
|
|
4
|
-
|
|
5
|
-
const ETHERSCAN_API_URL = 'https://api.etherscan.io/api'
|
|
6
|
-
const DEFAULT_ETHERSCAN_API_KEY = 'XM3VGRSNW1TMSIR14I9MVFP15X74GNHTRI'
|
|
7
|
-
|
|
8
|
-
let etherscanApiKey = DEFAULT_ETHERSCAN_API_KEY
|
|
9
|
-
|
|
10
|
-
export function setEtherscanApiKey(apiKey) {
|
|
11
|
-
etherscanApiKey = apiKey || DEFAULT_ETHERSCAN_API_KEY
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default makeConcurrent(
|
|
15
|
-
async function (isValidResponseCheck, module, action, params = {}) {
|
|
16
|
-
const data = await fetchival(new URL(ETHERSCAN_API_URL), { timeout: ms('15s') }).get({
|
|
17
|
-
...params,
|
|
18
|
-
module,
|
|
19
|
-
action,
|
|
20
|
-
apiKey: etherscanApiKey,
|
|
21
|
-
})
|
|
22
|
-
if (!isValidResponseCheck(data)) throw new Error(`Invalid response: ${JSON.stringify(data)}`)
|
|
23
|
-
return data.result
|
|
24
|
-
},
|
|
25
|
-
{ concurrency: 3 }
|
|
26
|
-
)
|
package/src/etherscan/ws.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import EventEmitter from 'events/' // '/' forces it to use the module from node_modules
|
|
2
|
-
import ms from 'ms'
|
|
3
|
-
import WebSocket from '../websocket'
|
|
4
|
-
|
|
5
|
-
const RECONNECT_INTERVAL = ms('10s')
|
|
6
|
-
const PING_INTERVAL = ms('20s')
|
|
7
|
-
|
|
8
|
-
export default function createWebSocket(url) {
|
|
9
|
-
const addresses = new Set()
|
|
10
|
-
const events = new EventEmitter().setMaxListeners(20)
|
|
11
|
-
const pingMessage = JSON.stringify({ event: 'ping' })
|
|
12
|
-
let ws
|
|
13
|
-
let wsOpened = false
|
|
14
|
-
let opened = false
|
|
15
|
-
let openTimeoutId
|
|
16
|
-
let pingIntervalId
|
|
17
|
-
|
|
18
|
-
function subscribeAddress(address) {
|
|
19
|
-
const data = JSON.stringify({ event: 'txlist', address })
|
|
20
|
-
ws.send(data)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function onMessage(data) {
|
|
24
|
-
data = JSON.parse(data)
|
|
25
|
-
switch (data.event) {
|
|
26
|
-
case 'txlist':
|
|
27
|
-
for (const tx of data.result) events.emit(`address-${data.address}`, tx)
|
|
28
|
-
break
|
|
29
|
-
|
|
30
|
-
case 'subscribe-txlist':
|
|
31
|
-
const match = data.message.toLowerCase().match(/0x[\da-f]{40}/)
|
|
32
|
-
if (match && data.status === '1') events.emit(`address-${match[0]}-subscribed`)
|
|
33
|
-
else ws.close()
|
|
34
|
-
break
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function isOpened() {
|
|
39
|
-
return wsOpened
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function open() {
|
|
43
|
-
opened = true
|
|
44
|
-
clearTimeout(openTimeoutId)
|
|
45
|
-
if (ws) return
|
|
46
|
-
|
|
47
|
-
ws = new WebSocket(url)
|
|
48
|
-
|
|
49
|
-
ws.on('message', (data) => {
|
|
50
|
-
try {
|
|
51
|
-
onMessage(data)
|
|
52
|
-
} catch {}
|
|
53
|
-
})
|
|
54
|
-
ws.once('open', () => {
|
|
55
|
-
for (const address of addresses.values()) subscribeAddress(address)
|
|
56
|
-
pingIntervalId = setInterval(() => ws && ws.send(pingMessage), PING_INTERVAL)
|
|
57
|
-
wsOpened = true
|
|
58
|
-
events.emit('open')
|
|
59
|
-
})
|
|
60
|
-
ws.once('close', () => {
|
|
61
|
-
ws = null
|
|
62
|
-
clearInterval(pingIntervalId)
|
|
63
|
-
if (opened) openTimeoutId = setTimeout(open, RECONNECT_INTERVAL)
|
|
64
|
-
wsOpened = false
|
|
65
|
-
events.emit('close')
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function close() {
|
|
70
|
-
opened = false
|
|
71
|
-
clearTimeout(openTimeoutId)
|
|
72
|
-
if (!ws) return
|
|
73
|
-
|
|
74
|
-
ws.close()
|
|
75
|
-
ws = null
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function watch(address) {
|
|
79
|
-
address = address.toLowerCase()
|
|
80
|
-
|
|
81
|
-
if (addresses.has(address)) return
|
|
82
|
-
addresses.add(address)
|
|
83
|
-
|
|
84
|
-
if (wsOpened) subscribeAddress(address)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return { events, isOpened, open, close, watch }
|
|
88
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { create } from './api'
|
|
2
|
-
import ClarityServer from './clarity'
|
|
3
|
-
import ApiCoinNodesServer from './api-coin-nodes'
|
|
4
|
-
import assert from 'minimalistic-assert'
|
|
5
|
-
import { serverMap } from './server-map'
|
|
6
|
-
|
|
7
|
-
function basicCreateEvmServer({ assetName, serverUrl, monitorType }) {
|
|
8
|
-
assert(assetName, 'assetName is required')
|
|
9
|
-
assert(serverUrl, 'serverUrl is required')
|
|
10
|
-
assert(monitorType, 'monitorType is required')
|
|
11
|
-
switch (monitorType) {
|
|
12
|
-
case 'no-history':
|
|
13
|
-
return new ApiCoinNodesServer({ uri: serverUrl })
|
|
14
|
-
case 'clarity':
|
|
15
|
-
return new ClarityServer({ baseAssetName: assetName, uri: serverUrl })
|
|
16
|
-
case 'magnifier':
|
|
17
|
-
return create(serverUrl, assetName)
|
|
18
|
-
default:
|
|
19
|
-
throw new Error(`No server API found for ${assetName} and type ${monitorType}`)
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function createEvmServer({ assetName, serverUrl, monitorType }) {
|
|
24
|
-
if (serverMap[assetName]) return serverMap[assetName]
|
|
25
|
-
const server = basicCreateEvmServer({ assetName, serverUrl, monitorType })
|
|
26
|
-
serverMap[assetName] = server
|
|
27
|
-
return server
|
|
28
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// @Deprecated
|
|
2
|
-
export const serverMap = Object.create(null)
|
|
3
|
-
|
|
4
|
-
export function getServer(asset) {
|
|
5
|
-
const server = asset.baseAsset.server
|
|
6
|
-
if (server) {
|
|
7
|
-
return server
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// legacy way using the global serverMap
|
|
11
|
-
return getServerByName(asset.baseAsset.name)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function getServerByName(baseAssetName) {
|
|
15
|
-
console.warn(
|
|
16
|
-
`Asking ${baseAssetName} server by name, please use asset.baseAsset.server or getServer(asset)`
|
|
17
|
-
)
|
|
18
|
-
const server = serverMap[baseAssetName]
|
|
19
|
-
if (!server) throw new Error(`Unsupported base asset ${baseAssetName}`)
|
|
20
|
-
return server
|
|
21
|
-
}
|