aboutsummaryrefslogtreecommitdiffstats
path: root/packages/0x.js/src/web3_wrapper.ts
blob: 6a6b4e760f8b3d61d5273d9b175f79ad085221c8 (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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import BigNumber from 'bignumber.js';
import * as _ from 'lodash';
import * as Web3 from 'web3';

import {Artifact, ArtifactContractName, TransactionReceipt, TxData, ZeroExError} from './types';
import {promisify} from './utils/promisify';

interface RawLogEntry {
    logIndex: string|null;
    transactionIndex: string|null;
    transactionHash: string;
    blockHash: string|null;
    blockNumber: string|null;
    address: string;
    data: string;
    topics: string[];
}

const CONTRACT_NAME_TO_NOT_FOUND_ERROR: {[contractName: string]: ZeroExError} = {
    ZRX: ZeroExError.ZRXContractDoesNotExist,
    EtherToken: ZeroExError.EtherTokenContractDoesNotExist,
    Token: ZeroExError.TokenContractDoesNotExist,
    TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist,
    TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist,
    Exchange: ZeroExError.ExchangeContractDoesNotExist,
};

export class Web3Wrapper {
    private web3: Web3;
    private networkId: number;
    private defaults: Partial<TxData>;
    private jsonRpcRequestId: number;
    constructor(provider: Web3.Provider, networkId: number, defaults?: Partial<TxData>) {
        if (_.isUndefined((provider as any).sendAsync)) {
            // Web3@1.0 provider doesn't support synchronous http requests,
            // so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x`
            // We re-assign the send method so that Web3@1.0 providers work with 0x.js
            (provider as any).sendAsync = (provider as any).send;
        }
        this.web3 = new Web3();
        this.networkId = networkId;
        this.web3.setProvider(provider);
        this.defaults = defaults || {};
        this.jsonRpcRequestId = 0;
    }
    public getContractDefaults(): Partial<TxData> {
        return this.defaults;
    }
    public setProvider(provider: Web3.Provider, networkId: number) {
        this.networkId = networkId;
        this.web3.setProvider(provider);
    }
    public isAddress(address: string): boolean {
        return this.web3.isAddress(address);
    }
    public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> {
        const addresses = await this.getAvailableAddressesAsync();
        return _.includes(addresses, senderAddress);
    }
    public async getNodeVersionAsync(): Promise<string> {
        const nodeVersion = await promisify<string>(this.web3.version.getNode)();
        return nodeVersion;
    }
    public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
        const transactionReceipt = await promisify<TransactionReceipt>(this.web3.eth.getTransactionReceipt)(txHash);
        if (!_.isNull(transactionReceipt)) {
            transactionReceipt.status = this.normalizeTxReceiptStatus(transactionReceipt.status);
        }
        return transactionReceipt;
    }
    public getCurrentProvider(): Web3.Provider {
        return this.web3.currentProvider;
    }
    public getNetworkId(): number {
        return this.networkId;
    }
    public async getContractInstanceFromArtifactAsync(
        artifact: Artifact, address?: string,
    ): Promise<Web3.ContractInstance> {
        let contractAddress: string;
        if (_.isUndefined(address)) {
            const networkId = this.getNetworkId();
            if (_.isUndefined(artifact.networks[networkId])) {
                throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
            }
            contractAddress = artifact.networks[networkId].address.toLowerCase();
        } else {
            contractAddress = address;
        }
        const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress);
        if (!doesContractExist) {
            throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]);
        }
        const contractInstance = this.getContractInstance(
            artifact.abi, contractAddress,
        );
        return contractInstance;
    }
    public toWei(ethAmount: BigNumber): BigNumber {
        const balanceWei = this.web3.toWei(ethAmount, 'ether');
        return balanceWei;
    }
    public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
        let balanceInWei = await promisify<BigNumber>(this.web3.eth.getBalance)(owner);
        balanceInWei = new BigNumber(balanceInWei);
        return balanceInWei;
    }
    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 accommodate poorly implemented clients
        const codeIsEmpty = /^0x0{0,40}$/i.test(code);
        return !codeIsEmpty;
    }
    public async signTransactionAsync(address: string, message: string): Promise<string> {
        const signData = await promisify<string>(this.web3.eth.sign)(address, message);
        return signData;
    }
    public async getBlockNumberAsync(): Promise<number> {
        const blockNumber = await promisify<number>(this.web3.eth.getBlockNumber)();
        return blockNumber;
    }
    public async getBlockAsync(blockParam: string|Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> {
        const block = await promisify<Web3.BlockWithoutTransactionData>(this.web3.eth.getBlock)(blockParam);
        return block;
    }
    public async getBlockTimestampAsync(blockParam: string|Web3.BlockParam): Promise<number> {
        const {timestamp} = await this.getBlockAsync(blockParam);
        return timestamp;
    }
    public async getAvailableAddressesAsync(): Promise<string[]> {
        const addresses = await promisify<string[]>(this.web3.eth.getAccounts)();
        return addresses;
    }
    public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> {
        let fromBlock = filter.fromBlock;
        if (_.isNumber(fromBlock)) {
            fromBlock = this.web3.toHex(fromBlock);
        }
        let toBlock = filter.toBlock;
        if (_.isNumber(toBlock)) {
            toBlock = this.web3.toHex(toBlock);
        }
        const serializedFilter = {
            ...filter,
            fromBlock,
            toBlock,
        };
        const payload = {
            jsonrpc: '2.0',
            id: this.jsonRpcRequestId++,
            method: 'eth_getLogs',
            params: [serializedFilter],
        };
        const rawLogs = await this.sendRawPayloadAsync<RawLogEntry[]>(payload);
        const formattedLogs = _.map(rawLogs, this.formatLog.bind(this));
        return formattedLogs;
    }
    private getContractInstance(abi: Web3.ContractAbi, address: string): Web3.ContractInstance {
        const web3ContractInstance = this.web3.eth.contract(abi).at(address);
        return web3ContractInstance;
    }
    private async getNetworkAsync(): Promise<number> {
        const networkId = await promisify<number>(this.web3.version.getNetwork)();
        return networkId;
    }
    private async sendRawPayloadAsync<A>(payload: Web3.JSONRPCRequestPayload): Promise<A> {
        const sendAsync = this.web3.currentProvider.sendAsync.bind(this.web3.currentProvider);
        const response = await promisify<Web3.JSONRPCResponsePayload>(sendAsync)(payload);
        const result = response.result;
        return result;
    }
    private normalizeTxReceiptStatus(status: undefined|null|string|0|1): null|0|1 {
        // Transaction status might have four values
        // undefined - Testrpc and other old clients
        // null - New clients on old transactions
        // number - Parity
        // hex - Geth
        if (_.isString(status)) {
            return this.web3.toDecimal(status) as 0|1;
        } else if (_.isUndefined(status)) {
            return null;
        } else {
            return status;
        }
    }
    private formatLog(rawLog: RawLogEntry): Web3.LogEntry {
        const formattedLog = {
            ...rawLog,
            logIndex: this.hexToDecimal(rawLog.logIndex),
            blockNumber: this.hexToDecimal(rawLog.blockNumber),
            transactionIndex: this.hexToDecimal(rawLog.transactionIndex),
        };
        return formattedLog;
    }
    private hexToDecimal(hex: string|null): number|null {
        if (_.isNull(hex)) {
            return null;
        }
        const decimal = this.web3.toDecimal(hex);
        return decimal;
    }
}