web3.js 14 KB
Newer Older
Marian Oancea's avatar
Marian Oancea committed
1
/*
Marian Oancea's avatar
Marian Oancea committed
2
    This file is part of ethereum.js.
Marian Oancea's avatar
Marian Oancea committed
3

Marian Oancea's avatar
Marian Oancea committed
4 5 6 7
    ethereum.js is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
Marian Oancea's avatar
Marian Oancea committed
8

Marian Oancea's avatar
Marian Oancea committed
9 10 11 12
    ethereum.js is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
Marian Oancea's avatar
Marian Oancea committed
13

Marian Oancea's avatar
Marian Oancea committed
14 15
    You should have received a copy of the GNU Lesser General Public License
    along with ethereum.js.  If not, see <http://www.gnu.org/licenses/>.
Marian Oancea's avatar
Marian Oancea committed
16 17
*/
/** @file main.js
18 19 20 21
 * @authors:
 *   Jeffrey Wilcke <jeff@ethdev.com>
 *   Marek Kotewicz <marek@ethdev.com>
 *   Marian Oancea <marian@ethdev.com>
Gav Wood's avatar
Gav Wood committed
22
 *   Gav Wood <g@ethdev.com>
23 24
 * @date 2014
 */
Marian Oancea's avatar
Marian Oancea committed
25

Marek Kotewicz's avatar
Marek Kotewicz committed
26 27 28 29
function flattenPromise (obj) {
    if (obj instanceof Promise) {
        return Promise.resolve(obj);
    }
Marian Oancea's avatar
Marian Oancea committed
30

Marek Kotewicz's avatar
Marek Kotewicz committed
31 32 33 34
    if (obj instanceof Array) {
        return new Promise(function (resolve) {
            var promises = obj.map(function (o) {
                return flattenPromise(o);
Marian Oancea's avatar
Marian Oancea committed
35
            });
Marian Oancea's avatar
Marian Oancea committed
36

Marek Kotewicz's avatar
Marek Kotewicz committed
37 38 39 40 41
            return Promise.all(promises).then(function (res) {
                for (var i = 0; i < obj.length; i++) {
                    obj[i] = res[i];
                }
                resolve(obj);
Marian Oancea's avatar
Marian Oancea committed
42
            });
Marek Kotewicz's avatar
Marek Kotewicz committed
43
        });
Marian Oancea's avatar
Marian Oancea committed
44 45
    }

Marek Kotewicz's avatar
Marek Kotewicz committed
46 47 48 49 50 51
    if (obj instanceof Object) {
        return new Promise(function (resolve) {
            var keys = Object.keys(obj);
            var promises = keys.map(function (key) {
                return flattenPromise(obj[key]);
            });
Marian Oancea's avatar
Marian Oancea committed
52

Marek Kotewicz's avatar
Marek Kotewicz committed
53 54 55 56 57 58 59 60
            return Promise.all(promises).then(function (res) {
                for (var i = 0; i < keys.length; i++) {
                    obj[keys[i]] = res[i];
                }
                resolve(obj);
            });
        });
    }
Marian Oancea's avatar
Marian Oancea committed
61

Marek Kotewicz's avatar
Marek Kotewicz committed
62 63
    return Promise.resolve(obj);
}
Marian Oancea's avatar
Marian Oancea committed
64

Gav Wood's avatar
Gav Wood committed
65 66 67 68 69 70
var web3Methods = function () {
    return [
    { name: 'sha3', call: 'web3_sha3' }
    ];
};

