@exodus/ethereum-api 2.24.3 → 2.24.5
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 +3 -3
- package/src/tx-log/ethereum-monitor.js +3 -3
- package/src/tx-log/monitor-utils/check-pending-transactions.js +39 -0
- package/src/tx-log/monitor-utils/get-all-log-items-by-asset.js +32 -0
- package/src/tx-log/monitor-utils/get-derive-data-needed-for-tick.js +14 -0
- package/src/tx-log/monitor-utils/get-derive-transactions-to-check.js +51 -0
- package/src/tx-log/monitor-utils/get-fee-amount.js +14 -0
- package/src/tx-log/monitor-utils/get-history-from-server.js +43 -0
- package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +141 -0
- package/src/tx-log/monitor-utils/get-names-of-tokens-transferred-by-server-tx.js +39 -0
- package/src/tx-log/monitor-utils/get-sender-nonce-key.js +11 -0
- package/src/tx-log/monitor-utils/get-transfers-by-token-name.js +16 -0
- package/src/tx-log/monitor-utils/get-value-of-transfers.js +22 -0
- package/src/tx-log/monitor-utils/index.js +7 -0
- package/src/tx-log/monitor-utils/is-confirmed-server-tx.js +6 -0
- package/src/tx-log/monitor-utils/is-considered-sent-token-tx.js +25 -0
- package/src/tx-log/monitor-utils/types.js +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "2.24.
|
|
3
|
+
"version": "2.24.5",
|
|
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.5.4",
|
|
18
18
|
"@exodus/crypto": "^1.0.0-rc.0",
|
|
19
|
-
"@exodus/ethereum-lib": "^2.21.
|
|
19
|
+
"@exodus/ethereum-lib": "^2.21.4",
|
|
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",
|
|
@@ -34,5 +34,5 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@exodus/models": "^8.7.2"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "2ad238fc652010aa759fcee58c6bbad707ec940d"
|
|
38
38
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/* @flow */
|
|
2
2
|
import { getServer } from '@exodus/ethereum-api'
|
|
3
|
+
import { isRpcBalanceAsset, getAssetAddresses } from '@exodus/ethereum-lib'
|
|
4
|
+
|
|
3
5
|
import {
|
|
4
6
|
type PendingTransactionsDictionary,
|
|
5
7
|
checkPendingTransactions,
|
|
@@ -7,9 +9,7 @@ import {
|
|
|
7
9
|
getDeriveDataNeededForTick,
|
|
8
10
|
getDeriveTransactionsToCheck,
|
|
9
11
|
getHistoryFromServer,
|
|
10
|
-
|
|
11
|
-
getAssetAddresses,
|
|
12
|
-
} from '@exodus/ethereum-lib'
|
|
12
|
+
} from './monitor-utils'
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
enableWSUpdates,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import getSenderNonceKey from './get-sender-nonce-key'
|
|
3
|
+
import getFeeAmount from './get-fee-amount'
|
|
4
|
+
import type { PendingTransactionsDictionary } from './types'
|
|
5
|
+
|
|
6
|
+
export default function checkPendingTransactions({
|
|
7
|
+
pendingTransactionsGroupedByAddressAndNonce = {},
|
|
8
|
+
pendingTransactionsToCheck = {},
|
|
9
|
+
txlist,
|
|
10
|
+
ourWalletAddress,
|
|
11
|
+
asset,
|
|
12
|
+
}: {
|
|
13
|
+
pendingTransactionsGroupedByAddressAndNonce: PendingTransactionsDictionary,
|
|
14
|
+
pendingTransactionsToCheck: PendingTransactionsDictionary,
|
|
15
|
+
txlist: Array<Object>,
|
|
16
|
+
ourWalletAddress: string,
|
|
17
|
+
asset: Object,
|
|
18
|
+
}): PendingTransactionsDictionary {
|
|
19
|
+
for (const tx of txlist) {
|
|
20
|
+
delete pendingTransactionsToCheck[tx.txId || tx.hash] // Found this transaction, do not mark it as dropped
|
|
21
|
+
|
|
22
|
+
// check if this TX replaces another pending TX
|
|
23
|
+
const replacedTx =
|
|
24
|
+
pendingTransactionsGroupedByAddressAndNonce[getSenderNonceKey(tx, ourWalletAddress)]
|
|
25
|
+
if (replacedTx && tx.hash !== replacedTx.tx.txId) {
|
|
26
|
+
// if a pending TX with lower fees comes from the server, don't add it
|
|
27
|
+
if (
|
|
28
|
+
replacedTx.tx.sent &&
|
|
29
|
+
replacedTx.tx.feeAmount.gt(getFeeAmount(asset, tx)) &&
|
|
30
|
+
tx.confirmations === 0
|
|
31
|
+
) {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
replacedTx.replaced = true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { pendingTransactionsGroupedByAddressAndNonce, pendingTransactionsToCheck }
|
|
39
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import getLogItemsFromServerTx from './get-log-items-from-server-tx'
|
|
2
|
+
|
|
3
|
+
// Iterates through an array of server transactions and formats them into log item
|
|
4
|
+
// objects (in Tx class fields format). It returns an object of arrays of log
|
|
5
|
+
// items, keyed by asset name.
|
|
6
|
+
|
|
7
|
+
export default function getAllLogItemsByAsset({
|
|
8
|
+
allTransactionsFromServer,
|
|
9
|
+
ourWalletAddress,
|
|
10
|
+
asset,
|
|
11
|
+
tokensByAddress,
|
|
12
|
+
assets,
|
|
13
|
+
}) {
|
|
14
|
+
const allAssets = [asset, ...tokensByAddress.values()]
|
|
15
|
+
const logItemsByAsset = Object.fromEntries(allAssets.map((asset) => [asset.name, []]))
|
|
16
|
+
|
|
17
|
+
allTransactionsFromServer.forEach((serverTx) => {
|
|
18
|
+
const logItemsByAssetForServerTx = getLogItemsFromServerTx({
|
|
19
|
+
serverTx,
|
|
20
|
+
ourWalletAddress,
|
|
21
|
+
tokensByAddress,
|
|
22
|
+
asset,
|
|
23
|
+
assets,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
Object.entries(logItemsByAssetForServerTx).forEach(([assetName, logItem]) => {
|
|
27
|
+
logItemsByAsset[assetName].push(logItem)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return logItemsByAsset
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// A super-selector that returns all the current data needed for a tick of the ETH monitor.
|
|
2
|
+
|
|
3
|
+
export default function getDeriveDataNeededForTick(aci) {
|
|
4
|
+
return async function({ assetName, walletAccount }) {
|
|
5
|
+
const receiveAddress = await aci.getReceiveAddress({ assetName, walletAccount })
|
|
6
|
+
const currentAccountState = await aci.getAccountState({ assetName, walletAccount })
|
|
7
|
+
const minimumConfirmations = await aci.getConfirmationsNumber({ assetName })
|
|
8
|
+
return {
|
|
9
|
+
ourWalletAddress: receiveAddress.toLowerCase(),
|
|
10
|
+
currentAccountState,
|
|
11
|
+
minimumConfirmations,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import ms from 'ms'
|
|
2
|
+
import getSenderNonceKey from './get-sender-nonce-key'
|
|
3
|
+
|
|
4
|
+
const UNCONFIRMED_TX_LIMIT = ms('5m')
|
|
5
|
+
|
|
6
|
+
const mapToObject = (map) => Object.fromEntries([...map.entries()]) // only for string keys
|
|
7
|
+
|
|
8
|
+
export default function getDeriveTransactionsToCheck({ getTxLog }) {
|
|
9
|
+
return async ({ assetName: _assetName, walletAccount, tokens, ourWalletAddress }) => {
|
|
10
|
+
const pendingTransactionsToCheck = new Map()
|
|
11
|
+
const pendingTransactionsGroupedByAddressAndNonce = new Map()
|
|
12
|
+
const simulatedTransactions = new Map()
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
|
|
15
|
+
for (const assetName of [_assetName, ...tokens.map(({ name }) => name)]) {
|
|
16
|
+
const txSet = await getTxLog({ assetName, walletAccount })
|
|
17
|
+
for (const tx of txSet) {
|
|
18
|
+
// ERC20 sends have an entry in ETH and one in the ERC20 log so we just want the ETH one
|
|
19
|
+
if (
|
|
20
|
+
!pendingTransactionsToCheck.has(tx.txId) &&
|
|
21
|
+
tx.pending &&
|
|
22
|
+
now - tx.date.getTime() > UNCONFIRMED_TX_LIMIT
|
|
23
|
+
) {
|
|
24
|
+
pendingTransactionsToCheck.set(tx.txId, { tx, assetName })
|
|
25
|
+
}
|
|
26
|
+
if (tx.meta.simulated) simulatedTransactions.set(tx.txId, tx)
|
|
27
|
+
|
|
28
|
+
const senderNonceKey = getSenderNonceKey(tx, ourWalletAddress)
|
|
29
|
+
if (
|
|
30
|
+
tx.pending &&
|
|
31
|
+
!pendingTransactionsGroupedByAddressAndNonce.has(senderNonceKey) &&
|
|
32
|
+
!tx.dropped
|
|
33
|
+
) {
|
|
34
|
+
pendingTransactionsGroupedByAddressAndNonce.set(senderNonceKey, {
|
|
35
|
+
tx,
|
|
36
|
+
replaced: false,
|
|
37
|
+
assetName,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
pendingTransactionsToCheck: mapToObject(pendingTransactionsToCheck),
|
|
45
|
+
pendingTransactionsGroupedByAddressAndNonce: mapToObject(
|
|
46
|
+
pendingTransactionsGroupedByAddressAndNonce
|
|
47
|
+
),
|
|
48
|
+
simulatedTransactions: mapToObject(simulatedTransactions),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Returns the amount of ethereum used for a transaction
|
|
2
|
+
// from the server, as a number unit.
|
|
3
|
+
|
|
4
|
+
export default function getFeeAmount(asset, serverTx) {
|
|
5
|
+
const { gas: gasLimit, gasUsed, gasPrice } = serverTx
|
|
6
|
+
|
|
7
|
+
// genesis, coinbase, uncles
|
|
8
|
+
if (!gasPrice) return asset.currency.ZERO
|
|
9
|
+
|
|
10
|
+
return asset.currency
|
|
11
|
+
.baseUnit(gasUsed || gasLimit)
|
|
12
|
+
.mul(gasPrice)
|
|
13
|
+
.toDefault()
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import isConfirmedServerTx from './is-confirmed-server-tx'
|
|
2
|
+
|
|
3
|
+
// Fetches JSON transaction history from the given Ethereum server
|
|
4
|
+
// object (from @exodus/ethereum-api). Starts fetching from the given
|
|
5
|
+
// block cursor and returns the latest cursor and all transactions
|
|
6
|
+
// fetched in the process. minimumConfirmations determines how many
|
|
7
|
+
// confirmations a transaction must have for the cursor to be moved
|
|
8
|
+
// to that transaction's block.
|
|
9
|
+
|
|
10
|
+
export default async function getHistoryFromServer({
|
|
11
|
+
server,
|
|
12
|
+
ourWalletAddress,
|
|
13
|
+
index,
|
|
14
|
+
minimumConfirmations,
|
|
15
|
+
limit = 1000,
|
|
16
|
+
}) {
|
|
17
|
+
if (limit === 0) return []
|
|
18
|
+
const allTransactionsFromServer = []
|
|
19
|
+
|
|
20
|
+
let isEndOfAddressHistory = false
|
|
21
|
+
let newIndex = 0
|
|
22
|
+
while (true) {
|
|
23
|
+
const transactions = await server.getHistoryV2(ourWalletAddress, {
|
|
24
|
+
index,
|
|
25
|
+
limit, // the server returns the number of TXs that's less or equal to the limit
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (transactions.length === 0) isEndOfAddressHistory = true
|
|
29
|
+
for (const transaction of transactions) {
|
|
30
|
+
allTransactionsFromServer.push(transaction)
|
|
31
|
+
index = transaction.addressIndex + 1 // this index is used to fetch a next page of TXs
|
|
32
|
+
if (transaction.confirmations >= minimumConfirmations) newIndex = transaction.addressIndex + 1 // this index is used to return from the function
|
|
33
|
+
if (!isConfirmedServerTx(transaction)) isEndOfAddressHistory = true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isEndOfAddressHistory) {
|
|
37
|
+
return {
|
|
38
|
+
allTransactionsFromServer,
|
|
39
|
+
index: newIndex,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import getValueOfTransfers from './get-value-of-transfers'
|
|
2
|
+
import getTransfersByTokenName from './get-transfers-by-token-name'
|
|
3
|
+
import getNamesOfTokensTransferredByServerTx from './get-names-of-tokens-transferred-by-server-tx'
|
|
4
|
+
import getFeeAmount from './get-fee-amount'
|
|
5
|
+
import isConsideredSentTokenTx from './is-considered-sent-token-tx'
|
|
6
|
+
import isConfirmedServerTx from './is-confirmed-server-tx'
|
|
7
|
+
import lodash from 'lodash'
|
|
8
|
+
|
|
9
|
+
// This function takes a server transaction object fetched from magnifier,
|
|
10
|
+
// and transforms it into Tx models to update the exodus state.
|
|
11
|
+
|
|
12
|
+
export default function getLogItemsFromServerTx({
|
|
13
|
+
serverTx,
|
|
14
|
+
asset,
|
|
15
|
+
ourWalletAddress,
|
|
16
|
+
tokensByAddress,
|
|
17
|
+
assets,
|
|
18
|
+
}) {
|
|
19
|
+
const confirmations = isConfirmedServerTx(serverTx) ? 1 : 0
|
|
20
|
+
const date = parseServerTxDate(serverTx.timestamp) // included even for unconfirmed txs
|
|
21
|
+
const txId = serverTx.hash
|
|
22
|
+
const nonce = parseInt(serverTx.nonce, 16)
|
|
23
|
+
const gasLimit = parseInt(serverTx.gas, 16)
|
|
24
|
+
const error = serverTx.error || (serverTx.status === false ? 'Failed' : null)
|
|
25
|
+
const feeAmount = getFeeAmount(asset, serverTx)
|
|
26
|
+
const ethereumTransfers = [serverTx, ...(serverTx.internal || [])]
|
|
27
|
+
const tokenTransfersByTokenName = getTransfersByTokenName(serverTx.erc20 || [], tokensByAddress)
|
|
28
|
+
const toAddress = tryFindExternalRecipient(ethereumTransfers, ourWalletAddress)
|
|
29
|
+
const ourWalletWasSender = serverTx.from === ourWalletAddress
|
|
30
|
+
|
|
31
|
+
const logItemCommonProperties = {
|
|
32
|
+
confirmations,
|
|
33
|
+
date,
|
|
34
|
+
error,
|
|
35
|
+
feeAmount: ourWalletWasSender ? feeAmount : undefined,
|
|
36
|
+
txId,
|
|
37
|
+
dropped: false,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const logItemsForServerTxEntries = []
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
const sendingTransferPresent = ethereumTransfers.some(({ from }) => from === ourWalletAddress)
|
|
44
|
+
const receivingTransferPresent = ethereumTransfers.some(({ to }) => to === ourWalletAddress)
|
|
45
|
+
|
|
46
|
+
if (sendingTransferPresent || receivingTransferPresent) {
|
|
47
|
+
const coinAmount = getValueOfTransfers(ourWalletAddress, asset, ethereumTransfers)
|
|
48
|
+
const selfSend = isSelfSendTx({
|
|
49
|
+
coinAmount,
|
|
50
|
+
ourWalletWasSender,
|
|
51
|
+
sendingTransferPresent,
|
|
52
|
+
receivingTransferPresent,
|
|
53
|
+
})
|
|
54
|
+
logItemsForServerTxEntries.push([
|
|
55
|
+
asset.name,
|
|
56
|
+
{
|
|
57
|
+
...logItemCommonProperties,
|
|
58
|
+
coinAmount,
|
|
59
|
+
coinName: asset.name,
|
|
60
|
+
data: {
|
|
61
|
+
data: serverTx.data || '0x',
|
|
62
|
+
nonce,
|
|
63
|
+
gasLimit,
|
|
64
|
+
},
|
|
65
|
+
from: ourWalletWasSender ? [] : [serverTx.from],
|
|
66
|
+
to: ourWalletWasSender ? toAddress : undefined,
|
|
67
|
+
selfSend,
|
|
68
|
+
tokens: getNamesOfTokensTransferredByServerTx({
|
|
69
|
+
asset,
|
|
70
|
+
tokensByAddress,
|
|
71
|
+
serverTx,
|
|
72
|
+
ourWalletAddress,
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
])
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// handle erc20
|
|
79
|
+
Object.entries(tokenTransfersByTokenName).forEach(([tokenName, tokenTransfers]) => {
|
|
80
|
+
const sendingTransferPresent = tokenTransfers.some(({ from }) => from === ourWalletAddress)
|
|
81
|
+
const receivingTransferPresent = tokenTransfers.some(({ to }) => to === ourWalletAddress)
|
|
82
|
+
if (!sendingTransferPresent && !receivingTransferPresent) return
|
|
83
|
+
|
|
84
|
+
const token = assets[tokenName]
|
|
85
|
+
const tokenTransferToAddress = tryFindExternalRecipient(tokenTransfers, ourWalletAddress)
|
|
86
|
+
const coinAmount = getValueOfTransfers(
|
|
87
|
+
ourWalletAddress,
|
|
88
|
+
token,
|
|
89
|
+
lodash.filter(tokenTransfers, { events: confirmations > 0 })
|
|
90
|
+
)
|
|
91
|
+
const tokenFromAddresses = lodash.uniq(
|
|
92
|
+
tokenTransfers.filter(({ to }) => to === ourWalletAddress).map(({ from }) => from)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const isConsideredSent = isConsideredSentTokenTx({
|
|
96
|
+
coinAmount,
|
|
97
|
+
ourWalletWasSender,
|
|
98
|
+
sendingTransferPresent,
|
|
99
|
+
receivingTransferPresent,
|
|
100
|
+
})
|
|
101
|
+
const selfSend = isSelfSendTx({
|
|
102
|
+
coinAmount,
|
|
103
|
+
ourWalletWasSender,
|
|
104
|
+
sendingTransferPresent,
|
|
105
|
+
receivingTransferPresent,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
logItemsForServerTxEntries.push([
|
|
109
|
+
tokenName,
|
|
110
|
+
{
|
|
111
|
+
...logItemCommonProperties,
|
|
112
|
+
coinAmount,
|
|
113
|
+
coinName: tokenName,
|
|
114
|
+
data: { nonce, gasLimit },
|
|
115
|
+
from: isConsideredSent ? [] : tokenFromAddresses,
|
|
116
|
+
to: isConsideredSent ? tokenTransferToAddress : undefined,
|
|
117
|
+
selfSend,
|
|
118
|
+
},
|
|
119
|
+
])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return Object.fromEntries(logItemsForServerTxEntries)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const tryFindExternalRecipient = (transfers, ourWalletAddress) =>
|
|
126
|
+
transfers.find(({ to }) => to !== ourWalletAddress)?.to || ourWalletAddress
|
|
127
|
+
|
|
128
|
+
// If the timestamp is in the future, use the current time.
|
|
129
|
+
const parseServerTxDate = (timestamp) =>
|
|
130
|
+
new Date(Math.min(Date.now(), parseInt(timestamp, 16) * 1000))
|
|
131
|
+
|
|
132
|
+
function isSelfSendTx({
|
|
133
|
+
coinAmount,
|
|
134
|
+
ourWalletWasSender,
|
|
135
|
+
sendingTransferPresent,
|
|
136
|
+
receivingTransferPresent,
|
|
137
|
+
}) {
|
|
138
|
+
return (
|
|
139
|
+
coinAmount.isZero && sendingTransferPresent && receivingTransferPresent && ourWalletWasSender
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import lodash from 'lodash'
|
|
2
|
+
import getValueOfTransfers from './get-value-of-transfers'
|
|
3
|
+
import { FEE_PAYMENT_PREFIX } from '@exodus/ethereum-lib'
|
|
4
|
+
|
|
5
|
+
// For ETH transactions, we store an array of token names inside
|
|
6
|
+
// the TX model. This array is used to display text in the UI for
|
|
7
|
+
// transactions which pay fees for ERC20 token transfers. This
|
|
8
|
+
// function is used to determine that array, choosing which
|
|
9
|
+
// tokens a transaction ostensibly 'paid fees' for.
|
|
10
|
+
|
|
11
|
+
export default function getNamesOfTokensTransferredByServerTx({
|
|
12
|
+
asset,
|
|
13
|
+
tokensByAddress,
|
|
14
|
+
ourWalletAddress,
|
|
15
|
+
serverTx,
|
|
16
|
+
}) {
|
|
17
|
+
// Treat smart contract ETH transfer transactions as ETH transfers, not token transfers
|
|
18
|
+
if (!getValueOfTransfers(ourWalletAddress, asset, serverTx.internal || []).isZero) {
|
|
19
|
+
return []
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const tokenAddresses = (serverTx.erc20 || []).map((event) => event.address)
|
|
23
|
+
if (
|
|
24
|
+
tokenAddresses.length === 0 &&
|
|
25
|
+
serverTx.data &&
|
|
26
|
+
serverTx.data !== '0x' &&
|
|
27
|
+
!serverTx.data.startsWith(FEE_PAYMENT_PREFIX)
|
|
28
|
+
) {
|
|
29
|
+
// We may still be transacting with tokens even though we found no `tokenAddresses`. This includes
|
|
30
|
+
// failed token transactions as well as other transactions sent to a token contract. If we do not
|
|
31
|
+
// recognize the `to` address in the next step, then we will not treat it as a token contract address.
|
|
32
|
+
tokenAddresses.push(serverTx.to)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return lodash
|
|
36
|
+
.uniq(tokenAddresses)
|
|
37
|
+
.map((address) => tokensByAddress.get(address)?.name)
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default function getSenderNonceKey(tx, ourWalletAddress): string {
|
|
2
|
+
if (Array.isArray(tx.from)) {
|
|
3
|
+
// Tx class. we don't define the 'from' address if our wallet was the sender.
|
|
4
|
+
const from = tx.from[0] || ourWalletAddress
|
|
5
|
+
return `${from}:${tx.data.nonce}`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// ethereum tx json object from magnifier
|
|
9
|
+
const txnonce = parseInt(tx.nonce, 16)
|
|
10
|
+
return `${tx.from}:${txnonce}`
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import lodash from 'lodash'
|
|
2
|
+
|
|
3
|
+
// Sorts an array of token transfer events into an object of transfer event arrays,
|
|
4
|
+
// whose keys are the names of the tokens which are being transferred in the corresponding array.
|
|
5
|
+
// Transfers whose token addresses are not present in the tokensByAddress map are ignored.
|
|
6
|
+
//
|
|
7
|
+
// i.e. [{ address: '0x1', from, to, value }, { address: '0x2', from, to, value }]
|
|
8
|
+
// -> { token1: [{ address: '0x1', from, to, value }], token2: [{ address: '0x2', from, to, value }] }
|
|
9
|
+
|
|
10
|
+
export default function getTransfersByTokenName(transfers, tokensByAddress) {
|
|
11
|
+
const transfersByTokenName = lodash.groupBy(
|
|
12
|
+
transfers,
|
|
13
|
+
(transfer) => tokensByAddress.get(transfer.address.toLowerCase())?.name || 'UNKNOWN'
|
|
14
|
+
)
|
|
15
|
+
return lodash.omit(transfersByTokenName, 'UNKNOWN')
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Transfer events for ETH or ERC20 tokens have the structure { from, to, value }.
|
|
2
|
+
// This determines the relative amount of a given asset that has been transfered in
|
|
3
|
+
// or out of ourWalletAddress as a number unit, assuming each item in the `transfers`
|
|
4
|
+
// array is a transfer for that asset. If we sent more than we received, the value is negative.
|
|
5
|
+
|
|
6
|
+
export default function getValueOfTransfers(ourWalletAddress, asset, transfers) {
|
|
7
|
+
return transfers
|
|
8
|
+
.reduce((balanceDifference, { from, to, value }) => {
|
|
9
|
+
const transferAmount = asset.currency.baseUnit(value)
|
|
10
|
+
|
|
11
|
+
if (from === ourWalletAddress) {
|
|
12
|
+
balanceDifference = balanceDifference.sub(transferAmount)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (to === ourWalletAddress) {
|
|
16
|
+
balanceDifference = balanceDifference.add(transferAmount)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return balanceDifference
|
|
20
|
+
}, asset.currency.ZERO)
|
|
21
|
+
.to(asset.ticker)
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as getDeriveDataNeededForTick } from './get-derive-data-needed-for-tick'
|
|
2
|
+
export { default as getAllLogItemsByAsset } from './get-all-log-items-by-asset'
|
|
3
|
+
export { default as getHistoryFromServer } from './get-history-from-server'
|
|
4
|
+
export { default as checkPendingTransactions } from './check-pending-transactions'
|
|
5
|
+
export { default as getDeriveTransactionsToCheck } from './get-derive-transactions-to-check'
|
|
6
|
+
|
|
7
|
+
export type { PendingTransactionsDictionary } from './types'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Token transactions are provided with either .from or .to address properties based this logic:
|
|
2
|
+
// - First, try to decide based on whether the token amount transferred is positive or negative.
|
|
3
|
+
// - If the amount is zero, try to decide based on whether the token transfer array
|
|
4
|
+
// contained only send or only receive type events.
|
|
5
|
+
// - Finally, if the value is zero and both receiving and sending transfer events
|
|
6
|
+
// exist, consider the TX as sent if our wallet was the TX initiator.
|
|
7
|
+
|
|
8
|
+
export default function isConsideredSentTokenTx({
|
|
9
|
+
coinAmount,
|
|
10
|
+
ourWalletWasSender,
|
|
11
|
+
sendingTransferPresent,
|
|
12
|
+
receivingTransferPresent,
|
|
13
|
+
}) {
|
|
14
|
+
if (coinAmount.isNegative) return true
|
|
15
|
+
if (coinAmount.isPositive) return false
|
|
16
|
+
|
|
17
|
+
// if the token TX's coinAmount is zero, the TX probably failed, so check
|
|
18
|
+
// whether the failed transfers were solely out of, or solely in to ourWalletAddress
|
|
19
|
+
if (sendingTransferPresent && !receivingTransferPresent) return true
|
|
20
|
+
if (!sendingTransferPresent && receivingTransferPresent) return false
|
|
21
|
+
|
|
22
|
+
// A rare edgecase which could only occur if somehow a token TX
|
|
23
|
+
// transferred out exactly as much as our wallet received.
|
|
24
|
+
return ourWalletWasSender
|
|
25
|
+
}
|