@exodus/ethereum-api 5.1.0-alpha.0 → 6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "5.1.0-alpha.0",
3
+ "version": "6.0.0",
4
4
  "description": "Ethereum Api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "@exodus/asset-lib": "^3.7.1",
18
18
  "@exodus/crypto": "^1.0.0-rc.0",
19
- "@exodus/ethereum-lib": "^2.27.0-alpha.0",
19
+ "@exodus/ethereum-lib": "^3.0.0",
20
20
  "@exodus/ethereumjs-util": "^7.1.0-exodus.6",
21
21
  "@exodus/fetch": "^1.2.1",
22
22
  "@exodus/simple-retry": "^0.0.6",
@@ -36,5 +36,5 @@
36
36
  "devDependencies": {
37
37
  "@exodus/models": "^8.10.4"
38
38
  },
39
- "gitHead": "3241be77078ca4bf456bd7b2e8f3fe545142c92e"
39
+ "gitHead": "b299a67ebe7a7c23638d868b074df4d20b3f56d9"
40
40
  }
@@ -1,8 +1,8 @@
1
+ import assert from 'minimalistic-assert'
1
2
  import { normalizeTxId, isEthereumLikeAsset, isEthereumLikeToken, ABI } from '@exodus/ethereum-lib'
2
- import { getServerByName, getServer } from './exodus-eth-server'
3
3
  import { memoizeLruCache } from '@exodus/asset-lib'
4
- import assets from '@exodus/assets'
5
4
  import SolidityContract from '@exodus/solidity-contract'
5
+ import { getServerByName, getServer } from './exodus-eth-server'
6
6
 
