aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/web3_wrapper.ts
blob: 0cd2a5cc28bb2d8a64a0197d61444bb457584985 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import { BigNumber, intervalUtils, promisify } from '@0xproject/utils';
import * as _ from 'lodash';
import { Dispatcher } from 'ts/redux/dispatcher';
import { utils } from 'ts/utils/utils';
import * as Web3 from 'web3';

export class Web3Wrapper {
    private _dispatcher: Dispatcher;
    private _web3: Web3;
    private _prevNetworkId: number;
    private _shouldPollUserAddress: boolean;
    private _watchNetworkAndBalanceIntervalId: NodeJS.Timer;
    private _prevUserEtherBalanceInEth: BigNumber;
    private _prevUserAddress: string;
    constructor(
        dispatcher: Dispatcher,
        provider: Web3.Provider,
        networkIdIfExists: number,
        shouldPollUserAddress: boolean,
    ) {
        this._dispatcher = dispatcher;
        this._prevNetworkId = networkIdIfExists;
        this._shouldPollUserAddress = shouldPollUserAddress;

        this._web3 = new Web3();
        this._web3.setProvider(provider);

        // tslint:disable-next-line:no-floating-promises
        this._startEmittingNetworkConnectionAndUserBalanceStateAsync();
    }
    public isAddress(address: string) {
        return this._web3.isAddress(address);
    }
    public async getAccountsAsync(): Promise<string[]> {
        const addresses = await promisify<string[]>(this._web3.eth.getAccounts)();
        return addresses;
    }
    public async getFirstAccountIfExistsAsync() {
        const addresses = await this.getAccountsAsync();
        if (_.isEmpty(addresses)) {
            return '';
        }
        return addresses[0];
    }
    public async getNodeVersionAsync(): Promise<string> {
        const nodeVersion = await promisify<string>(this._web3.version.getNode)();
        return nodeVersion;
    }
    public getProviderObj() {
        return this._web3.currentProvider;
    }
    public async getNetworkIdIfExists() {
        try {
            const networkId = await this._getNetworkAsync();
            return Number(networkId);
        } catch (err) {
            return undefined;
        }
    }
    public async getBalanceInEthAsync(owner: string): Promise<BigNumber> {
        const balanceInWei: BigNumber = await promisify<BigNumber>(this._web3.eth.getBalance)(owner);
        const balanceEthOldBigNumber = this._web3.fromWei(balanceInWei, 'ether');
        const balanceEth = new BigNumber(balanceEthOldBigNumber);
        return balanceEth;
    }
    public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
        const code = await promisify<string>(this._web3.eth.getCode)(address);
        // Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients
        const zeroHexAddressRegex = /^0[xX][0]*$/;
        const didFindCode = _.isNull(code.match(zeroHexAddressRegex));
        return didFindCode;
    }
    public async signTransactionAsync(address: string, message: string): Promise<string> {
        const signData = await promisify<string>(this._web3.eth.sign)(address, message);
        return signData;
    }
    public async getBlockTimestampAsync(blockHash: string): Promise<number> {
        const { timestamp } = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockHash);
        return timestamp;
    }
    public destroy() {
        this._stopEmittingNetworkConnectionAndUserBalanceStateAsync();
        // HACK: stop() is only available on providerEngine instances
        const provider = this._web3.currentProvider;
        if (!_.isUndefined((provider as any).stop)) {
            (provider as any).stop();
        }
    }
    // This should only be called from the LedgerConfigDialog
    public updatePrevUserAddress(userAddress: string) {
        this._prevUserAddress = userAddress;
    }
    private async _getNetworkAsync() {
        const networkId = await promisify(this._web3.version.getNetwork)();
        return networkId;
    }
    private async _startEmittingNetworkConnectionAndUserBalanceStateAsync() {
        if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
            return; // we are already emitting the state
        }

        let prevNodeVersion: string;
        this._prevUserEtherBalanceInEth = new BigNumber(0);
        this._dispatcher.updateNetworkId(this._prevNetworkId);
        this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
            async () => {
                // Check for network state changes
                const currentNetworkId = await this.getNetworkIdIfExists();
                if (currentNetworkId !== this._prevNetworkId) {
                    this._prevNetworkId = currentNetworkId;
                    this._dispatcher.updateNetworkId(currentNetworkId);
                }

                // Check for node version changes
                const currentNodeVersion = await this.getNodeVersionAsync();
                if (currentNodeVersion !== prevNodeVersion) {
                    prevNodeVersion = currentNodeVersion;
                    this._dispatcher.updateNodeVersion(currentNodeVersion);
                }

                if (this._shouldPollUserAddress) {
                    const userAddressIfExists = await this.getFirstAccountIfExistsAsync();
                    // Update makerAddress on network change
                    if (this._prevUserAddress !== userAddressIfExists) {
                        this._prevUserAddress = userAddressIfExists;
                        this._dispatcher.updateUserAddress(userAddressIfExists);
                    }

                    // Check for user ether balance changes
                    if (userAddressIfExists !== '') {
                        await this._updateUserEtherBalanceAsync(userAddressIfExists);
                    }
                } else {
                    // This logic is primarily for the Ledger, since we don't regularly poll for the address
                    // we simply update the balance for the last fetched address.
                    if (!_.isEmpty(this._prevUserAddress)) {
                        await this._updateUserEtherBalanceAsync(this._prevUserAddress);
                    }
                }
            },
            5000,
            (err: Error) => {
                utils.consoleLog(`Watching network and balances failed: ${err}`);
                this._stopEmittingNetworkConnectionAndUserBalanceStateAsync();
            },
        );
    }
    private async _updateUserEtherBalanceAsync(userAddress: string) {
        const balance = await this.getBalanceInEthAsync(userAddress);
        if (!balance.eq(this._prevUserEtherBalanceInEth)) {
            this._prevUserEtherBalanceInEth = balance;
            this._dispatcher.updateUserEtherBalance(balance);
        }
    }
    private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() {
        intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
    }
}