@exodus/ethereum-api 8.7.1 → 8.9.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 CHANGED
@@ -3,6 +3,34 @@
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.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.8.0...@exodus/ethereum-api@8.9.0) (2024-07-03)
7
+
8
+
9
+ ### Features
10
+
11
+ * dynamic evm factory ([#2659](https://github.com/ExodusMovement/assets/issues/2659)) ([0baae82](https://github.com/ExodusMovement/assets/commit/0baae82a7f53c8808c9cd2621cf04e9960568cc6))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **ethereum:** false dropped tx check ([#2707](https://github.com/ExodusMovement/assets/issues/2707)) ([d05a8c8](https://github.com/ExodusMovement/assets/commit/d05a8c895563fb13120956c0081db7ebe86195d6))
17
+
18
+
19
+
20
+ ## [8.8.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.7.1...@exodus/ethereum-api@8.8.0) (2024-07-02)
21
+
22
+
23
+ ### Features
24
+
25
+ * Attach some error reasons to create tx ([#2649](https://github.com/ExodusMovement/assets/issues/2649)) ([0f783e2](https://github.com/ExodusMovement/assets/commit/0f783e2a40079ce407b366e0f6411d279c1fb0ce)), closes [#2646](https://github.com/ExodusMovement/assets/issues/2646)
26
+
27
+
28
+ ### Bug Fixes
29
+
30
+ * ethereum-api default url in servers ([#2689](https://github.com/ExodusMovement/assets/issues/2689)) ([2fc70a4](https://github.com/ExodusMovement/assets/commit/2fc70a49c05f18f6153009b1cebdd9ea522509ac))
31
+
32
+
33
+
6
34
  ## [8.7.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.7.0...@exodus/ethereum-api@8.7.1) (2024-06-25)
7
35
 
8
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.7.1",
3
+ "version": "8.9.0",
4
4
  "description": "Ethereum Api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -20,6 +20,7 @@
20
20
  "lint:fix": "yarn lint --fix"
21
21
  },
22
22
  "dependencies": {
23
+ "@exodus/asset": "^1.2.0",
23
24
  "@exodus/asset-lib": "^4.2.0",
24
25
  "@exodus/assets": "^9.1.1",
25
26
  "@exodus/basic-utils": "^2.1.0",
@@ -48,7 +49,6 @@
48
49
  "ws": "^6.1.0"
49
50
  },
50
51
  "devDependencies": {
51
- "@exodus/assets": "^9.1.1",
52
52
  "@exodus/assets-testing": "^1.0.0",
53
53
  "@exodus/bsc-meta": "^1.2.2",
54
54
  "@exodus/ethereumarbone-meta": "^1.2.0",
@@ -66,5 +66,5 @@
66
66
  "type": "git",
67
67
  "url": "git+https://github.com/ExodusMovement/assets.git"
68
68
  },
69
- "gitHead": "0ff8ab06fea52301cc926c2b78ab812992cc5692"
69
+ "gitHead": "237c799e9bc8df6574be95056a999896e2503290"
70
70
  }
@@ -0,0 +1,162 @@
1
+ import { createMetaDef } from '@exodus/asset'
2
+ import { FeeData } from '@exodus/asset-lib'
3
+ import { createAssetFactory } from './create-asset'
4
+ import assert from 'minimalistic-assert'
5
+ import { UnitType } from '@exodus/currency'
6
+
7
+ // Move to ethereum-api for config base evm
8
+ export const createAssetPluginFactory = (config) => {
9
+ assert(config, 'config is required')
10
+ assert(config.chainId, 'chainId is required')
11
+ assert(config.meta, 'config.meta is required')
12
+ assert(config.meta.explorerUrl, 'meta.explorerUrl is required')
13
+
14
+ const chainId = config.chainId
15
+ const { explorerUrl, ...meta } = config.meta
16
+ const name = `evm_${chainId}`
17
+ const ticker = `EVM${chainId}`
18
+ const tokenType = `EVM_${chainId}_TOKEN`
19
+
20
+ const units = {
21
+ wei: 0,
22
+ Kwei: 3,
23
+ Mwei: 6,
24
+ Gwei: 9,
25
+ [ticker]: config.decimals || 18,
26
+ }
27
+
28
+ const currency = UnitType.create(units)
29
+
30
+ // adds lots of validation in config
31
+ const tokenOverrides = (token) => ({
32
+ ...token,
33
+ assetType: tokenType,
34
+ contract: token.addresses,
35
+ gasLimit: meta.gasLimit || 120e3,
36
+ })
37
+
38
+ const blockExplorer = {
39
+ addressUrl: (address) => `${explorerUrl}/address/${encodeURIComponent(address)}`,
40
+ txUrl: (txId) => `${explorerUrl}/tx/${encodeURIComponent(txId)}`,
41
+ }
42
+
43
+ const assetType = 'ETHEREUM_LIKE'
44
+ const { asset, assetsList } = createMetaDef({
45
+ assetParams: {
46
+ ...meta,
47
+ name,
48
+ ticker,
49
+ units,
50
+ chainId,
51
+ assetType,
52
+ blockExplorer,
53
+ },
54
+ tokensParams: meta.tokens || [],
55
+ tokenOverrides,
56
+ })
57
+
58
+ const resolveFeeDataConfigDefaults = () => {
59
+ const shared = {
60
+ tipGasPrice: '0 Gwei',
61
+ fuelThreshold: '0 Gwei',
62
+ gasPriceEconomicalRate: 0.8,
63
+ gasPriceMinimumRate: 0.6,
64
+ }
65
+
66
+ if (config.eip1559Enabled === true) {
67
+ assert(config.baseFeePerGas, 'baseFeePerGas is required')
68
+ const baseFeePerGas = currency.parse(config.baseFeePerGas)
69
+ const gasPrice = config.gasPrice ? currency.parse(config.gasPrice) : baseFeePerGas.mul(1.5)
70
+ return {
71
+ gasPrice: gasPrice.toBaseString({ unit: true }),
72
+ baseFeePerGas: baseFeePerGas.toBaseString({ unit: true }),
73
+ max: gasPrice.mul(5).toBaseString({ unit: true }),
74
+ min: gasPrice.div(5).toBaseString({ unit: true }),
75
+ eip1559Enabled: config.eip1559Enabled,
76
+ ...shared,
77
+ ...config.feeData,
78
+ }
79
+ }
80
+
81
+ if (config.eip1559Enabled === false) {
82
+ assert(config.gasPrice, 'gasPrice is required')
83
+ const gasPrice = currency.parse(config.gasPrice)
84
+ return {
85
+ gasPrice: gasPrice.toBaseString({ unit: true }),
86
+ max: gasPrice.mul(5).toBaseString({ unit: true }),
87
+ min: gasPrice.div(5).toBaseString({ unit: true }),
88
+ eip1559Enabled: config.eip1559Enabled,
89
+ ...shared,
90
+ ...config.feeData,
91
+ }
92
+ }
93
+
94
+ return {
95
+ gasPrice: '0.02 Gwei',
96
+ baseFeePerGas: '0.02 Gwei',
97
+ eip1559Enabled: true,
98
+ ...shared,
99
+ ...config.feeData,
100
+ }
101
+ }
102
+
103
+ const feeData = new FeeData({
104
+ config: resolveFeeDataConfigDefaults(),
105
+ mainKey: 'gasPrice',
106
+ currency: asset.currency,
107
+ })
108
+
109
+ const createAsset = createAssetFactory({
110
+ assetsList,
111
+ feeData,
112
+ ...config.plugin,
113
+ })
114
+
115
+ return { name, chainId, createAsset }
116
+ }
117
+
118
+ // https://docs.metamask.io/wallet/reference/wallet_addethereumchain/
119
+ export const fromAddEthereumChainParameterToFactoryParams = (params) => {
120
+ assert(params.chainId, 'chainId is required')
121
+ assert(params.chainName, 'chainName is required')
122
+ assert(params.rpcUrls?.[0], 'rpcUrls is required')
123
+ assert(params.blockExplorerUrls?.[0], 'blockExplorerUrls is required')
124
+ assert(params.nativeCurrency, 'nativeCurrency is required')
125
+ assert(params.nativeCurrency.symbol, 'nativeCurrency.symbol is required')
126
+ assert(params.nativeCurrency.decimals, 'nativeCurrency.decimals is required')
127
+
128
+ const chainId = parseInt(params.chainId.replace('0x', ''), 16)
129
+
130
+ // obvious coloring:)
131
+ const color =
132
+ params.color ||
133
+ '#' +
134
+ (chainId % 256).toString(16) +
135
+ ((chainId + 50) % 256).toString(16) +
136
+ ((chainId + 100) % 256).toString(16)
137
+ return {
138
+ chainId,
139
+ meta: {
140
+ explorerUrl: params.blockExplorerUrls[0],
141
+ displayName: params.chainName,
142
+ displayTicker: params.nativeCurrency.name || params.nativeCurrency.symbol,
143
+ primaryColor: color,
144
+ gradientColors: [color, color],
145
+ gradientCoords: {
146
+ x1: '0%',
147
+ y1: '0%',
148
+ x2: '100%',
149
+ y2: '100%',
150
+ },
151
+ },
152
+ plugin: {
153
+ serverUrl: params.rpcUrls?.[0],
154
+ confirmationsNumber: 5,
155
+ monitorType: 'no-history',
156
+ isTestnet: params.isTestnet,
157
+ },
158
+ baseFeePerGas: params.baseFeePerGas,
159
+ gasPrice: params.gasPrice,
160
+ eip1559Enabled: params.eip1559Enabled,
161
+ }
162
+ }
@@ -1,7 +1,7 @@
1
1
  import { connectAssetsList } from '@exodus/assets'
2
2
  import { pick } from '@exodus/basic-utils'
3
3
  import bip44Constants from '@exodus/bip44-constants/by-ticker'
4
- import { getBalances } from './get-balances'
4
+ import { getBalancesFactory } from './get-balances'
5
5
  import { ClarityMonitor } from './tx-log/clarity-monitor'
6
6
  import { EthereumNoHistoryMonitor } from './tx-log/ethereum-no-history-monitor'
7
7
  import { EthereumMonitor } from './tx-log/ethereum-monitor'
@@ -108,7 +108,12 @@ export const createAssetFactory = ({
108
108
  encodePublic: encodePublicFactory({ chainId, useEip1191ChainIdChecksum }),
109
109
  }
110
110
 
111
- const { createToken, getTokens } = createTokenFactory({ address, bip44, keys }, assets)
111
+ const getBalances = getBalancesFactory({ monitorType })
112
+
113
+ const { createToken, getTokens } = createTokenFactory(
114
+ { address, bip44, keys, getBalances },
115
+ assets
116
+ )
112
117
 
113
118
  const addressHasHistory = addressHasHistoryFactory({ server })
114
119
 
@@ -1,9 +1,8 @@
1
1
  import { isPolygonClaimUndelegate, isPolygonDelegate, isPolygonUndelegate } from './staking/matic'
2
2
  import { createContract } from '@exodus/ethereum-lib'
3
3
 
4
- import { getBalances } from './get-balances'
5
-
6
4
  import { createStakingApi } from './staking-api'
5
+ import assert from 'minimalistic-assert'
7
6
 
8
7
  const defaultTokenFeatures = {
9
8
  isMaxFeeAsset: true,
@@ -30,8 +29,9 @@ const getPolygonActivityTxs = ({ txs }) =>
30
29
  const getActivityTxs = ({ txs }) => txs.filter((tx) => !smallbalanceTx(tx))
31
30
 
32
31
  const getCreateBaseToken =
33
- (props) =>
32
+ ({ getBalances, ...props }) =>
34
33
  ({ name, contract, features, ...tokenDef }) => {
34
+ assert(getBalances, 'getBalances is required')
35
35
  const tokenSpecificFeatures = name === 'polygon' ? { staking: {} } : {}
36
36
  const tokenSpecificApiFunctions =
37
37
  name === 'polygon' ? { staking: createStakingApi({ network: name }) } : {}
@@ -0,0 +1,89 @@
1
+ export const reasons = {
2
+ fetchGasLimitFailed: 'Fetch gas limit failed',
3
+ nonceFetchFailed: 'Nonce fetch failed',
4
+ balanceFetchFailed: 'Balance fetch failed',
5
+ broadcastTxFailed: 'Broadcast tx failed',
6
+ getTransactionByHashFailed: 'Get transaction by hash failed',
7
+ ethCallErc20Failed: 'Eth call erc20 failed',
8
+ insufficientFunds: 'Insufficient funds',
9
+ bumpTxFailed: 'Bump tx failed',
10
+ }
11
+
12
+ const MAX_HINT_LENGTH = 100
13
+ // TODO: move this to be an generic error for all assets
14
+ export class EthLikeError extends Error {
15
+ #hintStack
16
+
17
+ /**
18
+ * Creates an instance of EthLikeError.
19
+ *
20
+ * @param {string} message - Standard error message.
21
+ * @param {string} tag - Tag associated with the error.
22
+ * @param {string} reason - A constant indicating the generic failure. Must not contain any sensitive information such as private keys, transaction IDs, or wallet addresses.
23
+ * @param {string} hint - A hint to help the user understand the error. Must not contain any sensitive information such as private keys, transaction IDs, or wallet addresses.
24
+ */
25
+ constructor({ message, reason, hint }) {
26
+ super(message)
27
+ this.name = 'EthLikeError'
28
+ this.#hintStack = [hint] // NOTE: we can add more hints to the stack
29
+ this.reason = reason
30
+ this.hint = this.#extractHint(hint)
31
+ }
32
+
33
+ addHint = (hint) => {
34
+ const filteredHint = this.#extractHint(hint)
35
+ if (!filteredHint) {
36
+ return this
37
+ }
38
+
39
+ this.#hintStack.push(hint)
40
+ this.hint = `${this.#hintStack.join(':')}`
41
+ return this
42
+ }
43
+
44
+ // NOTE: a class method for now, if we move this to be a generic error for all assets, each asset will have its own implementation
45
+ #extractHint = (hint) => {
46
+ if (!hint) return ''
47
+
48
+ // Define regex patterns for sensitive information
49
+ const sensitivePatterns = [
50
+ /(?:0x)?[\dA-Fa-f]{64}/g, // Pattern for transaction hashes (hex string of 64 characters, optional 0x prefix)
51
+ /(?:0x)?[\dA-Fa-f]{40}/g, // Pattern for wallet addresses (hex string of 40 characters, optional 0x prefix)
52
+ ]
53
+
54
+ let filteredHint = hint
55
+
56
+ // Check for sensitive information
57
+ const containsSensitiveInfo = sensitivePatterns.some((pattern) => pattern.test(filteredHint))
58
+
59
+ if (containsSensitiveInfo) {
60
+ console.warn('Hint contains sensitive information and will be ignored.')
61
+ return ''
62
+ }
63
+
64
+ // Limit length to 100 characters
65
+ if (filteredHint.length > MAX_HINT_LENGTH) {
66
+ filteredHint = filteredHint.slice(0, MAX_HINT_LENGTH - 3) + '...'
67
+ }
68
+
69
+ return filteredHint
70
+ }
71
+ }
72
+
73
+ export const withErrorReason = async (promise, errorReason, hint) => {
74
+ try {
75
+ return await promise
76
+ } catch (err) {
77
+ if (err instanceof EthLikeError) {
78
+ // ignore errorReason and add hint to stack
79
+ err.addHint(hint)
80
+ throw err
81
+ }
82
+
83
+ throw new EthLikeError({
84
+ message: err.message,
85
+ reason: errorReason,
86
+ hint,
87
+ })
88
+ }
89
+ }
@@ -4,6 +4,7 @@ import { memoizeLruCache } from '@exodus/asset-lib'
4
4
  import SolidityContract from '@exodus/solidity-contract'
5
5
  import { getServerByName, getServer } from './exodus-eth-server'
6
6
  import { fromHexToString } from './number-utils'
7
+ import * as ErrorWrapper from './error-wrapper'
7
8
 
8
9
  export async function isContract(baseAssetName, address) {
9
10
  return getServerByName(baseAssetName).isContract(address)
@@ -35,17 +36,27 @@ export const isForwarderContractCached = memoizeLruCache(
35
36
 
36
37
  export async function getNonce({ asset, address, tag = 'latest' }) {
37
38
  const server = getServer(asset)
38
- const nonce = await server.getTransactionCount(address, tag)
39
+ const nonce = await ErrorWrapper.withErrorReason(
40
+ server.getTransactionCount(address, tag),
41
+ ErrorWrapper.reasons.nonceFetchFailed,
42
+ 'getNonce'
43
+ )
44
+
39
45
  return parseInt(nonce, 16)
40
46
  }
41
47
 
42
48
  export async function estimateGas({ asset, ...args }) {
43
- return getServer(asset).estimateGas(args)
49
+ return ErrorWrapper.withErrorReason(
50
+ getServer(asset).estimateGas(args),
51
+ ErrorWrapper.reasons.fetchGasLimitFailed,
52
+ 'estimateGas'
53
+ )
44
54
  }
45
55
 
46
56
  // Only base assets, not tokens
47
57
  export async function getBalance({ asset, address }) {
48
58
  if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
59
+
49
60
  const server = getServer(asset)
50
61
  const balances = await server.getBalance(address)
51
62
  return balances?.confirmed?.value || '0'
@@ -54,6 +65,7 @@ export async function getBalance({ asset, address }) {
54
65
  // Only base assets, not tokens
55
66
  export async function getBalanceProxied({ asset, address, tag = 'latest' }) {
56
67
  if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
68
+
57
69
  const result = await getServer(asset).getBalanceProxied(address)
58
70
  return fromHexToString(result)
59
71
  }
@@ -61,6 +73,7 @@ export async function getBalanceProxied({ asset, address, tag = 'latest' }) {
61
73
  // Only ETH-like assets with token support
62
74
  export async function getTokenBalance({ asset, address }) {
63
75
  if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
76
+
64
77
  const server = getServer(asset)
65
78
  const balances = await server.getBalance(address)
66
79
  const contractAddress = asset.contract.address.toLowerCase()
@@ -69,6 +82,7 @@ export async function getTokenBalance({ asset, address }) {
69
82
 
70
83
  export async function getTokenBalanceFromNode({ asset, address }) {
71
84
  if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
85
+
72
86
  const server = getServer(asset)
73
87
  const contractAddress = asset.contract.address.toLowerCase()
74
88
  const balances = await server.balanceOf(address, contractAddress)
@@ -83,7 +97,7 @@ export function sendRawTransaction(asset) {
83
97
  export async function transactionExists({ asset, txId }) {
84
98
  const server = getServer(asset)
85
99
  txId = normalizeTxId(txId)
86
- const txResult = await server.getTransactionByHash(txId)
100
+ const txResult = server.getTransactionByHash(txId)
87
101
  return txResult && txResult.hash === txId
88
102
  }
89
103
 
@@ -122,12 +136,18 @@ export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARA
122
136
  callResponse = await server.ethCall({ to: address, data: ERC20[method].methodId })
123
137
  } catch (err) {
124
138
  if (err.message === 'execution reverted') {
125
- throw new Error(
126
- `Can't find parameters for contract with address ${address}. Are you sure it is a valid ERC20 contract?`
127
- )
139
+ throw new ErrorWrapper.EthLikeError({
140
+ message: `Can't find parameters for contract with address ${address}. Are you sure it is a valid ERC20 contract?`,
141
+ reason: ErrorWrapper.reasons.ethCallErc20Failed,
142
+ hint: 'ethCall:executionReverted',
143
+ })
128
144
  }
129
145
 
130
- throw new Error(err.message)
146
+ throw new ErrorWrapper.EthLikeError({
147
+ message: err.message,
148
+ reason: ErrorWrapper.reasons.ethCallErc20Failed,
149
+ hint: 'ethCall',
150
+ })
131
151
  }
132
152
 
133
153
  if (method === 'decimals') return parseInt(callResponse)
@@ -9,6 +9,7 @@ export default class ApiCoinNodesServer extends EventEmitter {
9
9
  constructor({ uri }) {
10
10
  super()
11
11
  this.uri = uri
12
+ this.defaultUri = uri
12
13
  this.id = 0
13
14
  }
14
15
 
@@ -9,6 +9,7 @@ export default class ClarityServer extends EventEmitter {
9
9
  super()
10
10
  this.baseAssetName = baseAssetName
11
11
  this.uri = uri
12
+ this.defaultUri = uri
12
13
  this.baseNamespace = `/v1/${this.baseAssetName}`
13
14
  this.sockets = Object.create(null)
14
15
  this.id = 0
@@ -102,6 +102,7 @@ export async function fetchGasLimit({
102
102
  )
103
103
  } catch (err) {
104
104
  if (throwOnError) throw err
105
+
105
106
  console.error('fetchGasLimit error', err)
106
107
 
107
108
  // fallback value for contract case
@@ -1,6 +1,8 @@
1
1
  import { isRpcBalanceAsset } from '@exodus/ethereum-lib'
2
2
 
3
3
  import { get } from 'lodash'
4
+ import assert from 'minimalistic-assert'
5
+
4
6
  import {
5
7
  getBalanceFromAccountState,
6
8
  getBalanceFromTxLog,
@@ -30,36 +32,40 @@ const getUnstaked = ({ accountState, asset }) => {
30
32
  * @param accountState the account state when the balance is loaded from RPC
31
33
  * @returns {{balance}|null} an object with the balance or null if the balance is unknown
32
34
  */
33
- export const getBalances = ({ asset, txLog, accountState }) => {
34
- const unconfirmedReceived = getUnconfirmedReceivedBalance({ asset, txLog })
35
- const unconfirmedSent = getUnconfirmedSentBalance({ asset, txLog })
35
+ export const getBalancesFactory = ({ monitorType }) => {
36
+ assert(monitorType, 'monitorType is required')
37
+ return ({ asset, txLog, accountState }) => {
38
+ const unconfirmedReceived = getUnconfirmedReceivedBalance({ asset, txLog })
39
+ const unconfirmedSent = getUnconfirmedSentBalance({ asset, txLog })
36
40
 
37
- // balance from txLog / rpc is considered to be total
38
- const balanceWithoutUnconfirmedSent = isRpcBalanceAsset(asset)
39
- ? getBalanceFromAccountState({ asset, accountState }).sub(unconfirmedSent)
40
- : getBalanceFromTxLog({ txLog, asset })
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 })
41
46
 
42
- const staked = getStaked({ asset, accountState })
43
- const unstaking = getUnstaking({ asset, accountState })
44
- const unstaked = getUnstaked({ asset, accountState })
45
- const total = balanceWithoutUnconfirmedSent
46
- const spendable = balanceWithoutUnconfirmedSent
47
- .sub(unconfirmedReceived)
48
- .sub(staked)
49
- .sub(unstaking)
50
- .sub(unstaked)
47
+ const staked = getStaked({ asset, accountState })
48
+ const unstaking = getUnstaking({ asset, accountState })
49
+ const unstaked = getUnstaked({ asset, accountState })
50
+ const total = balanceWithoutUnconfirmedSent
51
+ const spendable = balanceWithoutUnconfirmedSent
52
+ .sub(unconfirmedReceived)
53
+ .sub(staked)
54
+ .sub(unstaking)
55
+ .sub(unstaked)
51
56
 
52
- return {
53
- // new
54
- spendable,
55
- total,
56
- staked,
57
- unstaking,
58
- unstaked,
59
- unconfirmedReceived,
60
- unconfirmedSent,
61
- // legacy
62
- balance: total,
63
- spendableBalance: spendable,
57
+ return {
58
+ // new
59
+ spendable,
60
+ total,
61
+ staked,
62
+ unstaking,
63
+ unstaked,
64
+ unconfirmedReceived,
65
+ unconfirmedSent,
66
+ // legacy
67
+ balance: total,
68
+ spendableBalance: spendable,
69
+ }
64
70
  }
65
71
  }
package/src/index.js CHANGED
@@ -11,5 +11,10 @@ export * from './simulate-tx'
11
11
  export * from './allowance'
12
12
  export * from './optimism-gas'
13
13
  export * from './number-utils'
14
+ export { reasons as errorReasons, withErrorReason, EthLikeError } from './error-wrapper'
14
15
  export { txSendFactory, getFeeInfo } from './tx-send'
15
16
  export { createAssetFactory } from './create-asset'
17
+ export {
18
+ createAssetPluginFactory,
19
+ fromAddEthereumChainParameterToFactoryParams,
20
+ } from './create-asset-plugin-factory'
package/src/nft-utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import SolidityContract from '@exodus/solidity-contract'
2
2
  import { fetchGasLimit } from './gas-estimation'
3
3
  import assert from 'minimalistic-assert'
4
+ import * as ErrorWrapper from './error-wrapper'
4
5
 
5
6
  export const getNftArguments = async ({ asset, nft, fromAddress, toAddress }) => {
6
7
  assert(asset, 'asset is required')
@@ -43,7 +44,11 @@ export const getNftArguments = async ({ asset, nft, fromAddress, toAddress }) =>
43
44
  }
44
45
  })
45
46
  ).catch((e) => {
46
- throw new Error(errors.join('\n'))
47
+ throw new ErrorWrapper.EthLikeError({
48
+ message: errors.join('\n'),
49
+ reason: ErrorWrapper.reasons.fetchGasLimitFailed,
50
+ hint: 'getNftArguments',
51
+ })
47
52
  })
48
53
  return {
49
54
  contractAddress,
@@ -26,11 +26,13 @@ export class ClarityMonitor extends BaseMonitor {
26
26
  }
27
27
 
28
28
  setServer(config) {
29
- if (!config?.server || config.server === this.server.uri) {
29
+ const uri = config?.server || this.server.defaultUri
30
+
31
+ if (uri === this.server.uri) {
30
32
  return
31
33
  }
32
34
 
33
- this.server.setURI(config.server)
35
+ this.server.setURI(uri)
34
36
  this.subscribeWalletAddresses()
35
37
  if (this.config.GAS_PRICE_FROM_WEBSOCKET) {
36
38
  this.server.connectFee()
@@ -1,4 +1,3 @@
1
- import { getServer } from '../exodus-eth-server'
2
1
  import { isRpcBalanceAsset, getAssetAddresses } from '@exodus/ethereum-lib'
3
2
 
4
3
  import {
@@ -28,7 +27,7 @@ import { fromHexToString } from '../number-utils'
28
27
  export class EthereumMonitor extends BaseMonitor {
29
28
  constructor({ server, config, ...args }) {
30
29
  super(args)
31
- this.server = server || getServer(this.asset)
30
+ this.server = server
32
31
  this.config = { GAS_PRICE_FROM_WEBSOCKET: true, ...config }
33
32
  this.deriveDataNeededForTick = getDeriveDataNeededForTick(this.aci)
34
33
  this.deriveTransactionsToCheck = getDeriveTransactionsToCheck({
@@ -41,9 +40,13 @@ export class EthereumMonitor extends BaseMonitor {
41
40
  }
42
41
 
43
42
  setServer(config = {}) {
44
- if (config.serverv1 && this.server.getURL() !== config.serverv1) {
45
- this.server.setURL(config.serverv1)
43
+ const uri = config?.serverv1 || this.server.defaultUri
44
+
45
+ if (uri === this.server.getURL()) {
46
+ return
46
47
  }
48
+
49
+ this.server.setURL(uri)
47
50
  }
48
51
 
49
52
  async deriveData({ assetSource, tokens }) {
@@ -120,8 +123,8 @@ export class EthereumMonitor extends BaseMonitor {
120
123
  // FIXME: Fetching unconfirmed txs from node helps to avoid
121
124
  // flagging on-chain confirmed txs as dropped in the wallet.
122
125
  // Once the real bug is found, remove this logic
123
- const response = await this.server.getTransactionByHash(tx.txId)
124
- const isTxConfirmed = response?.result?.blockNumber !== null
126
+ const rpcTx = await this.server.getTransactionByHash(tx.txId)
127
+ const isTxConfirmed = !!rpcTx?.blockNumber
125
128
  this.logger.warn(`tx ${tx.txId} confirmed: ${isTxConfirmed}`)
126
129
  await updateTx(tx, assetName, { isTxConfirmed })
127
130
  }
@@ -1,6 +1,3 @@
1
- import { getServer } from '../exodus-eth-server'
2
- // eslint-disable-next-line import/no-deprecated
3
- import { DEFAULT_SERVER_URLS } from '@exodus/ethereum-lib'
4
1
  import { Tx } from '@exodus/models'
5
2
 
6
3
  import {
@@ -22,7 +19,7 @@ import { SynchronizedTime } from '@exodus/basic-utils'
22
19
  export class EthereumNoHistoryMonitor extends BaseMonitor {
23
20
  constructor({ server, config, ...args }) {
24
21
  super(args)
25
- this.server = server || getServer(this.asset)
22
+ this.server = server
26
23
  this.config = { ...config }
27
24
  this.deriveDataNeededForTick = getDeriveDataNeededForTick(this.aci)
28
25
  this.deriveTransactionsToCheck = getDeriveTransactionsToCheck({
@@ -31,14 +28,13 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
31
28
  }
32
29
 
33
30
  setServer(config) {
34
- if (!config?.server) {
35
- // eslint-disable-next-line import/no-deprecated
36
- this.server.setURI(DEFAULT_SERVER_URLS[this.asset.name])
31
+ const uri = config?.server || this.server.defaultUri
32
+
33
+ if (uri === this.server.uri) {
37
34
  return
38
35
  }
39
36
 
40
- if (config.server === this.server.uri) return
41
- this.server.setURI(config.server)
37
+ this.server.setURI(uri)
42
38
  }
43
39
 
44
40
  async getBalances({ tokens, ourWalletAddress }) {
@@ -5,6 +5,7 @@ import { calculateBumpedGasPrice, isEthereumLikeToken, normalizeTxId } from '@ex
5
5
  import assert from 'minimalistic-assert'
6
6
  import getFeeInfo from './get-fee-info'
7
7
  import { getNftArguments } from '../nft-utils'
8
+ import * as ErrorWrapper from '../error-wrapper'
8
9
 
9
10
  const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
10
11
  assert(assetClientInterface, 'assetClientInterface is required')
@@ -127,20 +128,56 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
127
128
  await baseAsset.api.broadcastTx(rawTx.toString('hex'))
128
129
  } catch (err) {
129
130
  const nonceTooLowErr = err.message.match(/nonce (is |)too low/i)
131
+ const insufficientFundsErr = err.message.match(/insufficient funds/i)
130
132
  const txAlreadyExists = nonceTooLowErr
131
133
  ? await transactionExists({ asset, txId })
132
134
  : err.message.match(/already known/i)
133
135
 
134
136
  if (txAlreadyExists) {
135
137
  console.info('tx already broadcast') // inject logger factory from platform
136
- } else if (bumpTxId || !nonceTooLowErr) {
137
- throw err
138
+ } else if (insufficientFundsErr) {
139
+ throw new ErrorWrapper.EthLikeError({
140
+ message: err.message,
141
+ reason: ErrorWrapper.reasons.insufficientFunds,
142
+ hint: 'broadcastTx',
143
+ })
144
+ } else if (bumpTxId) {
145
+ throw new ErrorWrapper.EthLikeError({
146
+ message: err.message,
147
+ reason: ErrorWrapper.reasons.bumpTxFailed,
148
+ hint: 'broadcastTx',
149
+ })
150
+ } else if (!nonceTooLowErr) {
151
+ throw new ErrorWrapper.EthLikeError({
152
+ message: err.message,
153
+ reason: ErrorWrapper.reasons.broadcastTxFailed,
154
+ hint: 'otherErr:broadcastTx',
155
+ })
138
156
  } else if (nonceTooLowErr) {
139
157
  console.info('trying to send again...') // inject logger factory from platform
140
158
  // let's try to fix the nonce issue
159
+
141
160
  nonce = await getNonce({ asset: baseAsset, address: fromAddress })
142
161
  ;({ txId, rawTx } = await createTx({ ...createTxParams, nonce }))
143
- await baseAsset.api.broadcastTx(rawTx.toString('hex'))
162
+
163
+ try {
164
+ await baseAsset.api.broadcastTx(rawTx.toString('hex'))
165
+ } catch (err) {
166
+ const insufficientFundsErr = err.message.match(/insufficient funds/i)
167
+ if (insufficientFundsErr) {
168
+ throw new ErrorWrapper.EthLikeError({
169
+ message: err.message,
170
+ reason: ErrorWrapper.reasons.insufficientFunds,
171
+ hint: 'retry:broadcastTx',
172
+ })
173
+ }
174
+
175
+ throw new ErrorWrapper.EthLikeError({
176
+ message: err.message,
177
+ reason: ErrorWrapper.reasons.broadcastTxFailed,
178
+ hint: 'retry:broadcastTx',
179
+ })
180
+ }
144
181
  }
145
182
  }
146
183