7
7
  export async function isContract(baseAssetName, address) {
8
8
  return getServerByName(baseAssetName).isContract(address)
@@ -86,18 +86,9 @@ const ERC20BytesParams = new SolidityContract(ABI.erc20BytesParams)
86
86
  const DEFAULT_PARAM_NAMES = ['decimals', 'name', 'symbol']
87
87
  const erc20ParamsCache = {}
88
88
 
89
- export const getERC20Params = async ({
90
- assetName,
91
- address,
92
- paramNames = DEFAULT_PARAM_NAMES,
93
- } = {}) => {
94
- const asset = assets[assetName]
95
- if (!asset) {
96
- throw new Error(`${assetName} not found`)
97
- }
98
- if (!address) {
99
- throw new Error(`Token address should be provided, got: ${address}`)
100
- }
89
+ export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARAM_NAMES } = {}) => {
90
+ assert(asset, 'getERC20Params(): asset required')
91
+ assert(address, 'getERC20Params(): address required')
101
92
 
102
93
  const cacheKey = `${address}:${paramNames}`
103
94
  if (erc20ParamsCache[cacheKey]) {
package/src/index.js CHANGED
@@ -9,4 +9,3 @@ export * from './tx-log'
9
9
  export * from './get-balances'
10
10
  export * from './staking'
11
11
  export * from './simulate-tx'
12
- export * from './optimism-gas'
@@ -1,4 +1,5 @@
1
- import assets from '@exodus/assets'
1
+ import assert from 'minimalistic-assert'
2
+ import { asset as ethereum } from '@exodus/ethereum-meta'
2
3
  import {
3
4
  checkIsERC721InputData,
4
5
  checkIsNFTInputData,
@@ -9,14 +10,14 @@ import SolidityContract from '@exodus/solidity-contract'
9
10
  import { fetchTxPreview } from './fetch-tx-preview'
10
11
  import { getERC20Params } from '../eth-like-util'
11
12
 
12
- const ethDecimals = assets.ethereum.units.ETH
13
+ const ethDecimals = ethereum.units.ETH
13
14
 
14
15
  const ethHexToInt = (hexValue) => parseInt(hexValue, '16')
15
16
 
16
- async function getAssetSymbolFromContract(contractAddress) {
17
+ async function getAssetSymbolFromContract(contractAddress, asset) {
17
18
  const { symbol: assetSymbol } = await getERC20Params({
18
19
  address: contractAddress,
19
- assetName: 'ethereum',
20
+ asset,
20
21
  paramNames: ['symbol'],
21
22
  })
22
23
 
@@ -53,12 +54,11 @@ async function prepareBalanceChanges(
53
54
  internalTransactions,
54
55
  balanceChanges,
55
56
  transactionInput,
56
- assetName = 'ethereum'
57
+ asset
57
58
  ) {
58
59
  const assetNameToSymbolMap = Object.create(null)
59
60
  assetNameToSymbolMap['ethereum'] = 'ETH' // Refactor after blowfish integration
60
61
 
61
- const asset = assets[assetName]
62
62
  const preparedBalanceChanges = [...balanceChanges]
63
63
 
64
64
  const decimals = Object.create(null)
@@ -85,7 +85,7 @@ async function prepareBalanceChanges(
85
85
  if (isERC20) {
86
86
  const assetSymbol =
87
87
  contractCall.contractAlias ||
88
- (await getAssetSymbolFromContract(contractCall.contractAddress))
88
+ (await getAssetSymbolFromContract(contractCall.contractAddress, asset))
89
89
 
90
90
  assetSymbols[contractCall.contractAddress] = assetSymbol
91
91
  decimals[assetSymbol] = contractCall.contractDecimals
@@ -98,40 +98,40 @@ async function prepareBalanceChanges(
98
98
  }
99
99
 
100
100
  for (const balanceChange of preparedBalanceChanges) {
101
- const { asset } = balanceChange
101
+ const { asset: assetData } = balanceChange
102
102
 
103
- if (!asset.symbol) {
104
- asset.symbol =
105
- assetSymbols[asset.contractAddress] ||
106
- (await getAssetSymbolFromContract(asset.contractAddress))
103
+ if (!assetData.symbol) {
104
+ assetData.symbol =
105
+ assetSymbols[assetData.contractAddress] ||
106
+ (await getAssetSymbolFromContract(assetData.contractAddress, asset))
107
107
  }
108
108
 
109
109
  if (isERC721) {
110
- asset.type = 'erc721'
111
- asset.title = contractName
110
+ assetData.type = 'erc721'
111
+ assetData.title = contractName
112
112
  continue
113
113
  }
114
114
 
115
- if (typeof decimals[asset.symbol] === 'number') {
116
- asset.decimal = decimals[asset.symbol]
115
+ if (typeof decimals[assetData.symbol] === 'number') {
116
+ assetData.decimal = decimals[assetData.symbol]
117
117
  continue
118
118
  }
119
119
 
120
- if (isERC20 && asset.contractAddress) {
120
+ if (isERC20 && assetData.contractAddress) {
121
121
  const { decimals: assetDecimal } = await getERC20Params({
122
- address: asset.contractAddress,
123
- assetName: 'ethereum',
122
+ address: assetData.contractAddress,
123
+ asset,
124
124
  paramNames: ['decimals'],
125
125
  })
126
126
 
127
- asset.decimal = assetDecimal
127
+ assetData.decimal = assetDecimal
128
128
  }
129
129
  }
130
130
 
131
131
  return maybeRemoveDuplicates(preparedBalanceChanges)
132
132
  }
133
133
 
134
- async function tryToDecodeApprovalTransaction(transaction) {
134
+ async function tryToDecodeApprovalTransaction(transaction, asset) {
135
135
  if (!transaction?.data.startsWith(APPROVE_METHOD_ID)) return null
136
136
 
137
137
  const contract = SolidityContract.erc20(transaction.to)
@@ -142,7 +142,7 @@ async function tryToDecodeApprovalTransaction(transaction) {
142
142
  const [grantedTo, balance] = decodedInput.values
143
143
 
144
144
  const symbol =
145
- (await getAssetSymbolFromContract(transaction.to))?.toUpperCase() || 'Unknown Token'
145
+ (await getAssetSymbolFromContract(transaction.to, asset))?.toUpperCase() || 'Unknown Token'
146
146
 
147
147
  return [{ grantedTo, balance, symbol, decimals: undefined }] // ToDo: Return 'decimals' in the future once we support changing the approval amount.
148
148
  } catch (e) {
@@ -152,11 +152,13 @@ async function tryToDecodeApprovalTransaction(transaction) {
152
152
  }
153
153
  }
154
154
 
155
- export async function retrieveSideEffects({ transaction, assetName, shouldSimulate = true }) {
155
+ export async function retrieveSideEffects({ transaction, asset, shouldSimulate = true }) {
156
+ assert(asset, 'retrieveSideEffects(): asset is required')
157
+
156
158
  const willSend = []
157
159
  const willReceive = []
158
160
 
159
- const approveTransactionData = await tryToDecodeApprovalTransaction(transaction)
161
+ const approveTransactionData = await tryToDecodeApprovalTransaction(transaction, asset)
160
162
  if (approveTransactionData) {
161
163
  return { willApprove: approveTransactionData }
162
164
  }
@@ -192,25 +194,25 @@ export async function retrieveSideEffects({ transaction, assetName, shouldSimula
192
194
  internalTransactions,
193
195
  sender.balanceChanges,
194
196
  transaction.data,
195
- assetName
197
+ asset
196
198
  )
197
199
 
198
200
  for (const balanceChange of preparedBalanceChanges) {
199
- const { delta, asset } = balanceChange
201
+ const { delta, asset: assetData } = balanceChange
200
202
 
201
203
  const account = {
202
- symbol: asset.symbol,
204
+ symbol: assetData.symbol,
203
205
  balance: delta,
204
- assetType: asset.type,
205
- decimal: asset.decimal,
206
+ assetType: assetData.type,
207
+ decimal: assetData.decimal,
206
208
  }
207
209
 
208
210
  if (delta.startsWith('-')) {
209
211
  willSend.push(account)
210
212
  } else {
211
- if (asset.type === 'erc721') {
213
+ if (assetData.type === 'erc721') {
212
214
  account.nft = {
213
- title: `${asset.title || asset.symbol} #${delta}`,
215
+ title: `${assetData.title || assetData.symbol} #${delta}`,
214
216
  }
215
217
  }
216
218
  willReceive.push(account)
@@ -8,6 +8,7 @@ import {
8
8
  getAllLogItemsByAsset,
9
9
  checkPendingTransactions,
10
10
  getDeriveTransactionsToCheck,
11
+ excludeUnchangedTokenBalances,
11
12
  } from './monitor-utils'
12
13
  import { getLogItemsFromServerTx, getDeriveDataNeededForTick, filterEffects } from './clarity-utils'
13
14
 
@@ -158,6 +159,7 @@ export class ClarityMonitor extends BaseMonitor {
158
159
 
159
160
  const accountState = await this.getNewAccountState({
160
161
  tokens,
162
+ currentTokenBalances: derivedData.currentAccountState?.tokenBalances,
161
163
  ourWalletAddress: derivedData.ourWalletAddress,
162
164
  })
163
165
  await this.updateAccountState({
@@ -187,7 +189,7 @@ export class ClarityMonitor extends BaseMonitor {
187
189
  }
188
190
  }
189
191
 
190
- async getNewAccountState({ tokens, ourWalletAddress }) {
192
+ async getNewAccountState({ tokens, currentTokenBalances, ourWalletAddress }) {
191
193
  const asset = this.asset
192
194
  const newAccountState = {}
193
195
  const balances = await this.getBalances({ tokens, ourWalletAddress })
@@ -196,14 +198,15 @@ export class ClarityMonitor extends BaseMonitor {
196
198
  newAccountState.balance = asset.currency.baseUnit(balance)
197
199
  }
198
200
  const tokenBalancePairs = Object.entries(balances).filter((entry) => entry[0] !== asset.name)
199
- const entries = tokenBalancePairs
201
+ const tokenBalanceEntries = tokenBalancePairs
200
202
  .map((pair) => {
201
203
  const token = tokens.find((token) => token.name === pair[0])
202
204
  const value = token.currency.baseUnit(pair[1] || 0)
203
- return value.isZero ? null : [token.name, value]
205
+ return [token.name, value]
204
206
  })
205
207
  .filter((pair) => pair)
206
- const tokenBalances = Object.fromEntries(entries)
208
+
209
+ const tokenBalances = excludeUnchangedTokenBalances(currentTokenBalances, tokenBalanceEntries)
207
210
  if (!isEmpty(tokenBalances)) newAccountState.tokenBalances = tokenBalances
208
211
  return newAccountState
209
212
  }
@@ -11,6 +11,7 @@ import {
11
11
  getDeriveDataNeededForTick,
12
12
  getDeriveTransactionsToCheck,
13
13
  getHistoryFromServer,
14
+ excludeUnchangedTokenBalances,
14
15
  } from './monitor-utils'
15
16
 
16
17
  import {
@@ -185,8 +186,10 @@ export class EthereumMonitor extends BaseMonitor {
185
186
 
186
187
  const accountState = await this.getNewAccountState({
187
188
  tokens,
189
+ currentTokenBalances: derivedData.currentAccountState?.tokenBalances,
188
190
  ourWalletAddress: derivedData.ourWalletAddress,
189
191
  })
192
+
190
193
  await this.updateAccountState({ newData: { index, ...accountState }, walletAccount })
191
194
 
192
195
  await this.removeFromTxLog(txsToRemove)
@@ -220,7 +223,7 @@ export class EthereumMonitor extends BaseMonitor {
220
223
  }
221
224
  }
222
225
 
223
- async getNewAccountState({ tokens, ourWalletAddress }) {
226
+ async getNewAccountState({ tokens, currentTokenBalances, ourWalletAddress }) {
224
227
  const asset = this.asset
225
228
  const newAccountState = {}
226
229
  const server = this.server
@@ -236,10 +239,11 @@ export class EthereumMonitor extends BaseMonitor {
236
239
  .map(async (token) => {
237
240
  const { confirmed } = await server.balanceOf(ourWalletAddress, token.contract.address)
238
241
  const value = token.currency.baseUnit(confirmed[token.contract.address] || 0)
239
- return value.isZero ? null : [token.name, value]
242
+ return [token.name, value]
240
243
  })
241
244
  )
242
- const tokenBalances = Object.fromEntries(tokenBalancePairs.filter((pair) => pair))
245
+
246
+ const tokenBalances = excludeUnchangedTokenBalances(currentTokenBalances, tokenBalancePairs)
243
247
  if (!isEmpty(tokenBalances)) newAccountState.tokenBalances = tokenBalances
244
248
  return newAccountState
245
249
  }
@@ -3,7 +3,11 @@ import { getServer } from '@exodus/ethereum-api'
3
3
  import { DEFAULT_SERVER_URLS } from '@exodus/ethereum-lib'
4
4
  import { Tx } from '@exodus/models'
5
5
 
6
- import { getDeriveDataNeededForTick, getDeriveTransactionsToCheck } from './monitor-utils'
6
+ import {
7
+ getDeriveDataNeededForTick,
8
+ getDeriveTransactionsToCheck,
9
+ excludeUnchangedTokenBalances,
10
+ } from './monitor-utils'
7
11
 
8
12
  import { isEmpty, unionBy, zipObject } from 'lodash'
9
13
 
@@ -57,21 +61,22 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
57
61
  return Object.fromEntries(entries)
58
62
  }
59
63
 
60
- async getNewAccountState({ tokens, ourWalletAddress }) {
64
+ async getNewAccountState({ tokens, currentTokenBalances, ourWalletAddress }) {
61
65
  const asset = this.asset
62
66
  const newAccountState = {}
63
67
  const balances = await this.getBalances({ tokens, ourWalletAddress })
64
68
  const balance = balances[asset.name]
65
69
  newAccountState.balance = asset.currency.baseUnit(balance)
66
70
  const tokenBalancePairs = Object.entries(balances).filter((entry) => entry[0] !== asset.name)
67
- const entries = tokenBalancePairs
71
+ const tokenBalanceEntries = tokenBalancePairs
68
72
  .map((pair) => {
69
73
  const token = tokens.find((token) => token.name === pair[0])
70
74
  const value = token.currency.baseUnit(pair[1] || 0)
71
- return value.isZero ? null : [token.name, value]
75
+ return [token.name, value]
72
76
  })
73
77
  .filter((pair) => pair)
74
- const tokenBalances = Object.fromEntries(entries)
78
+
79
+ const tokenBalances = excludeUnchangedTokenBalances(currentTokenBalances, tokenBalanceEntries)
75
80
  if (!isEmpty(tokenBalances)) newAccountState.tokenBalances = tokenBalances
76
81
  return newAccountState
77
82
  }
@@ -79,7 +84,11 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
79
84
  async deriveData({ assetSource, tokens }) {
80
85
  const { assetName, walletAccount } = assetSource
81
86
 
82
- const { ourWalletAddress } = await this.deriveDataNeededForTick({ assetName, walletAccount })
87
+ const { ourWalletAddress, currentAccountState } = await this.deriveDataNeededForTick({
88
+ assetName,
89
+ walletAccount,
90
+ })
91
+
83
92
  const {
84
93
  pendingTransactionsGroupedByAddressAndNonce,
85
94
  pendingTransactionsToCheck,
@@ -96,7 +105,7 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
96
105
  'tx.txId'
97
106
  )
98
107
 
99
- return { ourWalletAddress, pendingTransactions }
108
+ return { ourWalletAddress, pendingTransactions, currentAccountState }
100
109
  }
101
110
 
102
111
  async getTransactionsFromNode(transactions) {
@@ -148,7 +157,7 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
148
157
 
149
158
  const assetSource = { assetName: this.asset.name, walletAccount }
150
159
 
151
- const { ourWalletAddress, pendingTransactions } = await this.deriveData({
160
+ const { ourWalletAddress, pendingTransactions, currentAccountState } = await this.deriveData({
152
161
  assetSource,
153
162
  tokens,
154
163
  })
@@ -171,6 +180,7 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
171
180
 
172
181
  const accountState = await this.getNewAccountState({
173
182
  tokens,
183
+ currentTokenBalances: currentAccountState?.tokenBalances,
174
184
  ourWalletAddress,
175
185
  })
176
186
 
@@ -0,0 +1,13 @@
1
+ export function excludeUnchangedTokenBalances(currentTokenBalances, newTokenBalancePairs) {
2
+ const newTokenBalances = Object.fromEntries(newTokenBalancePairs)
3
+
4
+ const tokenBalances = newTokenBalancePairs.reduce((tokenBalancesAcc, [token, balance]) => {
5
+ const currentBalance = currentTokenBalances[token]
6
+ if (!newTokenBalances[token].isZero || (currentBalance && !currentBalance.isZero)) {
7
+ tokenBalancesAcc[token] = balance
8
+ }
9
+ return tokenBalancesAcc
10
+ }, {})
11
+
12
+ return tokenBalances
13
+ }
@@ -41,8 +41,9 @@ export default function getLogItemsFromServerTx({
41
41
  {
42
42
  const sendingTransferPresent = ethereumTransfers.some(({ from }) => from === ourWalletAddress)
43
43
  const receivingTransferPresent = ethereumTransfers.some(({ to }) => to === ourWalletAddress)
44
+ const nftTransferPresent = isNftTransfer({ serverTx, ourWalletAddress })
44
45
 
45
- if (sendingTransferPresent || receivingTransferPresent) {
46
+ if (sendingTransferPresent || receivingTransferPresent || nftTransferPresent) {
46
47
  const coinAmount = getValueOfTransfers(ourWalletAddress, asset, ethereumTransfers)
47
48
  const selfSend = isSelfSendTx({
48
49
  coinAmount,
@@ -140,3 +141,11 @@ function isSelfSendTx({
140
141
  coinAmount.isZero && sendingTransferPresent && receivingTransferPresent && ourWalletWasSender
141
142
  )
142
143
  }
144
+
145
+ function isNftTransfer({ serverTx, ourWalletAddress }) {
146
+ if (!Array.isArray(serverTx.erc721) || serverTx.erc721.length < 1) return false
147
+
148
+ return serverTx.erc721.some(
149
+ (transfer) => transfer.to === ourWalletAddress || transfer.from === ourWalletAddress
150
+ )
151
+ }
@@ -1,8 +1,10 @@
1
1
  export { default as getDeriveDataNeededForTick } from './get-derive-data-needed-for-tick'
2
+
2
3
  export { default as getAllLogItemsByAsset } from './get-all-log-items-by-asset'
3
4
  export { default as getLogItemsFromServerTx } from './get-log-items-from-server-tx'
4
5
  export { default as getHistoryFromServer } from './get-history-from-server'
5
6
  export { default as checkPendingTransactions } from './check-pending-transactions'
6
7
  export { default as getDeriveTransactionsToCheck } from './get-derive-transactions-to-check'
8
+ export * from './exclude-unchanged-token-balances'
7
9
 
8
10
  export type { PendingTransactionsDictionary } from './types'
@@ -1 +0,0 @@
1
- export const GAS_ORACLE_ADDRESS = '0x420000000000000000000000000000000000000F'
@@ -1,16 +0,0 @@
1
- import * as ethUtil from '@exodus/ethereumjs-util'
2
- import { convertUnsignedTx, createContract } from '@exodus/ethereum-lib'
3
- import { getServerByName } from '../exodus-eth-server'
4
- import { GAS_ORACLE_ADDRESS } from './addresses'
5
-
6
- const gasContract = createContract(GAS_ORACLE_ADDRESS, 'optimismGasOracle')
7
-
8
- export async function estimateOptimismL1DataFee({ unsignedTx }) {
9
- const ethjsTx = convertUnsignedTx(unsignedTx)
10
- const serialized = ethjsTx.serialize()
11
- const callData = gasContract.getL1Fee.build(serialized)
12
- const buffer = Buffer.from(callData)
13
- const data = ethUtil.bufferToHex(buffer)
14
- const server = getServerByName('optimism')
15
- return server.ethCall({ to: GAS_ORACLE_ADDRESS, data }, 'latest')
16
- }