Marek Kotewicz's avatar
Marek Kotewicz committed
71 72 73
var ethMethods = function () {
    var blockCall = function (args) {
        return typeof args[0] === "string" ? "eth_blockByHash" : "eth_blockByNumber";
Marian Oancea's avatar
Marian Oancea committed
74 75
    };

Marek Kotewicz's avatar
Marek Kotewicz committed
76
    var transactionCall = function (args) {
Gav Wood's avatar
Gav Wood committed
77
        return typeof args[0] === "string" ? 'eth_transactionByHash' : 'eth_transactionByNumber';
Marian Oancea's avatar
Marian Oancea committed
78 79
    };

Marek Kotewicz's avatar
Marek Kotewicz committed
80
    var uncleCall = function (args) {
Gav Wood's avatar
Gav Wood committed
81
        return typeof args[0] === "string" ? 'eth_uncleByHash' : 'eth_uncleByNumber';
Marian Oancea's avatar
Marian Oancea committed
82 83
    };

Marek Kotewicz's avatar
Marek Kotewicz committed
84 85 86
    var methods = [
    { name: 'balanceAt', call: 'eth_balanceAt' },
    { name: 'stateAt', call: 'eth_stateAt' },
Marek Kotewicz's avatar
Marek Kotewicz committed
87
    { name: 'storageAt', call: 'eth_storageAt' },
Marek Kotewicz's avatar
Marek Kotewicz committed
88 89 90 91 92 93 94
    { name: 'countAt', call: 'eth_countAt'},
    { name: 'codeAt', call: 'eth_codeAt' },
    { name: 'transact', call: 'eth_transact' },
    { name: 'call', call: 'eth_call' },
    { name: 'block', call: blockCall },
    { name: 'transaction', call: transactionCall },
    { name: 'uncle', call: uncleCall },
95 96
    { name: 'compilers', call: 'eth_compilers' },
    { name: 'lll', call: 'eth_lll' },
97
    { name: 'solidity', call: 'eth_solidity' },
Marek Kotewicz's avatar
Marek Kotewicz committed
98 99
    { name: 'serpent', call: 'eth_serpent' },
    { name: 'logs', call: 'eth_logs' }
Marek Kotewicz's avatar
Marek Kotewicz committed
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
    ];
    return methods;
};

