@exodus/ethereum-api 2.8.2 → 2.8.4
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 +10 -3
- package/src/eth-like-util.js +7 -0
- package/src/exodus-eth-server/api.js +6 -0
- package/src/exodus-eth-server/index.js +3 -1
- package/src/exodus-eth-server/ws.js +18 -8
- package/src/fee-monitor/harmony.js +12 -0
- package/src/fee-monitor/index.js +1 -0
- package/src/index.js +4 -0
- package/src/tx-log/__tests__/asset-client-interface-test-helper.js +22 -0
- package/src/tx-log/__tests__/assets-for-test-helper.js +30 -0
- package/src/tx-log/__tests__/bsc-history-return-values-for-test-helper.js +94 -0
- package/src/tx-log/__tests__/bsc-monitor.integration.test.js +166 -0
- package/src/tx-log/__tests__/bsc-monitor.test.js +184 -0
- package/src/tx-log/__tests__/ethereum-history-return-values-for-test-helper.js +357 -0
- package/src/tx-log/__tests__/ethereum-monitor.integration.test.js +174 -0
- package/src/tx-log/__tests__/ethereum-monitor.test.js +160 -0
- package/src/tx-log/__tests__/in-memory-asset-client-interface.js +196 -0
- package/src/tx-log/ethereum-monitor.js +271 -0
- package/src/tx-log/index.js +1 -0
- package/src/tx-log/ws-updates.js +75 -0
- package/src/websocket/index.js +23 -3
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { EthereumMonitor } from '../ethereum-monitor'
|
|
2
|
+
import { size, omit } from 'lodash'
|
|
3
|
+
import { AccountState } from '@exodus/models'
|
|
4
|
+
import { once } from 'events'
|
|
5
|
+
import { ethereum as feeData } from '@exodus/ethereum-lib/src/fee-data'
|
|
6
|
+
import assetMap from './assets-for-test-helper'
|
|
7
|
+
import { createAssetClientInterface } from './asset-client-interface-test-helper'
|
|
8
|
+
import { create } from '../../exodus-eth-server/api'
|
|
9
|
+
|
|
10
|
+
const EXODUS_ETH_SERVER_URL = 'https://geth.a.exodus.io/wallet/v1/'
|
|
11
|
+
|
|
12
|
+
jest.setTimeout(10000)
|
|
13
|
+
|
|
14
|
+
export const logger = {
|
|
15
|
+
trace: jest.fn(),
|
|
16
|
+
debug: jest.fn(),
|
|
17
|
+
log: jest.fn(),
|
|
18
|
+
info: jest.fn(),
|
|
19
|
+
warn: jest.fn(),
|
|
20
|
+
error: jest.fn(),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
Object.values(logger).forEach((fn) => fn.mockReset())
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const { ethereum } = assetMap
|
|
28
|
+
|
|
29
|
+
// Replacing default gasPrice to be sure WS will change it
|
|
30
|
+
const dummyGasPrice = ethereum.currency.Gwei(1) // 1 is the min fee...
|
|
31
|
+
const feeDataMock = feeData.update({ gasPrice: dummyGasPrice.toString() })
|
|
32
|
+
expect(feeDataMock.gasPrice).toEqual(dummyGasPrice)
|
|
33
|
+
|
|
34
|
+
export default class EthereumAccountState extends AccountState {
|
|
35
|
+
static defaults = {
|
|
36
|
+
cursor: '',
|
|
37
|
+
balance: ethereum.currency.ZERO,
|
|
38
|
+
tokenBalances: {},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ethereum.api = {
|
|
43
|
+
createAccountState: () => EthereumAccountState,
|
|
44
|
+
getConfirmationsNumber: () => 2,
|
|
45
|
+
getFeeData: () => feeDataMock,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fernando's dev wallet. Profile 1 and Profile 2
|
|
49
|
+
export const walletPublicKeys = {
|
|
50
|
+
ethereum: [
|
|
51
|
+
Buffer.from('0273c38a3c31c31b361dc8d6b93e56c316e34991c478d3a14ea3fcd1ab552bc25e', 'hex'),
|
|
52
|
+
Buffer.from('02f33b1edf1016f6720518c776da30567fc7b7052ae6b596dd22909e0a87164f8d', 'hex'),
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('ethereum monitor', () => {
|
|
57
|
+
test('can start monitor and update txs', async () => {
|
|
58
|
+
const assetClientInterface = createAssetClientInterface({ logger, walletPublicKeys })
|
|
59
|
+
const server = create(EXODUS_ETH_SERVER_URL)
|
|
60
|
+
const getHistoryV2 = jest.fn(async (...args) => {
|
|
61
|
+
const transactions = await server.getHistoryV2(...args)
|
|
62
|
+
return transactions.filter((tx) => {
|
|
63
|
+
return parseInt(tx.blockNumber, 16) < 14808250
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
const monitor = new EthereumMonitor({
|
|
67
|
+
interval: 20,
|
|
68
|
+
asset: ethereum,
|
|
69
|
+
assetClientInterface,
|
|
70
|
+
logger,
|
|
71
|
+
server: { ...server, getHistoryV2 },
|
|
72
|
+
})
|
|
73
|
+
try {
|
|
74
|
+
await monitor.start()
|
|
75
|
+
expect(logger.error).not.toBeCalled()
|
|
76
|
+
expect(logger.warn).not.toBeCalled()
|
|
77
|
+
const toBalanceFromTx = (txSet) => {
|
|
78
|
+
return txSet.getMutations().slice(-1)[0].balance
|
|
79
|
+
}
|
|
80
|
+
// Sintax sugar
|
|
81
|
+
const txs = (walletAccount, assetName) => {
|
|
82
|
+
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
83
|
+
}
|
|
84
|
+
const state = (walletAccount, assetName) => {
|
|
85
|
+
return assetClientInterface.getAccountState({ walletAccount, assetName })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const expectSameValue = (actual, expected) => {
|
|
89
|
+
expect(actual).toEqual(expected)
|
|
90
|
+
expect(actual.equals(expected)).toEqual(true)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
expect((await txs('exodus0', 'ethereum')).size).toEqual(7)
|
|
94
|
+
expect((await txs('exodus0', 'bat')).size).toEqual(1)
|
|
95
|
+
expect((await txs('exodus0', 'tetherusd')).size).toEqual(3)
|
|
96
|
+
expect((await txs('exodus1', 'ethereum')).size).toEqual(1)
|
|
97
|
+
|
|
98
|
+
expectSameValue(
|
|
99
|
+
toBalanceFromTx(await txs('exodus0', 'ethereum')),
|
|
100
|
+
ethereum.currency.defaultUnit('0.051201488965893697') // string due to floating error
|
|
101
|
+
)
|
|
102
|
+
expectSameValue(
|
|
103
|
+
toBalanceFromTx(await txs('exodus0', 'bat')),
|
|
104
|
+
assetMap.bat.currency.defaultUnit(8.42277112)
|
|
105
|
+
)
|
|
106
|
+
expectSameValue(
|
|
107
|
+
toBalanceFromTx(await txs('exodus0', 'tetherusd')),
|
|
108
|
+
assetMap.tetherusd.currency.defaultUnit(7.414404)
|
|
109
|
+
)
|
|
110
|
+
expectSameValue(
|
|
111
|
+
toBalanceFromTx(await txs('exodus1', 'ethereum')),
|
|
112
|
+
ethereum.currency.defaultUnit(0.00821921)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
// It seems eth balances and tokenBalances are not being filled. Clients needs to read tx like above
|
|
116
|
+
expectSameValue(
|
|
117
|
+
(await state('exodus0', 'ethereum')).balance,
|
|
118
|
+
ethereum.currency.defaultUnit(0)
|
|
119
|
+
)
|
|
120
|
+
expect((await state('exodus0', 'ethereum')).tokenBalances).toEqual({})
|
|
121
|
+
expectSameValue(
|
|
122
|
+
(await state('exodus1', 'ethereum')).balance,
|
|
123
|
+
ethereum.currency.defaultUnit(0)
|
|
124
|
+
)
|
|
125
|
+
expect((await state('exodus1', 'ethereum')).tokenBalances).toEqual({})
|
|
126
|
+
expect(size(assetClientInterface.states)).toEqual(2)
|
|
127
|
+
} finally {
|
|
128
|
+
await monitor.stop()
|
|
129
|
+
}
|
|
130
|
+
expect(logger.error).not.toBeCalled()
|
|
131
|
+
expect(logger.warn).not.toBeCalled()
|
|
132
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('can start and update gas price', async () => {
|
|
136
|
+
// ws is not reliable enough to finish on time.
|
|
137
|
+
const server = create(EXODUS_ETH_SERVER_URL)
|
|
138
|
+
const assetClientInterface = createAssetClientInterface({ logger, walletPublicKeys })
|
|
139
|
+
const monitor = new EthereumMonitor({
|
|
140
|
+
interval: 20,
|
|
141
|
+
asset: ethereum,
|
|
142
|
+
assetClientInterface,
|
|
143
|
+
logger,
|
|
144
|
+
server,
|
|
145
|
+
})
|
|
146
|
+
try {
|
|
147
|
+
expect(await assetClientInterface.getFeeData({ assetName: ethereum.name })).toEqual(
|
|
148
|
+
feeDataMock
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
const oncePromise = once(assetClientInterface, 'fee-config-updated')
|
|
152
|
+
await monitor.start()
|
|
153
|
+
await oncePromise
|
|
154
|
+
// Once started, feeData gets updated
|
|
155
|
+
expect(await assetClientInterface.getFeeData({ assetName: ethereum.name })).not.toEqual(
|
|
156
|
+
feeDataMock
|
|
157
|
+
)
|
|
158
|
+
// gas price and origin are the changed ones.
|
|
159
|
+
expect(
|
|
160
|
+
omit(
|
|
161
|
+
await assetClientInterface.getFeeData({ assetName: ethereum.name }),
|
|
162
|
+
'gasPrice',
|
|
163
|
+
'origin'
|
|
164
|
+
)
|
|
165
|
+
).toEqual(omit(feeDataMock, 'gasPrice', 'origin'))
|
|
166
|
+
} finally {
|
|
167
|
+
await monitor.stop()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
expect(logger.error).not.toBeCalled()
|
|
171
|
+
expect(logger.warn).not.toBeCalled()
|
|
172
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
173
|
+
})
|
|
174
|
+
})
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { EthereumMonitor } from '../ethereum-monitor'
|
|
2
|
+
import { encodePublic } from '@exodus/ethereum-lib'
|
|
3
|
+
import { createAssetClientInterface } from './asset-client-interface-test-helper'
|
|
4
|
+
import assetMap from './assets-for-test-helper'
|
|
5
|
+
import { AccountState } from '@exodus/models'
|
|
6
|
+
import { ethereum as feeData } from '@exodus/ethereum-lib/src/fee-data'
|
|
7
|
+
import historyReturnValuesForTest from './ethereum-history-return-values-for-test-helper'
|
|
8
|
+
import { size, cloneDeep } from 'lodash'
|
|
9
|
+
|
|
10
|
+
const { ethereum } = assetMap
|
|
11
|
+
|
|
12
|
+
export default class EthereumAccountState extends AccountState {
|
|
13
|
+
static defaults = {
|
|
14
|
+
cursor: '',
|
|
15
|
+
balance: ethereum.currency.ZERO,
|
|
16
|
+
tokenBalances: {},
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
ethereum.api = {
|
|
21
|
+
createAccountState: () => EthereumAccountState,
|
|
22
|
+
getConfirmationsNumber: () => 2,
|
|
23
|
+
getFeeData: () => feeData,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const logger = {
|
|
27
|
+
trace: jest.fn(),
|
|
28
|
+
debug: jest.fn(),
|
|
29
|
+
log: jest.fn(),
|
|
30
|
+
info: jest.fn(),
|
|
31
|
+
warn: jest.fn(),
|
|
32
|
+
error: jest.fn((...args) => console.error(args)),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
Object.values(logger).forEach((fn) => fn.mockReset())
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Fernando's dev wallet. Profile 1 and Profile 2
|
|
40
|
+
export const walletPublicKeys = {
|
|
41
|
+
ethereum: [
|
|
42
|
+
Buffer.from('0273c38a3c31c31b361dc8d6b93e56c316e34991c478d3a14ea3fcd1ab552bc25e', 'hex'),
|
|
43
|
+
Buffer.from('02f33b1edf1016f6720518c776da30567fc7b7052ae6b596dd22909e0a87164f8d', 'hex'),
|
|
44
|
+
],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('ethereum monitor', () => {
|
|
48
|
+
test('can create monitor', () => {
|
|
49
|
+
const server = {
|
|
50
|
+
getURL() {
|
|
51
|
+
return 'https://mockMe'
|
|
52
|
+
},
|
|
53
|
+
ws: { watch: jest.fn(), events: { on: jest.fn() } },
|
|
54
|
+
|
|
55
|
+
getHistoryV2: jest.fn(() => {
|
|
56
|
+
return Promise.resolve([])
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
59
|
+
const monitor = new EthereumMonitor({
|
|
60
|
+
interval: 20,
|
|
61
|
+
asset: ethereum,
|
|
62
|
+
assetClientInterface: createAssetClientInterface({ logger }),
|
|
63
|
+
logger,
|
|
64
|
+
server: server,
|
|
65
|
+
})
|
|
66
|
+
expect(monitor.server.getURL()).toEqual('https://mockMe')
|
|
67
|
+
|
|
68
|
+
expect(logger.warn).not.toBeCalled()
|
|
69
|
+
expect(logger.error).not.toBeCalled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('can start and stop monitor using simulated data', async () => {
|
|
73
|
+
const assetClientInterface = createAssetClientInterface({ logger, walletPublicKeys })
|
|
74
|
+
|
|
75
|
+
const getHistoryV2Returns = cloneDeep(historyReturnValuesForTest)
|
|
76
|
+
const server = {
|
|
77
|
+
getURL() {
|
|
78
|
+
return 'https://mockMe'
|
|
79
|
+
},
|
|
80
|
+
ws: { watch: jest.fn(), events: { on: jest.fn() }, open: jest.fn() },
|
|
81
|
+
|
|
82
|
+
getHistoryV2: jest.fn((address) => {
|
|
83
|
+
const history = getHistoryV2Returns[address]
|
|
84
|
+
const txPage = history?.shift()
|
|
85
|
+
return Promise.resolve(txPage || [])
|
|
86
|
+
}),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const monitor = new EthereumMonitor({
|
|
90
|
+
interval: 20,
|
|
91
|
+
asset: ethereum,
|
|
92
|
+
assetClientInterface,
|
|
93
|
+
logger,
|
|
94
|
+
server: server,
|
|
95
|
+
})
|
|
96
|
+
await monitor.start()
|
|
97
|
+
await monitor.stop()
|
|
98
|
+
expect(logger.warn).not.toBeCalled()
|
|
99
|
+
expect(logger.error).not.toBeCalled()
|
|
100
|
+
|
|
101
|
+
const toBalanceFromTx = (txSet) => {
|
|
102
|
+
return txSet.getMutations().slice(-1)[0].balance
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Sintax sugar
|
|
106
|
+
const txs = (walletAccount, assetName) => {
|
|
107
|
+
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
108
|
+
}
|
|
109
|
+
const state = (walletAccount, assetName) => {
|
|
110
|
+
return assetClientInterface.getAccountState({ walletAccount, assetName })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const expectSameValue = (actual, expected) => {
|
|
114
|
+
expect(actual).toEqual(expected)
|
|
115
|
+
expect(actual.equals(expected)).toEqual(true)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
expect((await txs('exodus0', 'ethereum')).size).toEqual(7)
|
|
119
|
+
expect((await txs('exodus0', 'bat')).size).toEqual(1)
|
|
120
|
+
expect((await txs('exodus0', 'tetherusd')).size).toEqual(3)
|
|
121
|
+
expect((await txs('exodus1', 'ethereum')).size).toEqual(1)
|
|
122
|
+
|
|
123
|
+
expectSameValue(
|
|
124
|
+
toBalanceFromTx(await txs('exodus0', 'ethereum')),
|
|
125
|
+
ethereum.currency.defaultUnit('0.051201488965893697') // string due to floating error
|
|
126
|
+
)
|
|
127
|
+
expectSameValue(
|
|
128
|
+
toBalanceFromTx(await txs('exodus0', 'bat')),
|
|
129
|
+
assetMap.bat.currency.defaultUnit(8.42277112)
|
|
130
|
+
)
|
|
131
|
+
expectSameValue(
|
|
132
|
+
toBalanceFromTx(await txs('exodus0', 'tetherusd')),
|
|
133
|
+
assetMap.tetherusd.currency.defaultUnit(7.414404)
|
|
134
|
+
)
|
|
135
|
+
expectSameValue(
|
|
136
|
+
toBalanceFromTx(await txs('exodus1', 'ethereum')),
|
|
137
|
+
ethereum.currency.defaultUnit(0.00821921)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
// It seems eth balances and tokenBalances are not being filled. Clients needs to read tx like above
|
|
141
|
+
expectSameValue((await state('exodus0', 'ethereum')).balance, ethereum.currency.defaultUnit(0))
|
|
142
|
+
expect((await state('exodus0', 'ethereum')).tokenBalances).toEqual({})
|
|
143
|
+
expectSameValue((await state('exodus1', 'ethereum')).balance, ethereum.currency.defaultUnit(0))
|
|
144
|
+
expect((await state('exodus1', 'ethereum')).tokenBalances).toEqual({})
|
|
145
|
+
expect(size(assetClientInterface.states)).toEqual(2)
|
|
146
|
+
|
|
147
|
+
expect(logger.error).not.toBeCalled()
|
|
148
|
+
expect(logger.warn).not.toBeCalled()
|
|
149
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('validate address', () => {
|
|
153
|
+
expect(encodePublic(walletPublicKeys.ethereum[0])).toEqual(
|
|
154
|
+
'0x90E481d9A664ebbE4Be180d9501962255463036d'
|
|
155
|
+
)
|
|
156
|
+
expect(encodePublic(walletPublicKeys.ethereum[1])).toEqual(
|
|
157
|
+
'0xf6c138C36341138dDFC314a11038dA8264B7Ef09'
|
|
158
|
+
)
|
|
159
|
+
})
|
|
160
|
+
})
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { pickBy, isEmpty, partition, isObject, bindAll, functions } from 'lodash'
|
|
2
|
+
import assert from 'minimalistic-assert'
|
|
3
|
+
import { Tx, TxSet } from '@exodus/models'
|
|
4
|
+
import { EventEmitter } from 'events'
|
|
5
|
+
|
|
6
|
+
const getKey = ({ assetName, walletAccount }) => `${walletAccount}:${assetName}`
|
|
7
|
+
const getAssetOnlyKey = ({ assetName }) => assetName
|
|
8
|
+
|
|
9
|
+
const createTxSet = ({ txs, coinName, assets }) => {
|
|
10
|
+
const values = txs.map((tx) => Tx.fromJSON({ ...tx, date: tx.date || new Date(), coinName }))
|
|
11
|
+
return TxSet.fromArray(values, { assets })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const createBatch = () => {
|
|
15
|
+
const batch = []
|
|
16
|
+
const push = (promise) => batch.push(promise)
|
|
17
|
+
const exec = async () => {
|
|
18
|
+
// enforce sequential execution
|
|
19
|
+
for (const promise in batch) {
|
|
20
|
+
await promise
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { push, exec }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class InMemoryAssetClientInterface extends EventEmitter {
|
|
27
|
+
constructor({ assets, logger, walletAccounts, wallet }) {
|
|
28
|
+
super()
|
|
29
|
+
assert(assets, 'assets is required')
|
|
30
|
+
assert(logger, 'logger is required')
|
|
31
|
+
assert(walletAccounts, 'walletAccounts is required')
|
|
32
|
+
assert(wallet, 'wallet is required')
|
|
33
|
+
assert(isObject(assets), 'assets must be an object indexed by asset name')
|
|
34
|
+
this.assets = assets
|
|
35
|
+
this.logger = logger
|
|
36
|
+
this.wallet = wallet
|
|
37
|
+
this.feeData = Object.values(assets)
|
|
38
|
+
.filter((asset) => asset.name === asset.baseAsset.name)
|
|
39
|
+
.filter((asset) => asset.api?.getFeeData)
|
|
40
|
+
.reduce((acc, asset) => ({ ...acc, [asset.name]: asset.api.getFeeData() }), {})
|
|
41
|
+
this.walletAccounts = walletAccounts
|
|
42
|
+
this.txs = {}
|
|
43
|
+
this.states = {}
|
|
44
|
+
bindAll(this, functions(this))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async getTxLog({ assetName, walletAccount }) {
|
|
48
|
+
const key = getKey({ assetName, walletAccount })
|
|
49
|
+
if (!this.txs[key]) {
|
|
50
|
+
return TxSet.EMPTY
|
|
51
|
+
}
|
|
52
|
+
return this.txs[key]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async updateTxLogAndNotify({ assetName, walletAccount, txs }) {
|
|
56
|
+
const batch = this.updateTxLogAndNotifyBatch({
|
|
57
|
+
assetName,
|
|
58
|
+
walletAccount,
|
|
59
|
+
txs,
|
|
60
|
+
})
|
|
61
|
+
return batch.exec()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
updateTxLogAndNotifyBatch({ assetName, walletAccount, txs, batch = createBatch() }) {
|
|
65
|
+
batch.push(this.updateTxs({ assetName, walletAccount, txs }))
|
|
66
|
+
return batch
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async updateTxs({ assetName, walletAccount, txs }) {
|
|
70
|
+
const currentTxLog = await this.getTxLog({ assetName, walletAccount })
|
|
71
|
+
const [existingLogItems, newLogItems] = partition(txs, (v) => currentTxLog.get(v.txId))
|
|
72
|
+
let newTxSet = currentTxLog
|
|
73
|
+
if (existingLogItems.length > 0) {
|
|
74
|
+
newTxSet = newTxSet.updateTxsProperties(existingLogItems)
|
|
75
|
+
}
|
|
76
|
+
if (newLogItems.length > 0) {
|
|
77
|
+
newTxSet = newTxSet.union(
|
|
78
|
+
createTxSet({ txs: newLogItems, coinName: assetName, assets: this.assets })
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
const key = getKey({ assetName, walletAccount })
|
|
82
|
+
if (newTxSet.size > 0) {
|
|
83
|
+
this.txs[key] = newTxSet
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async getAccountState({ assetName, walletAccount }) {
|
|
88
|
+
const asset = this.assets[assetName]
|
|
89
|
+
const key = getKey({ assetName, walletAccount })
|
|
90
|
+
|
|
91
|
+
if (!this.states[key]) {
|
|
92
|
+
const accountState = asset.api?.createAccountState?.()?.create()
|
|
93
|
+
if (!accountState) {
|
|
94
|
+
throw new Error(`Asset ${asset.name} does not support account state!`)
|
|
95
|
+
}
|
|
96
|
+
this.states[key] = accountState
|
|
97
|
+
}
|
|
98
|
+
return this.states[key]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async updateAccountState({ assetName, walletAccount, newData }) {
|
|
102
|
+
this.updateAccountStateBatch({
|
|
103
|
+
assetName,
|
|
104
|
+
walletAccount,
|
|
105
|
+
newData,
|
|
106
|
+
}).exec()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
updateAccountStateBatch({ assetName, walletAccount, newData, batch = createBatch() }) {
|
|
110
|
+
batch.push(
|
|
111
|
+
this._basicUpdateAccountState({
|
|
112
|
+
assetName,
|
|
113
|
+
walletAccount,
|
|
114
|
+
newData,
|
|
115
|
+
})
|
|
116
|
+
)
|
|
117
|
+
return batch
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async _basicUpdateAccountState({ assetName, walletAccount, newData }) {
|
|
121
|
+
if (!isEmpty(newData)) {
|
|
122
|
+
const key = getKey({ assetName, walletAccount })
|
|
123
|
+
const currentAccountState = await this.getAccountState({ assetName, walletAccount })
|
|
124
|
+
const newAccountState = currentAccountState.merge(newData)
|
|
125
|
+
this.states[key] = newAccountState
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async getReceiveAddresses({ assetName, walletAccount, chainIndex, addressIndex }) {
|
|
130
|
+
return [await this.getReceiveAddress({ assetName, walletAccount, chainIndex, addressIndex })]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async getReceiveAddress({ assetName, walletAccount, chainIndex, addressIndex }) {
|
|
134
|
+
const asset = this.assets[assetName]
|
|
135
|
+
const baseAsset = asset.baseAsset
|
|
136
|
+
assert(this.walletAccounts[walletAccount], 'Invalid walletAccount')
|
|
137
|
+
const accountIndex = this.walletAccounts[walletAccount].index
|
|
138
|
+
const { publicKey } = await this.wallet.genPublicKey({
|
|
139
|
+
baseAssetName: baseAsset.name,
|
|
140
|
+
accountIndex,
|
|
141
|
+
chainIndex,
|
|
142
|
+
addressIndex,
|
|
143
|
+
})
|
|
144
|
+
return asset.keys.encodePublic(publicKey).toString()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async getWalletAccountsByAsset() {
|
|
148
|
+
const baseAssets = Object.values(this.assets).filter(
|
|
149
|
+
(asset) => asset.name === asset.baseAsset.name
|
|
150
|
+
)
|
|
151
|
+
const walletAccountMap = {}
|
|
152
|
+
for (const baseAsset of baseAssets) {
|
|
153
|
+
walletAccountMap[baseAsset.name] = Object.keys(this.walletAccounts)
|
|
154
|
+
}
|
|
155
|
+
return walletAccountMap
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async getWalletAccounts({ assetName }) {
|
|
159
|
+
const walletAccountMap = await this.getWalletAccountsByAsset()
|
|
160
|
+
const walletAccounts = walletAccountMap[assetName]
|
|
161
|
+
assert(walletAccounts, `${assetName} is not supported`)
|
|
162
|
+
return walletAccounts
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async getAssetsForNetwork({ baseAssetName }) {
|
|
166
|
+
return pickBy(this.assets, (asset) => asset.baseAsset.name === baseAssetName)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async fetchToken({ assetId, baseAssetName }) {
|
|
170
|
+
throw new Error('implement!')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async addToken({ assetId, baseAssetName, allowedStatusList }) {
|
|
174
|
+
throw new Error('Implement!')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async getConfirmationsNumber({ assetName }) {
|
|
178
|
+
const baseAsset = this.assets[assetName].baseAsset
|
|
179
|
+
return baseAsset.api?.getConfirmationsNumber ? baseAsset.api.getConfirmationsNumber() : 1
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async getFeeData({ assetName }) {
|
|
183
|
+
const feeData = this.feeData[getAssetOnlyKey({ assetName })]
|
|
184
|
+
if (!feeData) {
|
|
185
|
+
throw new Error(`Asset ${assetName} does not support fee data`)
|
|
186
|
+
}
|
|
187
|
+
return feeData
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async updateFeeConfig({ assetName, feeConfig }) {
|
|
191
|
+
const currentFeeData = await this.getFeeData({ assetName })
|
|
192
|
+
const newFeeData = currentFeeData.update(feeConfig)
|
|
193
|
+
this.feeData[getAssetOnlyKey({ assetName })] = newFeeData
|
|
194
|
+
this.emit('fee-config-updated')
|
|
195
|
+
}
|
|
196
|
+
}
|