var ethProperties = function () {
    return [
    { name: 'coinbase', getter: 'eth_coinbase', setter: 'eth_setCoinbase' },
    { name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' },
    { name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' },
    { name: 'gasPrice', getter: 'eth_gasPrice' },
    { name: 'account', getter: 'eth_account' },
    { name: 'accounts', getter: 'eth_accounts' },
    { name: 'peerCount', getter: 'eth_peerCount' },
    { name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' },
    { name: 'number', getter: 'eth_number'}
    ];
};

var dbMethods = function () {
    return [
    { name: 'put', call: 'db_put' },
    { name: 'get', call: 'db_get' },
    { name: 'putString', call: 'db_putString' },
    { name: 'getString', call: 'db_getString' }
    ];
};

var shhMethods = function () {
    return [
    { name: 'post', call: 'shh_post' },
    { name: 'newIdentity', call: 'shh_newIdentity' },
    { name: 'haveIdentity', call: 'shh_haveIdentity' },
    { name: 'newGroup', call: 'shh_newGroup' },
    { name: 'addToGroup', call: 'shh_addToGroup' }
    ];
};

var ethWatchMethods = function () {
    var newFilter = function (args) {
        return typeof args[0] === 'string' ? 'eth_newFilterString' : 'eth_newFilter';
Marian Oancea's avatar
Marian Oancea committed
140 141
    };

Marek Kotewicz's avatar
Marek Kotewicz committed
142 143 144
    return [
    { name: 'newFilter', call: newFilter },
    { name: 'uninstallFilter', call: 'eth_uninstallFilter' },
Marek Kotewicz's avatar
Marek Kotewicz committed
145
    { name: 'getMessages', call: 'eth_filterLogs' }
Marek Kotewicz's avatar
Marek Kotewicz committed
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
    ];
};

var shhWatchMethods = function () {
    return [
    { name: 'newFilter', call: 'shh_newFilter' },
    { name: 'uninstallFilter', call: 'shh_uninstallFilter' },
    { name: 'getMessage', call: 'shh_getMessages' }
    ];
};

var setupMethods = function (obj, methods) {
    methods.forEach(function (method) {
        obj[method.name] = function () {
            return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) {
                var call = typeof method.call === "function" ? method.call(args) : method.call;
                return {call: call, args: args};
            }).then(function (request) {
                return new Promise(function (resolve, reject) {
                    web3.provider.send(request, function (err, result) {
                        if (!err) {
                            resolve(result);
                            return;
                        }
                        reject(err);
                    });
                });
            }).catch(function(err) {
                console.error(err);
            });
Marian Oancea's avatar
Marian Oancea committed
176
        };
Marek Kotewicz's avatar
Marek Kotewicz committed
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
    });
};

var setupProperties = function (obj, properties) {
    properties.forEach(function (property) {
        var proto = {};
        proto.get = function () {
            return new Promise(function(resolve, reject) {
                web3.provider.send({call: property.getter}, function(err, result) {
                    if (!err) {
                        resolve(result);
                        return;
                    }
                    reject(err);
                });
            });
        };
        if (property.setter) {
            proto.set = function (val) {
                return flattenPromise([val]).then(function (args) {
                    return new Promise(function (resolve) {
                        web3.provider.send({call: property.setter, args: args}, function (err, result) {
199
                            if (!err) {
Marian Oancea's avatar
Marian Oancea committed
200
                                resolve(result);
Marian Oancea's avatar
Marian Oancea committed
201
                                return;
Marian Oancea's avatar
Marian Oancea committed
202
                            }
203
                            reject(err);
Marian Oancea's avatar
Marian Oancea committed
204 205
                        });
                    });
Marek Kotewicz's avatar
Marek Kotewicz committed
206
                }).catch(function (err) {
Marian Oancea's avatar
Marian Oancea committed
207 208 209
                    console.error(err);
                });
            };
Marek Kotewicz's avatar
Marek Kotewicz committed
210 211 212 213 214
        }
        Object.defineProperty(obj, property.name, proto);
    });
};

Gav Wood's avatar
Gav Wood committed
215 216 217 218 219 220 221 222 223 224
// TODO: import from a dependency, don't duplicate.
var hexToDec = function (hex) {
    return parseInt(hex, 16).toString();
};

var decToHex = function (dec) {
    return parseInt(dec).toString(16);
};


Marek Kotewicz's avatar
Marek Kotewicz committed
225 226 227 228 229
var web3 = {
    _callbacks: {},
    _events: {},
    providers: {},

caktux's avatar
caktux committed
230 231 232 233 234 235 236 237 238 239
    toHex: function(str) {
        var hex = "";
        for(var i = 0; i < str.length; i++) {
            var n = str.charCodeAt(i).toString(16);
            hex += n.length < 2 ? '0' + n : n;
        }

        return hex;
    },

Marek Kotewicz's avatar
Marek Kotewicz committed
240 241 242 243 244 245 246 247 248 249
    toAscii: function(hex) {
        // Find termination
        var str = "";
        var i = 0, l = hex.length;
        if (hex.substring(0, 2) === '0x')
            i = 2;
        for(; i < l; i+=2) {
            var code = hex.charCodeAt(i);
            if(code === 0) {
                break;
Marian Oancea's avatar
Marian Oancea committed
250 251
            }

Marek Kotewicz's avatar
Marek Kotewicz committed
252
            str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
Marian Oancea's avatar
Marian Oancea committed
253
        }
Marian Oancea's avatar
Marian Oancea committed
254

Marek Kotewicz's avatar
Marek Kotewicz committed
255 256 257 258 259 260 261 262 263 264 265
        return str;
    },

    fromAscii: function(str, pad) {
        pad = pad === undefined ? 32 : pad;
        var hex = this.toHex(str);
        while(hex.length < pad*2)
            hex += "00";
        return "0x" + hex;
    },

Gav Wood's avatar
Gav Wood committed
266 267 268 269 270 271 272 273 274
    toDecimal: function (val) {
        return hexToDec(val.substring(2));
    },

    fromDecimal: function (val) {
        return "0x" + decToHex(val);
    },

    toEth: function(str) {
Marek Kotewicz's avatar
Marek Kotewicz committed
275
        var val = typeof str === "string" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str;
Gav Wood's avatar
Gav Wood committed
276 277 278 279 280 281 282 283
        var unit = 0;
        var units = [ 'wei', 'Kwei', 'Mwei', 'Gwei', 'szabo', 'finney', 'ether', 'grand', 'Mether', 'Gether', 'Tether', 'Pether', 'Eether', 'Zether', 'Yether', 'Nether', 'Dether', 'Vether', 'Uether' ];
        while (val > 3000 && unit < units.length - 1)
        {
            val /= 1000;
            unit++;
        }
        var s = val.toString().length < val.toFixed(2).length ? val.toString() : val.toFixed(2);
Marek Kotewicz's avatar
Marek Kotewicz committed
284 285 286
        var replaceFunction = function($0, $1, $2) {
            return $1 + ',' + $2;
        };
Marek Kotewicz's avatar
Marek Kotewicz committed
287

Gav Wood's avatar
Gav Wood committed
288 289
        while (true) {
            var o = s;
Marek Kotewicz's avatar
Marek Kotewicz committed
290 291
            s = s.replace(/(\d)(\d\d\d[\.\,])/, replaceFunction);
            if (o === s)
Gav Wood's avatar
Gav Wood committed
292 293 294 295 296
                break;
        }
        return s + ' ' + units[unit];
    },

Marek Kotewicz's avatar
Marek Kotewicz committed
297 298 299 300
    eth: {
        prototype: Object(), // jshint ignore:line
        watch: function (params) {
            return new Filter(params, ethWatch);
Marian Oancea's avatar
Marian Oancea committed
301
        }
Marek Kotewicz's avatar
Marek Kotewicz committed
302
    },
Marian Oancea's avatar
Marian Oancea committed
303

Marek Kotewicz's avatar
Marek Kotewicz committed
304 305 306
    db: {
        prototype: Object() // jshint ignore:line
    },
Marian Oancea's avatar
Marian Oancea committed
307

Marek Kotewicz's avatar
Marek Kotewicz committed
308 309 310 311
    shh: {
        prototype: Object(), // jshint ignore:line
        watch: function (params) {
            return new Filter(params, shhWatch);
Marian Oancea's avatar
Marian Oancea committed
312
        }
Marek Kotewicz's avatar
Marek Kotewicz committed
313
    },
Marian Oancea's avatar
Marian Oancea committed
314

Marek Kotewicz's avatar
Marek Kotewicz committed
315 316 317
    on: function(event, id, cb) {
        if(web3._events[event] === undefined) {
            web3._events[event] = {};
Marian Oancea's avatar
Marian Oancea committed
318 319
        }

Marek Kotewicz's avatar
Marek Kotewicz committed
320 321 322
        web3._events[event][id] = cb;
        return this;
    },
Marian Oancea's avatar
Marian Oancea committed
323

Marek Kotewicz's avatar
Marek Kotewicz committed
324 325 326
    off: function(event, id) {
        if(web3._events[event] !== undefined) {
            delete web3._events[event][id];
Marian Oancea's avatar
Marian Oancea committed
327 328
        }

Marek Kotewicz's avatar
Marek Kotewicz committed
329 330
        return this;
    },
Marian Oancea's avatar
Marian Oancea committed
331

Marek Kotewicz's avatar
Marek Kotewicz committed
332 333 334
    trigger: function(event, id, data) {
        var callbacks = web3._events[event];
        if (!callbacks || !callbacks[id]) {
Marian Oancea's avatar
Marian Oancea committed
335 336
            return;
        }
Marek Kotewicz's avatar
Marek Kotewicz committed
337 338 339 340 341
        var cb = callbacks[id];
        cb(data);
    }
};

Gav Wood's avatar
Gav Wood committed
342
setupMethods(web3, web3Methods());
343 344
setupMethods(web3.eth, ethMethods());
setupProperties(web3.eth, ethProperties());
Marek Kotewicz's avatar
Marek Kotewicz committed
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
setupMethods(web3.db, dbMethods());
setupMethods(web3.shh, shhMethods());

var ethWatch = {
    changed: 'eth_changed'
};
setupMethods(ethWatch, ethWatchMethods());
var shhWatch = {
    changed: 'shh_changed'
};
setupMethods(shhWatch, shhWatchMethods());

var ProviderManager = function() {
    this.queued = [];
    this.polls = [];
    this.ready = false;
    this.provider = undefined;
    this.id = 1;

    var self = this;
    var poll = function () {
        if (self.provider && self.provider.poll) {
            self.polls.forEach(function (data) {
                data.data._id = self.id;
                self.id++;
                self.provider.poll(data.data, data.id);
            });
Marian Oancea's avatar
Marian Oancea committed
372
        }
Marek Kotewicz's avatar
Marek Kotewicz committed
373
        setTimeout(poll, 12000);
Marian Oancea's avatar
Marian Oancea committed
374
    };
Marek Kotewicz's avatar
Marek Kotewicz committed
375 376
    poll();
};
Marian Oancea's avatar
Marian Oancea committed
377

Marek Kotewicz's avatar
Marek Kotewicz committed
378 379 380 381 382
ProviderManager.prototype.send = function(data, cb) {
    data._id = this.id;
    if (cb) {
        web3._callbacks[data._id] = cb;
    }
Marian Oancea's avatar
Marian Oancea committed
383

Marek Kotewicz's avatar
Marek Kotewicz committed
384 385
    data.args = data.args || [];
    this.id++;
Marian Oancea's avatar
Marian Oancea committed
386

Marek Kotewicz's avatar
Marek Kotewicz committed
387 388 389 390 391 392 393
    if(this.provider !== undefined) {
        this.provider.send(data);
    } else {
        console.warn("provider is not set");
        this.queued.push(data);
    }
};
Marian Oancea's avatar
Marian Oancea committed
394

Marek Kotewicz's avatar
Marek Kotewicz committed
395 396 397 398
ProviderManager.prototype.set = function(provider) {
    if(this.provider !== undefined && this.provider.unload !== undefined) {
        this.provider.unload();
    }
Marian Oancea's avatar
Marian Oancea committed
399

Marek Kotewicz's avatar
Marek Kotewicz committed
400 401 402
    this.provider = provider;
    this.ready = true;
};
Marian Oancea's avatar
Marian Oancea committed
403

Marek Kotewicz's avatar
Marek Kotewicz committed
404 405 406 407 408 409
ProviderManager.prototype.sendQueued = function() {
    for(var i = 0; this.queued.length; i++) {
        // Resend
        this.send(this.queued[i]);
    }
};
Marian Oancea's avatar
Marian Oancea committed
410

Marek Kotewicz's avatar
Marek Kotewicz committed
411 412 413
ProviderManager.prototype.installed = function() {
    return this.provider !== undefined;
};
Marian Oancea's avatar
Marian Oancea committed
414

Marek Kotewicz's avatar
Marek Kotewicz committed
415 416 417 418 419 420 421 422 423 424 425 426
ProviderManager.prototype.startPolling = function (data, pollId) {
    if (!this.provider || !this.provider.poll) {
        return;
    }
    this.polls.push({data: data, id: pollId});
};

ProviderManager.prototype.stopPolling = function (pollId) {
    for (var i = this.polls.length; i--;) {
        var poll = this.polls[i];
        if (poll.id === pollId) {
            this.polls.splice(i, 1);
Marian Oancea's avatar
Marian Oancea committed
427
        }
Marek Kotewicz's avatar
Marek Kotewicz committed
428 429 430 431 432 433 434 435 436 437 438
    }
};

web3.provider = new ProviderManager();

web3.setProvider = function(provider) {
    provider.onmessage = messageHandler;
    web3.provider.set(provider);
    web3.provider.sendQueued();
};

439 440 441 442
web3.haveProvider = function() {
    return !!web3.provider.provider;
};

Marek Kotewicz's avatar
Marek Kotewicz committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
var Filter = function(options, impl) {
    this.impl = impl;
    this.callbacks = [];

    var self = this;
    this.promise = impl.newFilter(options);
    this.promise.then(function (id) {
        self.id = id;
        web3.on(impl.changed, id, self.trigger.bind(self));
        web3.provider.startPolling({call: impl.changed, args: [id]}, id);
    });
};

Filter.prototype.arrived = function(callback) {
    this.changed(callback);
};

Filter.prototype.changed = function(callback) {
    var self = this;
    this.promise.then(function(id) {
        self.callbacks.push(callback);
    });
};

Filter.prototype.trigger = function(messages) {
    for(var i = 0; i < this.callbacks.length; i++) {
        this.callbacks[i].call(this, messages);
    }
};

Filter.prototype.uninstall = function() {
    var self = this;
    this.promise.then(function (id) {
        self.impl.uninstallFilter(id);
        web3.provider.stopPolling(id);
        web3.off(impl.changed, id);
    });
};

Filter.prototype.messages = function() {
    var self = this;
    return this.promise.then(function (id) {
        return self.impl.getMessages(id);
    });
};

Marek Kotewicz's avatar
Marek Kotewicz committed
489 490 491 492
Filter.prototype.logs = function () {
    return this.messages();
};

Marek Kotewicz's avatar
Marek Kotewicz committed
493 494 495 496 497
function messageHandler(data) {
    if(data._event !== undefined) {
        web3.trigger(data._event, data._id, data.data);
        return;
    }
Marian Oancea's avatar
Marian Oancea committed
498

Marek Kotewicz's avatar
Marek Kotewicz committed
499 500 501 502 503
    if(data._id) {
        var cb = web3._callbacks[data._id];
        if (cb) {
            cb.call(this, data.error, data.data);
            delete web3._callbacks[data._id];
Marian Oancea's avatar
Marian Oancea committed
504 505
        }
    }
Marek Kotewicz's avatar
Marek Kotewicz committed
506
}
Marian Oancea's avatar
Marian Oancea committed
507

Jeffrey Wilcke's avatar
Jeffrey Wilcke committed
508 509
if (typeof(module) !== "undefined")
    module.exports = web3;