Compare commits

..

34 Commits

Author SHA1 Message Date
Richard Moore
bb6bc4cac3 Check for partially-working normalize support. 2018-11-27 15:59:14 -05:00
Richard Moore
ef8b9c36ef Support for platforms where UTF-8 is only half broken. 2018-11-27 15:56:50 -05:00
Richard Moore
e6c943d01f Updated dist files. 2018-11-21 16:24:40 -05:00
Richard Moore
31d3ee899f Throw exception instead of returning null for getDefaultProvider (#351). 2018-11-21 16:23:44 -05:00
Richard Moore
98143a845b Updated dist files. 2018-11-20 15:45:47 -05:00
Richard Moore
bffc557be1 Added default provider support for Ethereum classic (#351). 2018-11-20 15:41:12 -05:00
Richard Moore
09208fa8fe Updated dist files. 2018-11-13 07:50:04 -05:00
Richard Moore
048c571d3d Fixed 0 confirmation waiting (#346). 2018-11-13 07:48:37 -05:00
Richard Moore
24757f1064 Updated dist files. 2018-11-12 17:27:47 -05:00
Richard Moore
88f2f51266 Fix spacing in checkArgument errors (#318). 2018-11-12 17:22:18 -05:00
Richard Moore
93152ef863 Do not replay block events when the provider event block is reset (#343). 2018-11-12 17:17:43 -05:00
Richard Moore
09b698b0a9 Updated dist files. 2018-11-09 14:42:29 -05:00
Richard Moore
478aaf9619 Force unorm shim when String.prototype.normalize is broken (#338). 2018-11-09 14:36:21 -05:00
Richard Moore
fad902b438 Better error message when normalize is missing. 2018-11-09 14:34:14 -05:00
Richard Moore
7bfaf292db Added shims for React-Native support. 2018-11-08 18:25:16 -05:00
Richard Moore
be0488a1a0 Updated dist files. 2018-11-08 16:03:33 -05:00
Richard Moore
28a52cd485 Fix for when blockTag is specified as a null equivalent value in contract overrides (#329). 2018-11-08 16:01:48 -05:00
Richard Moore
3a19f43844 Added "debug" event for providers; do not depend on the format as it may change, but this should help debugging in most cases (#320). 2018-11-08 15:59:30 -05:00
Richard Moore
4852e837d2 Fix for Kovan filters without an address (#339). 2018-11-08 15:56:35 -05:00
Richard Moore
fa68385cfe Updated dist files. 2018-10-14 19:05:38 -04:00
Richard Moore
d54609a458 Fixed lingering polling timer when no events left to process in a provider. 2018-10-14 19:01:53 -04:00
Richard Moore
f682861e0b Fixed utils.poll from mutating passed variables. 2018-10-14 19:01:09 -04:00
Richard Moore
023a20ff47 Fixed and refactored populating transaction values for signers (#306). 2018-10-14 19:00:15 -04:00
Richard Moore
e39cd84923 Fixed test cases for phantomjs (must use ES3 syntax). 2018-10-13 17:27:19 -04:00
Richard Moore
5020897f10 Updated dist files. 2018-10-13 17:19:07 -04:00
Richard Moore
6ac2d923b7 Fixed filtering with null non-indexed parameters (#305). 2018-10-13 17:17:02 -04:00
Richard Moore
6996dd86f4 Updated dist files. 2018-10-11 16:50:52 -04:00
Richard Moore
493273d698 Added optional blockTag to call; note that this may not behave as expected on all nodes (#226). 2018-10-11 16:03:18 -04:00
Richard Moore
84344ac4c2 Check all transaction parameters are valid; protect against typos (#299). 2018-10-11 15:16:31 -04:00
Richard Moore
9b118af304 Updated dist files. 2018-10-07 01:13:10 -04:00
Richard Moore
e39e2ed718 Added address to HDNode (#196). 2018-10-07 01:09:56 -04:00
Richard Moore
71f781d542 Added French and Spanish to test-hdnode. 2018-10-07 01:05:49 -04:00
Richard Moore
b2db10e216 Mark progressCallback as optional (#293). 2018-10-07 00:42:52 -04:00
Richard Moore
3736a15714 Updated dist files. 2018-10-04 20:25:07 -04:00
72 changed files with 1952 additions and 730 deletions

2
_version.d.ts vendored
View File

@@ -1 +1 @@
export declare const version = "4.0.2";
export declare const version = "4.0.13";

View File

@@ -1,3 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = "4.0.2";
exports.version = "4.0.13";

6
contract.d.ts vendored
View File

@@ -21,6 +21,12 @@ export interface Event extends Log {
getTransaction: () => Promise<TransactionResponse>;
getTransactionReceipt: () => Promise<TransactionReceipt>;
}
export interface ContractReceipt extends TransactionReceipt {
events?: Array<Event>;
}
export interface ContractTransaction extends TransactionResponse {
wait(confirmations?: number): Promise<ContractReceipt>;
}
export declare class VoidSigner extends Signer {
readonly address: string;
constructor(address: string, provider: Provider);

View File

@@ -106,14 +106,20 @@ function resolveAddresses(provider, value, paramType) {
function runMethod(contract, functionName, estimateOnly) {
var method = contract.interface.functions[functionName];
return function () {
var _this = this;
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
var tx = {};
var blockTag = null;
// If 1 extra parameter was passed in, it contains overrides
if (params.length === method.inputs.length + 1 && typeof (params[params.length - 1]) === 'object') {
tx = properties_1.shallowCopy(params.pop());
if (tx.blockTag != null) {
blockTag = tx.blockTag;
}
delete tx.blockTag;
// Check for unexpected keys (e.g. using "gas" instead of "gasLimit")
for (var key in tx) {
if (!allowedTransactionKeys[key]) {
@@ -153,7 +159,7 @@ function runMethod(contract, functionName, estimateOnly) {
if (tx.from == null && contract.signer) {
tx.from = contract.signer.getAddress();
}
return contract.provider.call(tx).then(function (value) {
return contract.provider.call(tx, blockTag).then(function (value) {
if ((bytes_1.hexDataLength(value) % 32) === 4 && bytes_1.hexDataSlice(value, 0, 4) === '0x08c379a0') {
var reason = abi_coder_1.defaultAbiCoder.decode(['string'], bytes_1.hexDataSlice(value, 4));
errors.throwError('call revert exception', errors.CALL_EXCEPTION, {
@@ -206,7 +212,36 @@ function runMethod(contract, functionName, estimateOnly) {
if (tx.from != null) {
errors.throwError('cannot override from in a transaction', errors.UNSUPPORTED_OPERATION, { operation: 'sendTransaction' });
}
return contract.signer.sendTransaction(tx);
return contract.signer.sendTransaction(tx).then(function (tx) {
var wait = tx.wait.bind(tx);
tx.wait = function (confirmations) {
return wait(confirmations).then(function (receipt) {
receipt.events = receipt.logs.map(function (log) {
var event = properties_1.deepCopy(log);
var parsed = _this.interface.parseLog(log);
if (parsed) {
event.args = parsed.values;
event.decode = parsed.decode;
event.event = parsed.name;
event.eventSignature = parsed.signature;
}
event.removeListener = function () { return _this.provider; };
event.getBlock = function () {
return _this.provider.getBlock(receipt.blockHash);
};
event.getTransaction = function () {
return _this.provider.getTransaction(receipt.transactionHash);
};
event.getTransactionReceipt = function () {
return Promise.resolve(receipt);
};
return event;
});
return receipt;
});
};
return tx;
});
}
throw new Error('invalid type - ' + method.type);
return null;
@@ -620,7 +655,7 @@ var ContractFactory = /** @class */ (function () {
errors.throwError('cannot override ' + key, errors.UNSUPPORTED_OPERATION, { operation: key });
});
// Make sure the call matches the constructor signature
errors.checkArgumentCount(args.length, this.interface.deployFunction.inputs.length, 'in Contract constructor');
errors.checkArgumentCount(args.length, this.interface.deployFunction.inputs.length, ' in Contract constructor');
// Set the data to the bytecode + the encoded constructor arguments
tx.data = this.interface.deployFunction.encode(this.bytecode, args);
return tx;

728
dist/ethers.js vendored

File diff suppressed because it is too large Load Diff

2
dist/ethers.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

49
dist/ethers.types.txt vendored
View File

@@ -17,9 +17,9 @@ declare module 'ethers/ethers' {
import * as wordlists from 'ethers/wordlists';
import { platform } from 'ethers/utils/shims';
import { version } from 'ethers/_version';
import { ContractFunction, Event, EventFilter } from 'ethers/contract';
import { ContractFunction, ContractTransaction, Event, EventFilter } from 'ethers/contract';
function getDefaultProvider(network?: utils.Network | string): providers.BaseProvider;
export { Signer, Wallet, VoidSigner, getDefaultProvider, providers, Contract, ContractFactory, constants, errors, utils, wordlists, platform, version, ContractFunction, Event, EventFilter };
export { Signer, Wallet, VoidSigner, getDefaultProvider, providers, Contract, ContractFactory, constants, errors, utils, wordlists, platform, version, ContractFunction, ContractTransaction, Event, EventFilter };
}
declare module 'ethers/contract' {
@@ -46,6 +46,12 @@ declare module 'ethers/contract' {
getTransaction: () => Promise<TransactionResponse>;
getTransactionReceipt: () => Promise<TransactionReceipt>;
}
export interface ContractReceipt extends TransactionReceipt {
events?: Array<Event>;
}
export interface ContractTransaction extends TransactionResponse {
wait(confirmations?: number): Promise<ContractReceipt>;
}
export class VoidSigner extends Signer {
readonly address: string;
constructor(address: string, provider: Provider);
@@ -146,7 +152,7 @@ declare module 'ethers/wallet' {
* Static methods to create Wallet instances.
*/
static createRandom(options?: any): Wallet;
static fromEncryptedJson(json: string, password: Arrayish, progressCallback: ProgressCallback): Promise<Wallet>;
static fromEncryptedJson(json: string, password: Arrayish, progressCallback?: ProgressCallback): Promise<Wallet>;
static fromMnemonic(mnemonic: string, path?: string, wordlist?: Wordlist): Wallet;
}
}
@@ -182,6 +188,7 @@ declare module 'ethers/errors' {
export function checkNew(self: any, kind: any): void;
export function checkArgumentCount(count: number, expectedCount: number, suffix?: string): void;
export function setCensorship(censorship: boolean, permanent?: boolean): void;
export function checkNormalize(): void;
}
declare module 'ethers/providers' {
@@ -213,10 +220,11 @@ declare module 'ethers/utils' {
import { keccak256 as solidityKeccak256, pack as solidityPack, sha256 as soliditySha256 } from 'ethers/utils/solidity';
import { randomBytes } from 'ethers/utils/random-bytes';
import { getNetwork } from 'ethers/utils/networks';
import { deepCopy, defineReadOnly, resolveProperties, shallowCopy } from 'ethers/utils/properties';
import { checkProperties, deepCopy, defineReadOnly, resolveProperties, shallowCopy } from 'ethers/utils/properties';
import * as RLP from 'ethers/utils/rlp';
import { computeAddress, computePublicKey, recoverAddress, recoverPublicKey, verifyMessage } from 'ethers/utils/secp256k1';
import { SigningKey } from 'ethers/utils/signing-key';
import { populateTransaction } from 'ethers/utils/transaction';
import { parse as parseTransaction, serialize as serializeTransaction } from 'ethers/utils/transaction';
import { formatBytes32String, parseBytes32String, toUtf8Bytes, toUtf8String } from 'ethers/utils/utf8';
import { commify, formatEther, parseEther, formatUnits, parseUnits } from 'ethers/utils/units';
@@ -232,7 +240,7 @@ declare module 'ethers/utils' {
import { ConnectionInfo, OnceBlockable, PollOptions } from 'ethers/utils/web';
import { EncryptOptions, ProgressCallback } from 'ethers/utils/secret-storage';
import { Wordlist } from 'ethers/utils/wordlist';
export { AbiCoder, defaultAbiCoder, formatSignature, formatParamType, parseSignature, parseParamType, RLP, fetchJson, getNetwork, deepCopy, defineReadOnly, resolveProperties, shallowCopy, arrayify, concat, padZeros, stripZeros, HDNode, SigningKey, Interface, base64, BigNumber, bigNumberify, hexlify, hexStripZeros, hexZeroPad, hexDataLength, hexDataSlice, toUtf8Bytes, toUtf8String, formatBytes32String, parseBytes32String, hashMessage, namehash, id, getAddress, getIcapAddress, getContractAddress, formatEther, parseEther, formatUnits, parseUnits, commify, keccak256, sha256, randomBytes, solidityPack, solidityKeccak256, soliditySha256, splitSignature, joinSignature, parseTransaction, serializeTransaction, getJsonWalletAddress, computeAddress, computePublicKey, recoverAddress, recoverPublicKey, verifyMessage, SupportedAlgorithms, UnicodeNormalizationForm, CoerceFunc, EventFragment, FunctionFragment, ParamType, BigNumberish, Arrayish, Hexable, Signature, Indexed, DeployDescription, EventDescription, FunctionDescription, LogDescription, TransactionDescription, Network, Networkish, Transaction, UnsignedTransaction, ConnectionInfo, OnceBlockable, PollOptions, EncryptOptions, ProgressCallback, Wordlist, };
export { AbiCoder, defaultAbiCoder, formatSignature, formatParamType, parseSignature, parseParamType, RLP, fetchJson, getNetwork, checkProperties, deepCopy, defineReadOnly, resolveProperties, shallowCopy, arrayify, concat, padZeros, stripZeros, HDNode, SigningKey, Interface, base64, BigNumber, bigNumberify, hexlify, hexStripZeros, hexZeroPad, hexDataLength, hexDataSlice, toUtf8Bytes, toUtf8String, formatBytes32String, parseBytes32String, hashMessage, namehash, id, getAddress, getIcapAddress, getContractAddress, formatEther, parseEther, formatUnits, parseUnits, commify, keccak256, sha256, randomBytes, solidityPack, solidityKeccak256, soliditySha256, splitSignature, joinSignature, parseTransaction, populateTransaction, serializeTransaction, getJsonWalletAddress, computeAddress, computePublicKey, recoverAddress, recoverPublicKey, verifyMessage, SupportedAlgorithms, UnicodeNormalizationForm, CoerceFunc, EventFragment, FunctionFragment, ParamType, BigNumberish, Arrayish, Hexable, Signature, Indexed, DeployDescription, EventDescription, FunctionDescription, LogDescription, TransactionDescription, Network, Networkish, Transaction, UnsignedTransaction, ConnectionInfo, OnceBlockable, PollOptions, EncryptOptions, ProgressCallback, Wordlist, };
}
declare module 'ethers/wordlists' {
@@ -254,7 +262,7 @@ declare module 'ethers/utils/shims' {
}
declare module 'ethers/_version' {
export const version = "4.0.2";
export const version = "4.0.13";
}
declare module 'ethers/utils/bignumber' {
@@ -406,6 +414,7 @@ declare module 'ethers/utils/transaction' {
import { BigNumber } from 'ethers/utils/bignumber';
import { Arrayish, Signature } from 'ethers/utils/bytes';
import { BigNumberish } from 'ethers/utils/bignumber';
import { Provider } from 'ethers/providers/abstract-provider';
export type UnsignedTransaction = {
to?: string;
nonce?: number;
@@ -431,6 +440,7 @@ declare module 'ethers/utils/transaction' {
}
export function serialize(transaction: UnsignedTransaction, signature?: Arrayish | Signature): string;
export function parse(rawTransaction: Arrayish): Transaction;
export function populateTransaction(transaction: any, provider: Provider, from: string | Promise<string>): Promise<Transaction>;
}
declare module 'ethers/providers/abstract-provider' {
@@ -482,6 +492,7 @@ declare module 'ethers/providers/abstract-provider' {
transactionHash?: string;
logs?: Array<Log>;
blockNumber?: number;
confirmations?: number;
cumulativeGasUsed?: BigNumber;
byzantium: boolean;
status?: number;
@@ -500,9 +511,10 @@ declare module 'ethers/providers/abstract-provider' {
blockNumber?: number;
blockHash?: string;
timestamp?: number;
confirmations: number;
from: string;
raw?: string;
wait: (timeout?: number) => Promise<TransactionReceipt>;
wait: (confirmations?: number) => Promise<TransactionReceipt>;
}
export type EventType = string | Array<string> | Filter;
export type Listener = (...args: Array<any>) => void;
@@ -515,7 +527,7 @@ declare module 'ethers/providers/abstract-provider' {
abstract getCode(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
abstract call(transaction: TransactionRequest): Promise<string>;
abstract call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract estimateGas(transaction: TransactionRequest): Promise<BigNumber>;
abstract getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>, includeTransactions?: boolean): Promise<Block>;
abstract getTransaction(transactionHash: string): Promise<TransactionResponse>;
@@ -614,6 +626,7 @@ declare module 'ethers/utils/hdnode' {
export class HDNode {
readonly privateKey: string;
readonly publicKey: string;
readonly address: string;
readonly mnemonic: string;
readonly path: string;
readonly chainCode: string;
@@ -703,7 +716,9 @@ declare module 'ethers/providers/base-provider' {
import { Transaction } from 'ethers/utils/transaction';
import { Network, Networkish } from 'ethers/utils/networks';
export class BaseProvider extends Provider {
protected _emitted: any;
protected _emitted: {
[eventName: string]: number | 'pending';
};
/**
* ready
*
@@ -721,7 +736,9 @@ declare module 'ethers/providers/base-provider' {
readonly blockNumber: number;
polling: boolean;
pollingInterval: number;
waitForTransaction(transactionHash: string, timeout?: number): Promise<TransactionReceipt>;
_getFastBlockNumber(): Promise<number>;
_setFastBlockNumber(blockNumber: number): void;
waitForTransaction(transactionHash: string, confirmations?: number): Promise<TransactionReceipt>;
getBlockNumber(): Promise<number>;
getGasPrice(): Promise<BigNumber>;
getBalance(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<BigNumber>;
@@ -730,7 +747,7 @@ declare module 'ethers/providers/base-provider' {
getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
_wrapTransaction(tx: Transaction, hash?: string): TransactionResponse;
call(transaction: TransactionRequest): Promise<string>;
call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
estimateGas(transaction: TransactionRequest): Promise<BigNumber>;
getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>, includeTransactions?: boolean): Promise<Block>;
getTransaction(transactionHash: string): Promise<TransactionResponse>;
@@ -828,7 +845,11 @@ declare module 'ethers/providers/json-rpc-provider' {
perform(method: string, params: any): Promise<any>;
protected _startPending(): void;
protected _stopPending(): void;
static hexlifyTransaction(transaction: TransactionRequest): any;
static hexlifyTransaction(transaction: TransactionRequest, allowExtra?: {
[key: string]: boolean;
}): {
[key: string]: string;
};
}
}
@@ -905,6 +926,7 @@ declare module 'ethers/utils/networks' {
name: string;
chainId: number;
ensAddress?: string;
_defaultProvider?: (providers: any) => any;
};
export type Networkish = Network | string | number;
/**
@@ -921,6 +943,9 @@ declare module 'ethers/utils/properties' {
export function setType(object: any, type: string): void;
export function isType(object: any, type: string): boolean;
export function resolveProperties(object: any): Promise<any>;
export function checkProperties(object: any, properties: {
[name: string]: boolean;
}): void;
export function shallowCopy(object: any): any;
export function deepCopy(object: any, frozen?: boolean): any;
export function inheritable(parent: any): (child: any) => void;

1
dist/shims.js vendored Normal file

File diff suppressed because one or more lines are too long

1
errors.d.ts vendored
View File

@@ -14,3 +14,4 @@ export declare function throwError(message: string, code: string, params: any):
export declare function checkNew(self: any, kind: any): void;
export declare function checkArgumentCount(count: number, expectedCount: number, suffix?: string): void;
export declare function setCensorship(censorship: boolean, permanent?: boolean): void;
export declare function checkNormalize(): void;

View File

@@ -108,3 +108,14 @@ function setCensorship(censorship, permanent) {
_permanentCensorErrors = !!permanent;
}
exports.setCensorship = setCensorship;
function checkNormalize() {
try {
if (String.fromCharCode(0xe9).normalize('NFD') !== String.fromCharCode(0x65, 0x0301)) {
throw new Error('broken');
}
}
catch (error) {
throwError('platform missing String.prototype.normalize', exports.UNSUPPORTED_OPERATION, { operation: 'String.prototype.normalize' });
}
}
exports.checkNormalize = checkNormalize;

4
ethers.d.ts vendored
View File

@@ -8,6 +8,6 @@ import * as utils from './utils';
import * as wordlists from './wordlists';
import { platform } from './utils/shims';
import { version } from './_version';
import { ContractFunction, Event, EventFilter } from './contract';
import { ContractFunction, ContractTransaction, Event, EventFilter } from './contract';
declare function getDefaultProvider(network?: utils.Network | string): providers.BaseProvider;
export { Signer, Wallet, VoidSigner, getDefaultProvider, providers, Contract, ContractFactory, constants, errors, utils, wordlists, platform, version, ContractFunction, Event, EventFilter };
export { Signer, Wallet, VoidSigner, getDefaultProvider, providers, Contract, ContractFactory, constants, errors, utils, wordlists, platform, version, ContractFunction, ContractTransaction, Event, EventFilter };

View File

@@ -36,9 +36,16 @@ exports.version = _version_1.version;
////////////////////////
// Helper Functions
function getDefaultProvider(network) {
return new providers.FallbackProvider([
new providers.InfuraProvider(network),
new providers.EtherscanProvider(network),
]);
if (network == null) {
network = 'homestead';
}
var n = utils.getNetwork(network);
if (!n || !n._defaultProvider) {
errors.throwError('unsupported getDefaultProvider network', errors.UNSUPPORTED_OPERATION, {
operation: 'getDefaultProvider',
network: network
});
}
return n._defaultProvider(providers);
}
exports.getDefaultProvider = getDefaultProvider;

View File

@@ -152,7 +152,9 @@ function taskBundle(name, options) {
if (options.minify) {
result = result.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify())
.pipe(uglify({
output: { ascii_only: true }
}))
.pipe(sourcemaps.write('./'))
}
@@ -174,6 +176,30 @@ taskBundle("minified", { filename: "ethers.min.js", dest: 'dist', minify: true }
// Creates dist/ethers.min.js
taskBundle("minified-test", { filename: "ethers.min.js", dest: 'tests/dist', minify: true });
gulp.task('shims', function () {
var result = browserify({
basedir: '.',
debug: false,
entries: [ './tests/shims/index.js' ],
cache: { },
packageCache: {},
standalone: "_shims",
insertGlobalVars: {
process: function() { return; },
}
})
.bundle()
.pipe(source('shims.js'))
.pipe(buffer())
.pipe(uglify({
output: { ascii_only: true }
}))
.pipe(gulp.dest('dist'));
return result;
});
/*
// Dump the TypeScript definitions to dist/types/
gulp.task("types", function() {
@@ -265,7 +291,9 @@ function taskLang(locale) {
.bundle()
.pipe(source("wordlist-" + locale + ".js"))
.pipe(buffer())
.pipe(uglify())
.pipe(uglify({
output: { ascii_only: true }
}))
.pipe(gulp.dest("dist"));
});
}

77
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ethers",
"version": "4.0.1",
"version": "4.0.13",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -69,9 +69,9 @@
"integrity": "sha512-8TqvB0ReZWwtcd3LXq3YSrBoLyXFgBX/sBZfGye9+YS8zH7/g+i6QRIuiDmwBoTzcQ/pk89nZYTYU4c5akKkzw=="
},
"JSONStream": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz",
"integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
"integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
"dev": true,
"requires": {
"jsonparse": "^1.2.0",
@@ -95,13 +95,10 @@
"dev": true
},
"acorn-dynamic-import": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz",
"integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==",
"dev": true,
"requires": {
"acorn": "^5.0.0"
}
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz",
"integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==",
"dev": true
},
"acorn-jsx": {
"version": "4.1.1",
@@ -113,16 +110,31 @@
}
},
"acorn-node": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.5.2.tgz",
"integrity": "sha512-krFKvw/d1F17AN3XZbybIUzEY4YEPNiGo05AfP3dBlfVKrMHETKpgjpuZkSF8qDNt9UkQcqj7am8yJLseklCMg==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.6.2.tgz",
"integrity": "sha512-rIhNEZuNI8ibQcL7ANm/mGyPukIaZsRNX9psFNQURyJW0nu6k8wjSDld20z6v2mDBWqX13pIEnk9gGZJHIlEXg==",
"dev": true,
"requires": {
"acorn": "^5.7.1",
"acorn-dynamic-import": "^3.0.0",
"acorn": "^6.0.2",
"acorn-dynamic-import": "^4.0.0",
"acorn-walk": "^6.1.0",
"xtend": "^4.0.1"
},
"dependencies": {
"acorn": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz",
"integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==",
"dev": true
}
}
},
"acorn-walk": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz",
"integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==",
"dev": true
},
"aes-js": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
@@ -783,9 +795,9 @@
"dev": true
},
"browserify": {
"version": "16.2.2",
"resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.2.tgz",
"integrity": "sha512-fMES05wq1Oukts6ksGUU2TMVHHp06LyQt0SIwbXIHm7waSrQmNBZePsU0iM/4f94zbvb/wHma+D1YrdzWYnF/A==",
"version": "16.2.3",
"resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.3.tgz",
"integrity": "sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ==",
"dev": true,
"requires": {
"JSONStream": "^1.0.3",
@@ -927,9 +939,9 @@
}
},
"buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.0.tgz",
"integrity": "sha512-nUJyfChH7PMJy75eRDCCKtszSEFokUNXC1hNVSe+o+VdcgvDPLs20k3v8UXI8ruRYAJiYtyRea8mYyqPxoHWDw==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz",
"integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==",
"dev": true,
"requires": {
"base64-js": "^1.0.2",
@@ -996,9 +1008,9 @@
}
},
"cached-path-relative": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz",
"integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz",
"integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==",
"dev": true
},
"caller-path": {
@@ -5288,9 +5300,9 @@
}
},
"pbkdf2": {
"version": "3.0.16",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz",
"integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==",
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
"integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
"dev": true,
"requires": {
"create-hash": "^1.1.2",
@@ -5392,16 +5404,17 @@
}
},
"public-encrypt": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz",
"integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
"integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
"dev": true,
"requires": {
"bn.js": "^4.1.0",
"browserify-rsa": "^4.0.0",
"create-hash": "^1.1.0",
"parse-asn1": "^5.0.0",
"randombytes": "^2.0.1"
"randombytes": "^2.0.1",
"safe-buffer": "^5.1.2"
}
},
"pump": {

View File

@@ -1,14 +1,14 @@
{
"name": "ethers",
"version": "4.0.2",
"version": "4.0.13",
"description": "Ethereum wallet library.",
"main": "./index.js",
"types": "./index.d.ts",
"scripts": {
"build": "npm run dist-version && tsc -p ./tsconfig.json",
"auto-build": "npm run build -- -w",
"dist": "npm run dist-version && npm run build && gulp default minified && npm run dist-types",
"dist-test": "gulp default-test minified-test",
"dist": "npm run dist-version && npm run build && gulp default minified shims && npm run dist-types",
"dist-test": "gulp default-test minified-test shims",
"dist-bip39": "gulp bip39-es bip39-fr bip39-it bip39-ja bip39-ko bip39-zh",
"dist-types": "dts-bundle --name ethers --main ./index.d.ts --out ./dist/ethers.types.txt",
"dist-version": "node -e \"let v = require('./package.json').version; require('fs').writeFileSync('./src.ts/_version.ts', 'export const version = \\\"' + v +'\\\";\\n')\"",
@@ -31,7 +31,7 @@
"xmlhttprequest": "1.8.0"
},
"devDependencies": {
"browserify": "^16.2.2",
"browserify": "^16.2.3",
"browserify-zlib": "^0.2.0",
"dts-bundle": "^0.7.3",
"eslint": "^5.0.1",
@@ -53,7 +53,7 @@
"vinyl-source-stream": "^2.0.0",
"web3-providers-http": "1.0.0-beta.35"
},
"browser": "./dist/ethers.js",
"browser": "./dist/ethers.min.js",
"keywords": [
"ethereum",
"library",

View File

@@ -46,6 +46,7 @@ export interface TransactionReceipt {
transactionHash?: string;
logs?: Array<Log>;
blockNumber?: number;
confirmations?: number;
cumulativeGasUsed?: BigNumber;
byzantium: boolean;
status?: number;
@@ -64,9 +65,10 @@ export interface TransactionResponse extends Transaction {
blockNumber?: number;
blockHash?: string;
timestamp?: number;
confirmations: number;
from: string;
raw?: string;
wait: (timeout?: number) => Promise<TransactionReceipt>;
wait: (confirmations?: number) => Promise<TransactionReceipt>;
}
export declare type EventType = string | Array<string> | Filter;
export declare type Listener = (...args: Array<any>) => void;
@@ -79,7 +81,7 @@ export declare abstract class Provider implements OnceBlockable {
abstract getCode(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
abstract call(transaction: TransactionRequest): Promise<string>;
abstract call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract estimateGas(transaction: TransactionRequest): Promise<BigNumber>;
abstract getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>, includeTransactions?: boolean): Promise<Block>;
abstract getTransaction(transactionHash: string): Promise<TransactionResponse>;

View File

@@ -7,11 +7,16 @@ import { Network, Networkish } from '../utils/networks';
export declare class BaseProvider extends Provider {
private _network;
private _events;
protected _emitted: any;
protected _emitted: {
[eventName: string]: number | 'pending';
};
private _pollingInterval;
private _poller;
private _lastBlockNumber;
private _balances;
private _fastBlockNumber;
private _fastBlockNumberPromise;
private _fastQueryDate;
/**
* ready
*
@@ -30,7 +35,9 @@ export declare class BaseProvider extends Provider {
readonly blockNumber: number;
polling: boolean;
pollingInterval: number;
waitForTransaction(transactionHash: string, timeout?: number): Promise<TransactionReceipt>;
_getFastBlockNumber(): Promise<number>;
_setFastBlockNumber(blockNumber: number): void;
waitForTransaction(transactionHash: string, confirmations?: number): Promise<TransactionReceipt>;
getBlockNumber(): Promise<number>;
getGasPrice(): Promise<BigNumber>;
getBalance(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<BigNumber>;
@@ -39,7 +46,7 @@ export declare class BaseProvider extends Provider {
getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
_wrapTransaction(tx: Transaction, hash?: string): TransactionResponse;
call(transaction: TransactionRequest): Promise<string>;
call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
estimateGas(transaction: TransactionRequest): Promise<BigNumber>;
getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>, includeTransactions?: boolean): Promise<Block>;
getTransaction(transactionHash: string): Promise<TransactionResponse>;

View File

@@ -79,9 +79,15 @@ function arrayOf(check) {
return result;
});
}
function checkHash(hash) {
if (typeof (hash) === 'string' && bytes_1.hexDataLength(hash) === 32) {
return hash.toLowerCase();
function checkHash(hash, requirePrefix) {
if (typeof (hash) === 'string') {
// geth-etc does add a "0x" prefix on receipt.root
if (!requirePrefix && hash.substring(0, 2) !== '0x') {
hash = '0x' + hash;
}
if (bytes_1.hexDataLength(hash) === 32) {
return hash.toLowerCase();
}
}
errors.throwError('invalid hash', errors.INVALID_ARGUMENT, { arg: 'hash', value: hash });
return null;
@@ -150,6 +156,7 @@ var formatTransaction = {
blockHash: allowNull(checkHash, null),
blockNumber: allowNull(checkNumber, null),
transactionIndex: allowNull(checkNumber, null),
confirmations: allowNull(checkNumber, null),
from: address_1.getAddress,
gasPrice: bignumber_1.bigNumberify,
gasLimit: bignumber_1.bigNumberify,
@@ -201,6 +208,10 @@ function checkTransactionResponse(transaction) {
}
var result = check(formatTransaction, transaction);
var networkId = transaction.networkId;
// geth-etc returns chainId
if (transaction.chainId != null && networkId == null && result.v == null) {
networkId = transaction.chainId;
}
if (bytes_1.isHexString(networkId)) {
networkId = bignumber_1.bigNumberify(networkId).toNumber();
}
@@ -278,6 +289,7 @@ var formatTransactionReceipt = {
transactionHash: checkHash,
logs: arrayOf(checkTransactionReceiptLog),
blockNumber: checkNumber,
confirmations: allowNull(checkNumber, null),
cumulativeGasUsed: bignumber_1.bigNumberify,
status: allowNull(checkNumber)
};
@@ -373,12 +385,12 @@ function getEventTag(eventName) {
return 'address:' + address_1.getAddress(eventName);
}
eventName = eventName.toLowerCase();
if (eventName === 'block' || eventName === 'pending' || eventName === 'error') {
return eventName;
}
else if (bytes_1.hexDataLength(eventName) === 32) {
if (bytes_1.hexDataLength(eventName) === 32) {
return 'tx:' + eventName;
}
if (eventName.indexOf(':') === -1) {
return eventName;
}
}
else if (Array.isArray(eventName)) {
return 'filter::' + serializeTopics(eventName);
@@ -388,6 +400,11 @@ function getEventTag(eventName) {
}
throw new Error('invalid event - ' + eventName);
}
//////////////////////////////
// Helper Object
function getTime() {
return (new Date()).getTime();
}
var BaseProvider = /** @class */ (function (_super) {
__extends(BaseProvider, _super);
function BaseProvider(network) {
@@ -415,39 +432,52 @@ var BaseProvider = /** @class */ (function (_super) {
// Events being listened to
_this._events = [];
_this._pollingInterval = 4000;
// We use this to track recent emitted events; for example, if we emit a "block" of 100
// and we get a `getBlock(100)` request which would result in null, we should retry
// until we get a response. This provides devs with a consistent view. Similarly for
// transaction hashes.
_this._emitted = { block: _this._lastBlockNumber };
_this._emitted = { block: -2 };
_this._fastQueryDate = 0;
return _this;
}
BaseProvider.prototype._doPoll = function () {
var _this = this;
this.getBlockNumber().then(function (blockNumber) {
_this._setFastBlockNumber(blockNumber);
// If the block hasn't changed, meh.
if (blockNumber === _this._lastBlockNumber) {
return;
}
if (_this._lastBlockNumber === -2) {
_this._lastBlockNumber = blockNumber - 1;
// First polling cycle, trigger a "block" events
if (_this._emitted.block === -2) {
_this._emitted.block = blockNumber - 1;
}
// Notify all listener for each block that has passed
for (var i = _this._lastBlockNumber + 1; i <= blockNumber; i++) {
if (_this._emitted.block < i) {
_this._emitted.block = i;
for (var i = _this._emitted.block + 1; i <= blockNumber; i++) {
_this.emit('block', i);
}
// The emitted block was updated, check for obsolete events
if (_this._emitted.block !== blockNumber) {
_this._emitted.block = blockNumber;
Object.keys(_this._emitted).forEach(function (key) {
// The block event does not expire
if (key === 'block') {
return;
}
// The block we were at when we emitted this event
var eventBlockNumber = _this._emitted[key];
// We cannot garbage collect pending transactions or blocks here
// They should be garbage collected by the Provider when setting
// "pending" events
if (eventBlockNumber === 'pending') {
return;
}
// Evict any transaction hashes or block hashes over 12 blocks
// old, since they should not return null anyways
Object.keys(_this._emitted).forEach(function (key) {
if (key === 'block') {
return;
}
if (_this._emitted[key] > i + 12) {
delete _this._emitted[key];
}
});
}
_this.emit('block', i);
if (blockNumber - eventBlockNumber > 12) {
delete _this._emitted[key];
}
});
}
// First polling cycle
if (_this._lastBlockNumber === -2) {
_this._lastBlockNumber = blockNumber - 1;
}
// Sweep balances and remove addresses we no longer have events for
var newBalances = {};
@@ -473,25 +503,27 @@ var BaseProvider = /** @class */ (function (_super) {
newBalances[address_2] = _this._balances[address_2];
}
_this.getBalance(address_2, 'latest').then(function (balance) {
var lastBalance = this._balances[address_2];
var lastBalance = _this._balances[address_2];
if (lastBalance && balance.eq(lastBalance)) {
return;
}
this._balances[address_2] = balance;
this.emit(address_2, balance);
_this._balances[address_2] = balance;
_this.emit(address_2, balance);
return null;
}).catch(function (error) { _this.emit('error', error); });
break;
}
case 'filter': {
var address = comps[1];
var topics = deserializeTopics(comps[2]);
var filter_1 = {
address: address,
address: comps[1],
fromBlock: _this._lastBlockNumber + 1,
toBlock: blockNumber,
topics: topics
};
if (!filter_1.address) {
delete filter_1.address;
}
_this.getLogs(filter_1).then(function (logs) {
if (logs.length === 0) {
return;
@@ -514,8 +546,10 @@ var BaseProvider = /** @class */ (function (_super) {
this.doPoll();
};
BaseProvider.prototype.resetEventsBlock = function (blockNumber) {
this._lastBlockNumber = blockNumber;
this._doPoll();
this._lastBlockNumber = blockNumber - 1;
if (this.polling) {
this._doPoll();
}
};
Object.defineProperty(BaseProvider.prototype, "network", {
get: function () {
@@ -529,10 +563,7 @@ var BaseProvider = /** @class */ (function (_super) {
};
Object.defineProperty(BaseProvider.prototype, "blockNumber", {
get: function () {
if (this._lastBlockNumber < 0) {
return null;
}
return this._lastBlockNumber;
return this._fastBlockNumber;
},
enumerable: true,
configurable: true
@@ -574,13 +605,47 @@ var BaseProvider = /** @class */ (function (_super) {
enumerable: true,
configurable: true
});
BaseProvider.prototype._getFastBlockNumber = function () {
var _this = this;
var now = getTime();
// Stale block number, request a newer value
if ((now - this._fastQueryDate) > 2 * this._pollingInterval) {
this._fastQueryDate = now;
this._fastBlockNumberPromise = this.getBlockNumber().then(function (blockNumber) {
if (_this._fastBlockNumber == null || blockNumber > _this._fastBlockNumber) {
_this._fastBlockNumber = blockNumber;
}
return _this._fastBlockNumber;
});
}
return this._fastBlockNumberPromise;
};
BaseProvider.prototype._setFastBlockNumber = function (blockNumber) {
// Older block, maybe a stale request
if (this._fastBlockNumber != null && blockNumber < this._fastBlockNumber) {
return;
}
// Update the time we updated the blocknumber
this._fastQueryDate = getTime();
// Newer block number, use it
if (this._fastBlockNumber == null || blockNumber > this._fastBlockNumber) {
this._fastBlockNumber = blockNumber;
this._fastBlockNumberPromise = Promise.resolve(blockNumber);
}
};
// @TODO: Add .poller which must be an event emitter with a 'start', 'stop' and 'block' event;
// this will be used once we move to the WebSocket or other alternatives to polling
BaseProvider.prototype.waitForTransaction = function (transactionHash, timeout) {
BaseProvider.prototype.waitForTransaction = function (transactionHash, confirmations) {
var _this = this;
if (confirmations == null) {
confirmations = 1;
}
return web_1.poll(function () {
return _this.getTransactionReceipt(transactionHash).then(function (receipt) {
if (receipt == null) {
if (confirmations === 0) {
return receipt;
}
if (receipt == null || receipt.confirmations < confirmations) {
return undefined;
}
return receipt;
@@ -595,6 +660,7 @@ var BaseProvider = /** @class */ (function (_super) {
if (value != result) {
throw new Error('invalid response - getBlockNumber');
}
_this._setFastBlockNumber(value);
return value;
});
});
@@ -688,7 +754,7 @@ var BaseProvider = /** @class */ (function (_super) {
// This should be called by any subclass wrapping a TransactionResponse
BaseProvider.prototype._wrapTransaction = function (tx, hash) {
var _this = this;
if (bytes_1.hexDataLength(hash) !== 32) {
if (hash != null && bytes_1.hexDataLength(hash) !== 32) {
throw new Error('invalid response - sendTransaction');
}
var result = tx;
@@ -696,13 +762,23 @@ var BaseProvider = /** @class */ (function (_super) {
if (hash != null && tx.hash !== hash) {
errors.throwError('Transaction hash mismatch from Provider.sendTransaction.', errors.UNKNOWN_ERROR, { expectedHash: tx.hash, returnedHash: hash });
}
this._emitted['t:' + tx.hash] = 'pending';
// @TODO: (confirmations? number, timeout? number)
result.wait = function () {
return _this.waitForTransaction(hash).then(function (receipt) {
result.wait = function (confirmations) {
// We know this transaction *must* exist (whether it gets mined is
// another story), so setting an emitted value forces us to
// wait even if the node returns null for the receipt
if (confirmations !== 0) {
_this._emitted['t:' + tx.hash] = 'pending';
}
return _this.waitForTransaction(tx.hash, confirmations).then(function (receipt) {
if (receipt == null && confirmations === 0) {
return null;
}
// No longer pending, allow the polling loop to garbage collect this
_this._emitted['t:' + tx.hash] = receipt.blockNumber;
if (receipt.status === 0) {
errors.throwError('transaction failed', errors.CALL_EXCEPTION, {
transactionHash: hash,
transactionHash: tx.hash,
transaction: tx
});
}
@@ -711,13 +787,14 @@ var BaseProvider = /** @class */ (function (_super) {
};
return result;
};
BaseProvider.prototype.call = function (transaction) {
BaseProvider.prototype.call = function (transaction, blockTag) {
var _this = this;
var tx = properties_1.shallowCopy(transaction);
return this.ready.then(function () {
return properties_1.resolveProperties(tx).then(function (tx) {
return properties_1.resolveProperties({ blockTag: blockTag, tx: tx }).then(function (_a) {
var blockTag = _a.blockTag, tx = _a.tx;
return _this._resolveNames(tx, ['to', 'from']).then(function (tx) {
var params = { transaction: checkTransactionRequest(tx) };
var params = { blockTag: checkBlockTag(blockTag), transaction: checkTransactionRequest(tx) };
return _this.perform('call', params).then(function (result) {
return bytes_1.hexlify(result);
});
@@ -751,12 +828,12 @@ var BaseProvider = /** @class */ (function (_super) {
return properties_1.resolveProperties({ blockHashOrBlockTag: blockHashOrBlockTag }).then(function (_a) {
var blockHashOrBlockTag = _a.blockHashOrBlockTag;
try {
var blockHash = bytes_1.hexlify(blockHashOrBlockTag);
if (bytes_1.hexDataLength(blockHash) === 32) {
var blockHash_1 = bytes_1.hexlify(blockHashOrBlockTag);
if (bytes_1.hexDataLength(blockHash_1) === 32) {
return web_1.poll(function () {
return _this.perform('getBlock', { blockHash: blockHash, includeTransactions: !!includeTransactions }).then(function (block) {
return _this.perform('getBlock', { blockHash: blockHash_1, includeTransactions: !!includeTransactions }).then(function (block) {
if (block == null) {
if (_this._emitted['b:' + blockHash] == null) {
if (_this._emitted['b:' + blockHash_1] == null) {
return null;
}
return undefined;
@@ -776,7 +853,7 @@ var BaseProvider = /** @class */ (function (_super) {
return web_1.poll(function () {
return _this.perform('getBlock', { blockTag: blockTag_1, includeTransactions: !!includeTransactions }).then(function (block) {
if (block == null) {
if (blockNumber_1 > _this._emitted.block) {
if (blockNumber_1 <= _this._emitted.block) {
return undefined;
}
return null;
@@ -795,7 +872,7 @@ var BaseProvider = /** @class */ (function (_super) {
return this.ready.then(function () {
return properties_1.resolveProperties({ transactionHash: transactionHash }).then(function (_a) {
var transactionHash = _a.transactionHash;
var params = { transactionHash: checkHash(transactionHash) };
var params = { transactionHash: checkHash(transactionHash, true) };
return web_1.poll(function () {
return _this.perform('getTransaction', params).then(function (result) {
if (result == null) {
@@ -804,7 +881,22 @@ var BaseProvider = /** @class */ (function (_super) {
}
return undefined;
}
return BaseProvider.checkTransactionResponse(result);
var tx = BaseProvider.checkTransactionResponse(result);
if (tx.blockNumber == null) {
tx.confirmations = 0;
}
else if (tx.confirmations == null) {
return _this._getFastBlockNumber().then(function (blockNumber) {
// Add the confirmations using the fast block number (pessimistic)
var confirmations = (blockNumber - tx.blockNumber) + 1;
if (confirmations <= 0) {
confirmations = 1;
}
tx.confirmations = confirmations;
return _this._wrapTransaction(tx);
});
}
return _this._wrapTransaction(tx);
});
}, { onceBlock: _this });
});
@@ -815,7 +907,7 @@ var BaseProvider = /** @class */ (function (_super) {
return this.ready.then(function () {
return properties_1.resolveProperties({ transactionHash: transactionHash }).then(function (_a) {
var transactionHash = _a.transactionHash;
var params = { transactionHash: checkHash(transactionHash) };
var params = { transactionHash: checkHash(transactionHash, true) };
return web_1.poll(function () {
return _this.perform('getTransactionReceipt', params).then(function (result) {
if (result == null) {
@@ -824,7 +916,26 @@ var BaseProvider = /** @class */ (function (_super) {
}
return undefined;
}
return checkTransactionReceipt(result);
// "geth-etc" returns receipts before they are ready
if (result.blockHash == null) {
return undefined;
}
var receipt = checkTransactionReceipt(result);
if (receipt.blockNumber == null) {
receipt.confirmations = 0;
}
else if (receipt.confirmations == null) {
return _this._getFastBlockNumber().then(function (blockNumber) {
// Add the confirmations using the fast block number (pessimistic)
var confirmations = (blockNumber - receipt.blockNumber) + 1;
if (confirmations <= 0) {
confirmations = 1;
}
receipt.confirmations = confirmations;
return receipt;
});
}
return receipt;
});
}, { onceBlock: _this });
});
@@ -1019,6 +1130,9 @@ var BaseProvider = /** @class */ (function (_super) {
result = true;
return !(event.once);
});
if (this.listenerCount() === 0) {
this.polling = false;
}
return result;
};
BaseProvider.prototype.listenerCount = function (eventName) {

View File

@@ -112,52 +112,65 @@ var EtherscanProvider = /** @class */ (function (_super) {
return _this;
}
EtherscanProvider.prototype.perform = function (method, params) {
//if (!params) { params = {}; }
var _this = this;
var url = this.baseUrl;
var apiKey = '';
if (this.apiKey) {
apiKey += '&apikey=' + this.apiKey;
}
var get = function (url, procFunc) {
return web_1.fetchJson(url, null, procFunc || getJsonResult).then(function (result) {
_this.emit('debug', {
action: 'perform',
request: url,
response: result,
provider: _this
});
return result;
});
};
switch (method) {
case 'getBlockNumber':
url += '/api?module=proxy&action=eth_blockNumber' + apiKey;
return web_1.fetchJson(url, null, getJsonResult);
return get(url);
case 'getGasPrice':
url += '/api?module=proxy&action=eth_gasPrice' + apiKey;
return web_1.fetchJson(url, null, getJsonResult);
return get(url);
case 'getBalance':
// Returns base-10 result
url += '/api?module=account&action=balance&address=' + params.address;
url += '&tag=' + params.blockTag + apiKey;
return web_1.fetchJson(url, null, getResult);
return get(url, getResult);
case 'getTransactionCount':
url += '/api?module=proxy&action=eth_getTransactionCount&address=' + params.address;
url += '&tag=' + params.blockTag + apiKey;
return web_1.fetchJson(url, null, getJsonResult);
return get(url);
case 'getCode':
url += '/api?module=proxy&action=eth_getCode&address=' + params.address;
url += '&tag=' + params.blockTag + apiKey;
return web_1.fetchJson(url, null, getJsonResult);
return get(url, getJsonResult);
case 'getStorageAt':
url += '/api?module=proxy&action=eth_getStorageAt&address=' + params.address;
url += '&position=' + params.position;
url += '&tag=' + params.blockTag + apiKey;
return web_1.fetchJson(url, null, getJsonResult);
return get(url, getJsonResult);
case 'sendTransaction':
url += '/api?module=proxy&action=eth_sendRawTransaction&hex=' + params.signedTransaction;
url += apiKey;
return web_1.fetchJson(url, null, getJsonResult).catch(function (error) {
// "Insufficient funds. The account you tried to send transaction from does not have enough funds. Required 21464000000000 and got: 0"
if (error.responseText.toLowerCase().indexOf('insufficient funds') >= 0) {
errors.throwError('insufficient funds', errors.INSUFFICIENT_FUNDS, {});
}
// "Transaction with the same hash was already imported."
if (error.responseText.indexOf('same hash was already imported') >= 0) {
errors.throwError('nonce has already been used', errors.NONCE_EXPIRED, {});
}
// "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce."
if (error.responseText.indexOf('another transaction with same nonce') >= 0) {
errors.throwError('replacement fee too low', errors.REPLACEMENT_UNDERPRICED, {});
return get(url).catch(function (error) {
if (error.responseText) {
// "Insufficient funds. The account you tried to send transaction from does not have enough funds. Required 21464000000000 and got: 0"
if (error.responseText.toLowerCase().indexOf('insufficient funds') >= 0) {
errors.throwError('insufficient funds', errors.INSUFFICIENT_FUNDS, {});
}
// "Transaction with the same hash was already imported."
if (error.responseText.indexOf('same hash was already imported') >= 0) {
errors.throwError('nonce has already been used', errors.NONCE_EXPIRED, {});
}
// "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce."
if (error.responseText.indexOf('another transaction with same nonce') >= 0) {
errors.throwError('replacement fee too low', errors.REPLACEMENT_UNDERPRICED, {});
}
}
throw error;
});
@@ -171,33 +184,39 @@ var EtherscanProvider = /** @class */ (function (_super) {
url += '&boolean=false';
}
url += apiKey;
return web_1.fetchJson(url, null, getJsonResult);
return get(url);
}
throw new Error('getBlock by blockHash not implmeneted');
case 'getTransaction':
url += '/api?module=proxy&action=eth_getTransactionByHash&txhash=' + params.transactionHash;
url += apiKey;
return web_1.fetchJson(url, null, getJsonResult);
return get(url);
case 'getTransactionReceipt':
url += '/api?module=proxy&action=eth_getTransactionReceipt&txhash=' + params.transactionHash;
url += apiKey;
return web_1.fetchJson(url, null, getJsonResult);
case 'call':
return get(url);
case 'call': {
var transaction = getTransactionString(params.transaction);
if (transaction) {
transaction = '&' + transaction;
}
url += '/api?module=proxy&action=eth_call' + transaction;
//url += '&tag=' + params.blockTag + apiKey;
if (params.blockTag !== 'latest') {
throw new Error('EtherscanProvider does not support blockTag for call');
}
url += apiKey;
return web_1.fetchJson(url, null, getJsonResult);
case 'estimateGas':
return get(url);
}
case 'estimateGas': {
var transaction = getTransactionString(params.transaction);
if (transaction) {
transaction = '&' + transaction;
}
url += '/api?module=proxy&action=eth_estimateGas&' + transaction;
url += apiKey;
return web_1.fetchJson(url, null, getJsonResult);
return get(url);
}
case 'getLogs':
url += '/api?module=logs&action=getLogs';
try {
@@ -227,7 +246,7 @@ var EtherscanProvider = /** @class */ (function (_super) {
}
url += apiKey;
var self = this;
return web_1.fetchJson(url, null, getResult).then(function (logs) {
return get(url, getResult).then(function (logs) {
var txs = {};
var seq = Promise.resolve();
logs.forEach(function (log) {
@@ -256,7 +275,7 @@ var EtherscanProvider = /** @class */ (function (_super) {
}
url += '/api?module=stats&action=ethprice';
url += apiKey;
return web_1.fetchJson(url, null, getResult).then(function (result) {
return get(url, getResult).then(function (result) {
return parseFloat(result.ethusd);
});
default:
@@ -266,6 +285,7 @@ var EtherscanProvider = /** @class */ (function (_super) {
};
// @TODO: Allow startBlock and endBlock to be Promises
EtherscanProvider.prototype.getHistory = function (addressOrName, startBlock, endBlock) {
var _this = this;
var url = this.baseUrl;
var apiKey = '';
if (this.apiKey) {
@@ -283,6 +303,12 @@ var EtherscanProvider = /** @class */ (function (_super) {
url += '&endblock=' + endBlock;
url += '&sort=asc' + apiKey;
return web_1.fetchJson(url, null, getResult).then(function (result) {
_this.emit('debug', {
action: 'getHistory',
request: url,
response: result,
provider: _this
});
var output = [];
result.forEach(function (tx) {
['contractAddress', 'to'].forEach(function (key) {

View File

@@ -40,7 +40,8 @@ function checkNetworks(networks) {
// Matches!
if (check.name === network.name &&
check.chainId === network.chainId &&
check.ensAddress === network.ensAddress) {
((check.ensAddress === network.ensAddress) ||
(check.ensAddress == null && network.ensAddress == null))) {
return;
}
errors.throwError('provider mismatch', errors.INVALID_ARGUMENT, { arg: 'networks', value: networks });

View File

@@ -27,5 +27,9 @@ export declare class JsonRpcProvider extends BaseProvider {
perform(method: string, params: any): Promise<any>;
protected _startPending(): void;
protected _stopPending(): void;
static hexlifyTransaction(transaction: TransactionRequest): any;
static hexlifyTransaction(transaction: TransactionRequest, allowExtra?: {
[key: string]: boolean;
}): {
[key: string]: string;
};
}

View File

@@ -20,13 +20,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC
var base_provider_1 = require("./base-provider");
var abstract_signer_1 = require("../abstract-signer");
var errors = __importStar(require("../errors"));
var address_1 = require("../utils/address");
var bytes_1 = require("../utils/bytes");
var networks_1 = require("../utils/networks");
var properties_1 = require("../utils/properties");
var transaction_1 = require("../utils/transaction");
var utf8_1 = require("../utils/utf8");
var web_1 = require("../utils/web");
var errors = __importStar(require("../errors"));
function timer(timeout) {
return new Promise(function (resolve) {
setTimeout(function () {
@@ -77,14 +78,6 @@ var JsonRpcSigner = /** @class */ (function (_super) {
}
return _this;
}
/* May add back in the future; for now it is considered confusing. :)
get address(): string {
if (!this._address) {
errors.throwError('no sync sync address available; use getAddress', errors.UNSUPPORTED_OPERATION, { operation: 'address' });
}
return this._address
}
*/
JsonRpcSigner.prototype.getAddress = function () {
var _this = this;
if (this._address) {
@@ -106,20 +99,18 @@ var JsonRpcSigner = /** @class */ (function (_super) {
};
JsonRpcSigner.prototype.sendTransaction = function (transaction) {
var _this = this;
var tx = properties_1.shallowCopy(transaction);
if (tx.from == null) {
tx.from = this.getAddress().then(function (address) {
if (!address) {
return null;
}
return address.toLowerCase();
});
}
if (transaction.gasLimit == null) {
tx.gasLimit = this.provider.estimateGas(tx);
}
return properties_1.resolveProperties(tx).then(function (tx) {
return _this.provider.send('eth_sendTransaction', [JsonRpcProvider.hexlifyTransaction(tx)]).then(function (hash) {
// Once populateTransaction resolves, the from address will be populated from getAddress
var from = null;
var getAddress = this.getAddress().then(function (address) {
if (address) {
from = address.toLowerCase();
}
return from;
});
return transaction_1.populateTransaction(transaction, this.provider, getAddress).then(function (tx) {
var hexTx = JsonRpcProvider.hexlifyTransaction(tx);
hexTx.from = from;
return _this.provider.send('eth_sendTransaction', [hexTx]).then(function (hash) {
return web_1.poll(function () {
return _this.provider.getTransaction(hash).then(function (tx) {
if (tx === null) {
@@ -132,21 +123,23 @@ var JsonRpcSigner = /** @class */ (function (_super) {
throw error;
});
}, function (error) {
// See: JsonRpcProvider.sendTransaction (@TODO: Expose a ._throwError??)
if (error.responseText.indexOf('insufficient funds') >= 0) {
errors.throwError('insufficient funds', errors.INSUFFICIENT_FUNDS, {
transaction: tx
});
}
if (error.responseText.indexOf('nonce too low') >= 0) {
errors.throwError('nonce has already been used', errors.NONCE_EXPIRED, {
transaction: tx
});
}
if (error.responseText.indexOf('replacement transaction underpriced') >= 0) {
errors.throwError('replacement fee too low', errors.REPLACEMENT_UNDERPRICED, {
transaction: tx
});
if (error.responseText) {
// See: JsonRpcProvider.sendTransaction (@TODO: Expose a ._throwError??)
if (error.responseText.indexOf('insufficient funds') >= 0) {
errors.throwError('insufficient funds', errors.INSUFFICIENT_FUNDS, {
transaction: tx
});
}
if (error.responseText.indexOf('nonce too low') >= 0) {
errors.throwError('nonce has already been used', errors.NONCE_EXPIRED, {
transaction: tx
});
}
if (error.responseText.indexOf('replacement transaction underpriced') >= 0) {
errors.throwError('replacement fee too low', errors.REPLACEMENT_UNDERPRICED, {
transaction: tx
});
}
}
throw error;
});
@@ -169,6 +162,9 @@ var JsonRpcSigner = /** @class */ (function (_super) {
return JsonRpcSigner;
}(abstract_signer_1.Signer));
exports.JsonRpcSigner = JsonRpcSigner;
var allowedTransactionKeys = {
chainId: true, data: true, gasLimit: true, gasPrice: true, nonce: true, to: true, value: true
};
var JsonRpcProvider = /** @class */ (function (_super) {
__extends(JsonRpcProvider, _super);
function JsonRpcProvider(url, network) {
@@ -221,13 +217,22 @@ var JsonRpcProvider = /** @class */ (function (_super) {
});
};
JsonRpcProvider.prototype.send = function (method, params) {
var _this = this;
var request = {
method: method,
params: params,
id: 42,
jsonrpc: "2.0"
};
return web_1.fetchJson(this.connection, JSON.stringify(request), getResult);
return web_1.fetchJson(this.connection, JSON.stringify(request), getResult).then(function (result) {
_this.emit('debug', {
action: 'send',
request: request,
response: result,
provider: _this
});
return result;
});
};
JsonRpcProvider.prototype.perform = function (method, params) {
switch (method) {
@@ -245,17 +250,19 @@ var JsonRpcProvider = /** @class */ (function (_super) {
return this.send('eth_getStorageAt', [getLowerCase(params.address), params.position, params.blockTag]);
case 'sendTransaction':
return this.send('eth_sendRawTransaction', [params.signedTransaction]).catch(function (error) {
// "insufficient funds for gas * price + value"
if (error.responseText.indexOf('insufficient funds') > 0) {
errors.throwError('insufficient funds', errors.INSUFFICIENT_FUNDS, {});
}
// "nonce too low"
if (error.responseText.indexOf('nonce too low') > 0) {
errors.throwError('nonce has already been used', errors.NONCE_EXPIRED, {});
}
// "replacement transaction underpriced"
if (error.responseText.indexOf('replacement transaction underpriced') > 0) {
errors.throwError('replacement fee too low', errors.REPLACEMENT_UNDERPRICED, {});
if (error.responseText) {
// "insufficient funds for gas * price + value"
if (error.responseText.indexOf('insufficient funds') > 0) {
errors.throwError('insufficient funds', errors.INSUFFICIENT_FUNDS, {});
}
// "nonce too low"
if (error.responseText.indexOf('nonce too low') > 0) {
errors.throwError('nonce has already been used', errors.NONCE_EXPIRED, {});
}
// "replacement transaction underpriced"
if (error.responseText.indexOf('replacement transaction underpriced') > 0) {
errors.throwError('replacement fee too low', errors.REPLACEMENT_UNDERPRICED, {});
}
}
throw error;
});
@@ -272,9 +279,9 @@ var JsonRpcProvider = /** @class */ (function (_super) {
case 'getTransactionReceipt':
return this.send('eth_getTransactionReceipt', [params.transactionHash]);
case 'call':
return this.send('eth_call', [JsonRpcProvider.hexlifyTransaction(params.transaction), 'latest']);
return this.send('eth_call', [JsonRpcProvider.hexlifyTransaction(params.transaction, { from: true }), params.blockTag]);
case 'estimateGas':
return this.send('eth_estimateGas', [JsonRpcProvider.hexlifyTransaction(params.transaction)]);
return this.send('eth_estimateGas', [JsonRpcProvider.hexlifyTransaction(params.transaction, { from: true })]);
case 'getLogs':
if (params.filter && params.filter.address != null) {
params.filter.address = getLowerCase(params.filter.address);
@@ -301,6 +308,7 @@ var JsonRpcProvider = /** @class */ (function (_super) {
}
var seq = Promise.resolve();
hashes.forEach(function (hash) {
// @TODO: This should be garbage collected at some point... How? When?
self._emitted['t:' + hash.toLowerCase()] = 'pending';
seq = seq.then(function () {
return self.getTransaction(hash).then(function (tx) {
@@ -332,10 +340,21 @@ var JsonRpcProvider = /** @class */ (function (_super) {
// - gasLimit => gas
// - All values hexlified
// - All numeric values zero-striped
// @TODO: Not any, a dictionary of string to strings
JsonRpcProvider.hexlifyTransaction = function (transaction) {
// NOTE: This allows a TransactionRequest, but all values should be resolved
// before this is called
JsonRpcProvider.hexlifyTransaction = function (transaction, allowExtra) {
// Check only allowed properties are given
var allowed = properties_1.shallowCopy(allowedTransactionKeys);
if (allowExtra) {
for (var key in allowExtra) {
if (allowExtra[key]) {
allowed[key] = true;
}
}
}
properties_1.checkProperties(transaction, allowed);
var result = {};
// Some nodes (INFURA ropsten; INFURA mainnet is fine) don't like extra zeros.
// Some nodes (INFURA ropsten; INFURA mainnet is fine) don't like leading zeros.
['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(function (key) {
if (transaction[key] == null) {
return;

View File

@@ -1 +1 @@
export const version = "4.0.2";
export const version = "4.0.13";

View File

@@ -16,7 +16,7 @@ import { UnsignedTransaction } from './utils/transaction';
///////////////////////////////
// Imported Abstracts
import { Provider } from './providers/abstract-provider';
import { BlockTag, Provider } from './providers/abstract-provider';
import { Signer } from './abstract-signer';
///////////////////////////////
@@ -148,14 +148,22 @@ type RunFunction = (...params: Array<any>) => Promise<any>;
function runMethod(contract: Contract, functionName: string, estimateOnly: boolean): RunFunction {
let method = contract.interface.functions[functionName];
return function(...params): Promise<any> {
var tx: any = {}
let tx: any = {}
let blockTag: BlockTag = null;
// If 1 extra parameter was passed in, it contains overrides
if (params.length === method.inputs.length + 1 && typeof(params[params.length - 1]) === 'object') {
tx = shallowCopy(params.pop());
if (tx.blockTag != null) {
blockTag = tx.blockTag;
}
delete tx.blockTag;
// Check for unexpected keys (e.g. using "gas" instead of "gasLimit")
for (var key in tx) {
for (let key in tx) {
if (!allowedTransactionKeys[key]) {
throw new Error('unknown transaction override ' + key);
}
@@ -202,7 +210,7 @@ function runMethod(contract: Contract, functionName: string, estimateOnly: boole
tx.from = contract.signer.getAddress()
}
return contract.provider.call(tx).then((value) => {
return contract.provider.call(tx, blockTag).then((value) => {
if ((hexDataLength(value) % 32) === 4 && hexDataSlice(value, 0, 4) === '0x08c379a0') {
let reason = defaultAbiCoder.decode([ 'string' ], hexDataSlice(value, 4));
@@ -791,7 +799,7 @@ export class ContractFactory {
});
// Make sure the call matches the constructor signature
errors.checkArgumentCount(args.length, this.interface.deployFunction.inputs.length, 'in Contract constructor');
errors.checkArgumentCount(args.length, this.interface.deployFunction.inputs.length, ' in Contract constructor');
// Set the data to the bytecode + the encoded constructor arguments
tx.data = this.interface.deployFunction.encode(this.bytecode, args);

View File

@@ -122,3 +122,22 @@ export function setCensorship(censorship: boolean, permanent?: boolean): void {
_censorErrors = !!censorship;
_permanentCensorErrors = !!permanent;
}
export function checkNormalize(): void {
try {
// Make sure all forms of normalization are supported
["NFD", "NFC", "NFKD", "NFKC"].forEach((form) => {
try {
"test".normalize(form);
} catch(error) {
throw new Error('missing ' + form);
}
});
if (String.fromCharCode(0xe9).normalize('NFD') !== String.fromCharCode(0x65, 0x0301)) {
throw new Error('broken implementation')
}
} catch (error) {
throwError('platform missing String.prototype.normalize', UNSUPPORTED_OPERATION, { operation: 'String.prototype.normalize', form: error.message });
}
}

View File

@@ -33,10 +33,15 @@ import { ContractFunction, ContractTransaction, Event, EventFilter } from './con
// Helper Functions
function getDefaultProvider(network?: utils.Network | string): providers.BaseProvider {
return new providers.FallbackProvider([
new providers.InfuraProvider(network),
new providers.EtherscanProvider(network),
]);
if (network == null) { network = 'homestead'; }
let n = utils.getNetwork(network);
if (!n || !n._defaultProvider) {
errors.throwError('unsupported getDefaultProvider network', errors.UNSUPPORTED_OPERATION, {
operation: 'getDefaultProvider',
network: network
});
}
return n._defaultProvider(providers);
}

View File

@@ -125,7 +125,7 @@ export abstract class Provider implements OnceBlockable {
abstract getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
abstract call(transaction: TransactionRequest): Promise<string>;
abstract call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract estimateGas(transaction: TransactionRequest): Promise<BigNumber>;
abstract getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>, includeTransactions?: boolean): Promise<Block>;

View File

@@ -38,10 +38,10 @@ import { Network, Networkish } from '../utils/networks';
// @TODO: not any?
function check(format: any, object: any): any {
var result: any = {};
for (var key in format) {
let result: any = {};
for (let key in format) {
try {
var value = format[key](object[key]);
let value = format[key](object[key]);
if (value !== undefined) { result[key] = value; }
} catch (error) {
error.checkKey = key;
@@ -72,7 +72,7 @@ function arrayOf(check: CheckFunc): CheckFunc {
return (function(array: any): Array<any> {
if (!Array.isArray(array)) { throw new Error('not an array'); }
var result: any = [];
let result: any = [];
array.forEach(function(value) {
result.push(check(value));
@@ -82,9 +82,13 @@ function arrayOf(check: CheckFunc): CheckFunc {
});
}
function checkHash(hash: any): string {
if (typeof(hash) === 'string' && hexDataLength(hash) === 32) {
return hash.toLowerCase();
function checkHash(hash: any, requirePrefix?: boolean): string {
if (typeof(hash) === 'string') {
// geth-etc does add a "0x" prefix on receipt.root
if (!requirePrefix && hash.substring(0, 2) !== '0x') { hash = '0x' + hash; }
if (hexDataLength(hash) === 32) {
return hash.toLowerCase();
}
}
errors.throwError('invalid hash', errors.INVALID_ARGUMENT, { arg: 'hash', value: hash });
return null;
@@ -204,7 +208,7 @@ function checkTransactionResponse(transaction: any): TransactionResponse {
// Very loose providers (e.g. TestRPC) don't provide a signature or raw
if (transaction.v && transaction.r && transaction.s) {
var raw = [
let raw = [
stripZeros(hexlify(transaction.nonce)),
stripZeros(hexlify(transaction.gasPrice)),
stripZeros(hexlify(transaction.gasLimit)),
@@ -220,10 +224,14 @@ function checkTransactionResponse(transaction: any): TransactionResponse {
}
}
let result = check(formatTransaction, transaction);
var result = check(formatTransaction, transaction);
let networkId = transaction.networkId;
var networkId = transaction.networkId;
// geth-etc returns chainId
if (transaction.chainId != null && networkId == null && result.v == null) {
networkId = transaction.chainId;
}
if (isHexString(networkId)) {
networkId = bigNumberify(networkId).toNumber();
@@ -324,7 +332,7 @@ function checkTransactionReceipt(transactionReceipt: any): TransactionReceipt {
//var status = transactionReceipt.status;
//var root = transactionReceipt.root;
var result: TransactionReceipt = check(formatTransactionReceipt, transactionReceipt);
let result: TransactionReceipt = check(formatTransactionReceipt, transactionReceipt);
result.logs.forEach(function(entry, index) {
if (entry.transactionLogIndex == null) {
entry.transactionLogIndex = index;
@@ -425,13 +433,14 @@ function getEventTag(eventName: EventType): string {
eventName = eventName.toLowerCase();
if (eventName === 'block' || eventName === 'pending' || eventName === 'error') {
return eventName;
} else if (hexDataLength(eventName) === 32) {
if (hexDataLength(eventName) === 32) {
return 'tx:' + eventName;
}
if (eventName.indexOf(':') === -1) {
return eventName;
}
} else if (Array.isArray(eventName)) {
return 'filter::' + serializeTopics(eventName);
@@ -474,10 +483,21 @@ export class BaseProvider extends Provider {
private _network: Network;
private _events: Array<_Event>;
protected _emitted: any;
// To help mitigate the eventually conssitent nature of the blockchain
// we keep a mapping of events we emit. If we emit an event X, we expect
// that a user should be able to query for that event in the callback,
// if the node returns null, we stall the response until we get back a
// meaningful value, since we may be hitting a re-org, or a node that
// has not indexed the event yet.
// Events:
// - t:{hash} - Transaction hash
// - b:{hash} - BlockHash
// - block - The most recent emitted block
protected _emitted: { [ eventName: string ]: number | 'pending' };
private _pollingInterval: number;
private _poller: any; // @TODO: what does TypeScript thing setInterval returns?
private _poller: any; // @TODO: what does TypeScript think setInterval returns?
private _lastBlockNumber: number;
@@ -531,11 +551,7 @@ export class BaseProvider extends Provider {
this._pollingInterval = 4000;
// We use this to track recent emitted events; for example, if we emit a "block" of 100
// and we get a `getBlock(100)` request which would result in null, we should retry
// until we get a response. This provides devs with a consistent view. Similarly for
// transaction hashes.
this._emitted = { block: this._lastBlockNumber };
this._emitted = { block: -2 };
this._fastQueryDate = 0;
}
@@ -547,28 +563,47 @@ export class BaseProvider extends Provider {
// If the block hasn't changed, meh.
if (blockNumber === this._lastBlockNumber) { return; }
if (this._lastBlockNumber === -2) { this._lastBlockNumber = blockNumber - 1; }
// First polling cycle, trigger a "block" events
if (this._emitted.block === -2) {
this._emitted.block = blockNumber - 1;
}
// Notify all listener for each block that has passed
for (var i = this._lastBlockNumber + 1; i <= blockNumber; i++) {
if (this._emitted.block < i) {
this._emitted.block = i;
// Evict any transaction hashes or block hashes over 12 blocks
// old, since they should not return null anyways
Object.keys(this._emitted).forEach((key) => {
if (key === 'block') { return; }
if (this._emitted[key] > i + 12) {
delete this._emitted[key];
}
});
}
for (let i = (<number>this._emitted.block) + 1; i <= blockNumber; i++) {
this.emit('block', i);
}
// The emitted block was updated, check for obsolete events
if ((<number>this._emitted.block) !== blockNumber) {
this._emitted.block = blockNumber;
Object.keys(this._emitted).forEach((key) => {
// The block event does not expire
if (key === 'block') { return; }
// The block we were at when we emitted this event
let eventBlockNumber = this._emitted[key];
// We cannot garbage collect pending transactions or blocks here
// They should be garbage collected by the Provider when setting
// "pending" events
if (eventBlockNumber === 'pending') { return; }
// Evict any transaction hashes or block hashes over 12 blocks
// old, since they should not return null anyways
if (blockNumber - eventBlockNumber > 12) {
delete this._emitted[key];
}
});
}
// First polling cycle
if (this._lastBlockNumber === -2) {
this._lastBlockNumber = blockNumber - 1;
}
// Sweep balances and remove addresses we no longer have events for
var newBalances: any = {};
let newBalances: any = {};
// Find all transaction hashes we are waiting on
this._events.forEach((event) => {
@@ -582,6 +617,7 @@ export class BaseProvider extends Provider {
this.emit(hash, receipt);
return null;
}).catch((error: Error) => { this.emit('error', error); });
break;
}
@@ -590,25 +626,27 @@ export class BaseProvider extends Provider {
if (this._balances[address]) {
newBalances[address] = this._balances[address];
}
this.getBalance(address, 'latest').then(function(balance) {
var lastBalance = this._balances[address];
this.getBalance(address, 'latest').then((balance) => {
let lastBalance = this._balances[address];
if (lastBalance && balance.eq(lastBalance)) { return; }
this._balances[address] = balance;
this.emit(address, balance);
return null;
}).catch((error: Error) => { this.emit('error', error); });
break;
}
case 'filter': {
let address = comps[1];
let topics = deserializeTopics(comps[2]);
let filter = {
address: address,
address: comps[1],
fromBlock: this._lastBlockNumber + 1,
toBlock: blockNumber,
topics: topics
}
if (!filter.address) { delete filter.address; }
this.getLogs(filter).then((logs) => {
if (logs.length === 0) { return; }
logs.forEach((log: Log) => {
@@ -629,12 +667,13 @@ export class BaseProvider extends Provider {
return null;
}).catch((error: Error) => { });
this.doPoll();
}
resetEventsBlock(blockNumber: number): void {
this._lastBlockNumber = blockNumber;
this._doPoll();
this._lastBlockNumber = blockNumber - 1;
if (this.polling) { this._doPoll(); }
}
get network(): Network {
@@ -646,8 +685,7 @@ export class BaseProvider extends Provider {
}
get blockNumber(): number {
if (this._lastBlockNumber < 0) { return null; }
return this._lastBlockNumber;
return this._fastBlockNumber;
}
get polling(): boolean {
@@ -718,10 +756,13 @@ export class BaseProvider extends Provider {
// this will be used once we move to the WebSocket or other alternatives to polling
waitForTransaction(transactionHash: string, confirmations?: number): Promise<TransactionReceipt> {
if (!confirmations) { confirmations = 1; }
if (confirmations == null) { confirmations = 1; }
return poll(() => {
return this.getTransactionReceipt(transactionHash).then((receipt) => {
if (receipt == null || receipt.confirmations < confirmations) { return undefined; }
if (confirmations === 0) { return receipt; }
if (receipt == null || receipt.confirmations < confirmations) {
return undefined;
}
return receipt;
});
}, { onceBlock: this });
@@ -751,7 +792,7 @@ export class BaseProvider extends Provider {
return this.ready.then(() => {
return resolveProperties({ addressOrName: addressOrName, blockTag: blockTag }).then(({ addressOrName, blockTag }) => {
return this.resolveName(addressOrName).then((address) => {
var params = { address: address, blockTag: checkBlockTag(blockTag) };
let params = { address: address, blockTag: checkBlockTag(blockTag) };
return this.perform('getBalance', params).then((result) => {
return bigNumberify(result);
});
@@ -764,7 +805,7 @@ export class BaseProvider extends Provider {
return this.ready.then(() => {
return resolveProperties({ addressOrName: addressOrName, blockTag: blockTag }).then(({ addressOrName, blockTag }) => {
return this.resolveName(addressOrName).then((address) => {
var params = { address: address, blockTag: checkBlockTag(blockTag) };
let params = { address: address, blockTag: checkBlockTag(blockTag) };
return this.perform('getTransactionCount', params).then((result) => {
return bigNumberify(result).toNumber();
});
@@ -777,7 +818,7 @@ export class BaseProvider extends Provider {
return this.ready.then(() => {
return resolveProperties({ addressOrName: addressOrName, blockTag: blockTag }).then(({ addressOrName, blockTag }) => {
return this.resolveName(addressOrName).then((address) => {
var params = {address: address, blockTag: checkBlockTag(blockTag)};
let params = {address: address, blockTag: checkBlockTag(blockTag)};
return this.perform('getCode', params).then((result) => {
return hexlify(result);
});
@@ -790,7 +831,7 @@ export class BaseProvider extends Provider {
return this.ready.then(() => {
return resolveProperties({ addressOrName: addressOrName, position: position, blockTag: blockTag }).then(({ addressOrName, position, blockTag }) => {
return this.resolveName(addressOrName).then((address) => {
var params = {
let params = {
address: address,
blockTag: checkBlockTag(blockTag),
position: hexStripZeros(hexlify(position)),
@@ -806,7 +847,7 @@ export class BaseProvider extends Provider {
sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse> {
return this.ready.then(() => {
return resolveProperties({ signedTransaction: signedTransaction }).then(({ signedTransaction }) => {
var params = { signedTransaction: hexlify(signedTransaction) };
let params = { signedTransaction: hexlify(signedTransaction) };
return this.perform('sendTransaction', params).then((hash) => {
return this._wrapTransaction(parseTransaction(signedTransaction), hash);
}, function (error) {
@@ -831,11 +872,22 @@ export class BaseProvider extends Provider {
errors.throwError('Transaction hash mismatch from Provider.sendTransaction.', errors.UNKNOWN_ERROR, { expectedHash: tx.hash, returnedHash: hash });
}
this._emitted['t:' + tx.hash] = 'pending';
// @TODO: (confirmations? number, timeout? number)
result.wait = (confirmations?: number) => {
// We know this transaction *must* exist (whether it gets mined is
// another story), so setting an emitted value forces us to
// wait even if the node returns null for the receipt
if (confirmations !== 0) {
this._emitted['t:' + tx.hash] = 'pending';
}
return this.waitForTransaction(tx.hash, confirmations).then((receipt) => {
if (receipt == null && confirmations === 0) { return null; }
// No longer pending, allow the polling loop to garbage collect this
this._emitted['t:' + tx.hash] = receipt.blockNumber;
if (receipt.status === 0) {
errors.throwError('transaction failed', errors.CALL_EXCEPTION, {
transactionHash: tx.hash,
@@ -850,12 +902,12 @@ export class BaseProvider extends Provider {
}
call(transaction: TransactionRequest): Promise<string> {
call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
let tx: TransactionRequest = shallowCopy(transaction);
return this.ready.then(() => {
return resolveProperties(tx).then((tx) => {
return resolveProperties({ blockTag: blockTag, tx: tx }).then(({ blockTag, tx }) => {
return this._resolveNames(tx, [ 'to', 'from' ]).then((tx) => {
var params = { transaction: checkTransactionRequest(tx) };
let params = { blockTag: checkBlockTag(blockTag), transaction: checkTransactionRequest(tx) };
return this.perform('call', params).then((result) => {
return hexlify(result);
});
@@ -876,7 +928,7 @@ export class BaseProvider extends Provider {
return this.ready.then(() => {
return resolveProperties(tx).then((tx) => {
return this._resolveNames(tx, [ 'to', 'from' ]).then((tx) => {
var params = { transaction: checkTransactionRequest(tx) };
let params = { transaction: checkTransactionRequest(tx) };
return this.perform('estimateGas', params).then((result) => {
return bigNumberify(result);
});
@@ -889,7 +941,7 @@ export class BaseProvider extends Provider {
return this.ready.then(() => {
return resolveProperties({ blockHashOrBlockTag: blockHashOrBlockTag }).then(({ blockHashOrBlockTag }) => {
try {
var blockHash = hexlify(blockHashOrBlockTag);
let blockHash = hexlify(blockHashOrBlockTag);
if (hexDataLength(blockHash) === 32) {
return poll(() => {
return this.perform('getBlock', { blockHash: blockHash, includeTransactions: !!includeTransactions }).then((block) => {
@@ -917,7 +969,7 @@ export class BaseProvider extends Provider {
return poll(() => {
return this.perform('getBlock', { blockTag: blockTag, includeTransactions: !!includeTransactions }).then((block) => {
if (block == null) {
if (blockNumber > this._emitted.block) {
if (blockNumber <= this._emitted.block) {
return undefined;
}
return null;
@@ -935,7 +987,7 @@ export class BaseProvider extends Provider {
getTransaction(transactionHash: string): Promise<TransactionResponse> {
return this.ready.then(() => {
return resolveProperties({ transactionHash: transactionHash }).then(({ transactionHash }) => {
let params = { transactionHash: checkHash(transactionHash) };
let params = { transactionHash: checkHash(transactionHash, true) };
return poll(() => {
return this.perform('getTransaction', params).then((result) => {
if (result == null) {
@@ -972,7 +1024,7 @@ export class BaseProvider extends Provider {
getTransactionReceipt(transactionHash: string): Promise<TransactionReceipt> {
return this.ready.then(() => {
return resolveProperties({ transactionHash: transactionHash }).then(({ transactionHash }) => {
let params = { transactionHash: checkHash(transactionHash) };
let params = { transactionHash: checkHash(transactionHash, true) };
return poll(() => {
return this.perform('getTransactionReceipt', params).then((result) => {
if (result == null) {
@@ -1013,7 +1065,7 @@ export class BaseProvider extends Provider {
return this.ready.then(() => {
return resolveProperties(filter).then((filter) => {
return this._resolveNames(filter, ['address']).then((filter) => {
var params = { filter: checkFilter(filter) };
let params = { filter: checkFilter(filter) };
return this.perform('getLogs', params).then((result) => {
return arrayOf(checkLog)(result);
});
@@ -1033,9 +1085,9 @@ export class BaseProvider extends Provider {
// @TODO: Could probably use resolveProperties instead?
private _resolveNames(object: any, keys: Array<string>): Promise<{ [key: string]: string }> {
var promises: Array<Promise<void>> = [];
let promises: Array<Promise<void>> = [];
var result: { [key: string ]: string } = shallowCopy(object);
let result: { [key: string ]: string } = shallowCopy(object);
keys.forEach(function(key) {
if (result[key] == null) { return; }
@@ -1062,8 +1114,8 @@ export class BaseProvider extends Provider {
}
// keccak256('resolver(bytes32)')
var data = '0x0178b8bf' + namehash(name).substring(2);
var transaction = { to: network.ensAddress, data: data };
let data = '0x0178b8bf' + namehash(name).substring(2);
let transaction = { to: network.ensAddress, data: data };
return this.call(transaction).then((data) => {
@@ -1088,22 +1140,22 @@ export class BaseProvider extends Provider {
return Promise.resolve(getAddress(name));
} catch (error) { }
var self = this;
let self = this;
var nodeHash = namehash(name);
let nodeHash = namehash(name);
// Get the addr from the resovler
return this._getResolver(name).then(function(resolverAddress) {
// keccak256('addr(bytes32)')
var data = '0x3b3b57de' + nodeHash.substring(2);
var transaction = { to: resolverAddress, data: data };
let data = '0x3b3b57de' + nodeHash.substring(2);
let transaction = { to: resolverAddress, data: data };
return self.call(transaction);
// extract the address from the data
}).then(function(data) {
if (hexDataLength(data) !== 32) { return null; }
var address = getAddress(hexDataSlice(data, 12));
let address = getAddress(hexDataSlice(data, 12));
if (address === '0x0000000000000000000000000000000000000000') { return null; }
return address;
});
@@ -1118,17 +1170,17 @@ export class BaseProvider extends Provider {
address = getAddress(address);
var name = address.substring(2) + '.addr.reverse'
var nodehash = namehash(name);
let name = address.substring(2) + '.addr.reverse'
let nodehash = namehash(name);
var self = this;
let self = this;
return this._getResolver(name).then(function(resolverAddress) {
if (!resolverAddress) { return null; }
// keccak('name(bytes32)')
var data = '0x691f3431' + nodehash.substring(2);
var transaction = { to: resolverAddress, data: data };
let data = '0x691f3431' + nodehash.substring(2);
let transaction = { to: resolverAddress, data: data };
return self.call(transaction);
}).then(function(data) {
@@ -1140,12 +1192,12 @@ export class BaseProvider extends Provider {
data = data.substring(64);
if (data.length < 64) { return null; }
var length = bigNumberify('0x' + data.substring(0, 64)).toNumber();
let length = bigNumberify('0x' + data.substring(0, 64)).toNumber();
data = data.substring(64);
if (2 * length > data.length) { return null; }
var name = toUtf8String('0x' + data.substring(0, 2 * length));
let name = toUtf8String('0x' + data.substring(0, 2 * length));
// Make sure the reverse record matches the foward record
return self.resolveName(name).then(function(addr) {
@@ -1212,6 +1264,8 @@ export class BaseProvider extends Provider {
return !(event.once);
});
if (this.listenerCount() === 0) { this.polling = false; }
return result;
}
@@ -1261,6 +1315,7 @@ export class BaseProvider extends Provider {
return this;
}
}
defineReadOnly(Provider, 'inherits', inheritable(Provider));

View File

@@ -108,50 +108,60 @@ export class EtherscanProvider extends BaseProvider{
perform(method: string, params: any) {
//if (!params) { params = {}; }
var url = this.baseUrl;
let url = this.baseUrl;
let apiKey = '';
if (this.apiKey) { apiKey += '&apikey=' + this.apiKey; }
let get = (url: string, procFunc?: (value: any) => any) => {
return fetchJson(url, null, procFunc || getJsonResult).then((result) => {
this.emit('debug', {
action: 'perform',
request: url,
response: result,
provider: this
});
return result;
});
};
switch (method) {
case 'getBlockNumber':
url += '/api?module=proxy&action=eth_blockNumber' + apiKey;
return fetchJson(url, null, getJsonResult);
return get(url);
case 'getGasPrice':
url += '/api?module=proxy&action=eth_gasPrice' + apiKey;
return fetchJson(url, null, getJsonResult);
return get(url);
case 'getBalance':
// Returns base-10 result
url += '/api?module=account&action=balance&address=' + params.address;
url += '&tag=' + params.blockTag + apiKey;
return fetchJson(url, null, getResult);
return get(url, getResult);
case 'getTransactionCount':
url += '/api?module=proxy&action=eth_getTransactionCount&address=' + params.address;
url += '&tag=' + params.blockTag + apiKey;
return fetchJson(url, null, getJsonResult);
return get(url);
case 'getCode':
url += '/api?module=proxy&action=eth_getCode&address=' + params.address;
url += '&tag=' + params.blockTag + apiKey;
return fetchJson(url, null, getJsonResult);
return get(url, getJsonResult);
case 'getStorageAt':
url += '/api?module=proxy&action=eth_getStorageAt&address=' + params.address;
url += '&position=' + params.position;
url += '&tag=' + params.blockTag + apiKey;
return fetchJson(url, null, getJsonResult);
return get(url, getJsonResult);
case 'sendTransaction':
url += '/api?module=proxy&action=eth_sendRawTransaction&hex=' + params.signedTransaction;
url += apiKey;
return fetchJson(url, null, getJsonResult).catch((error) => {
return get(url).catch((error) => {
if (error.responseText) {
// "Insufficient funds. The account you tried to send transaction from does not have enough funds. Required 21464000000000 and got: 0"
if (error.responseText.toLowerCase().indexOf('insufficient funds') >= 0) {
@@ -178,34 +188,40 @@ export class EtherscanProvider extends BaseProvider{
url += '&boolean=false';
}
url += apiKey;
return fetchJson(url, null, getJsonResult);
return get(url);
}
throw new Error('getBlock by blockHash not implmeneted');
case 'getTransaction':
url += '/api?module=proxy&action=eth_getTransactionByHash&txhash=' + params.transactionHash;
url += apiKey;
return fetchJson(url, null, getJsonResult);
return get(url);
case 'getTransactionReceipt':
url += '/api?module=proxy&action=eth_getTransactionReceipt&txhash=' + params.transactionHash;
url += apiKey;
return fetchJson(url, null, getJsonResult);
return get(url);
case 'call':
var transaction = getTransactionString(params.transaction);
case 'call': {
let transaction = getTransactionString(params.transaction);
if (transaction) { transaction = '&' + transaction; }
url += '/api?module=proxy&action=eth_call' + transaction;
//url += '&tag=' + params.blockTag + apiKey;
if (params.blockTag !== 'latest') {
throw new Error('EtherscanProvider does not support blockTag for call');
}
url += apiKey;
return fetchJson(url, null, getJsonResult);
return get(url);
}
case 'estimateGas':
var transaction = getTransactionString(params.transaction);
case 'estimateGas': {
let transaction = getTransactionString(params.transaction);
if (transaction) { transaction = '&' + transaction; }
url += '/api?module=proxy&action=eth_estimateGas&' + transaction;
url += apiKey;
return fetchJson(url, null, getJsonResult);
return get(url);
}
case 'getLogs':
url += '/api?module=logs&action=getLogs';
@@ -227,7 +243,7 @@ export class EtherscanProvider extends BaseProvider{
if (params.filter.topics.length > 1) {
throw new Error('unsupported topic format');
}
var topic0 = params.filter.topics[0];
let topic0 = params.filter.topics[0];
if (typeof(topic0) !== 'string' || topic0.length !== 66) {
throw new Error('unsupported topic0 format');
}
@@ -240,7 +256,7 @@ export class EtherscanProvider extends BaseProvider{
url += apiKey;
var self = this;
return fetchJson(url, null, getResult).then(function(logs: Array<any>) {
return get(url, getResult).then(function(logs: Array<any>) {
var txs: { [hash: string]: string } = {};
var seq = Promise.resolve();
@@ -268,7 +284,7 @@ export class EtherscanProvider extends BaseProvider{
if (this.network.name !== 'homestead') { return Promise.resolve(0.0); }
url += '/api?module=stats&action=ethprice';
url += apiKey;
return fetchJson(url, null, getResult).then(function(result) {
return get(url, getResult).then(function(result) {
return parseFloat(result.ethusd);
});
@@ -297,6 +313,12 @@ export class EtherscanProvider extends BaseProvider{
url += '&sort=asc' + apiKey;
return fetchJson(url, null, getResult).then((result: Array<any>) => {
this.emit('debug', {
action: 'getHistory',
request: url,
response: result,
provider: this
});
var output: Array<TransactionResponse> = [];
result.forEach((tx) => {
['contractAddress', 'to'].forEach(function(key) {

View File

@@ -32,7 +32,8 @@ function checkNetworks(networks: Array<Network>): boolean {
// Matches!
if (check.name === network.name &&
check.chainId === network.chainId &&
check.ensAddress === network.ensAddress) { return; }
((check.ensAddress === network.ensAddress) ||
(check.ensAddress == null && network.ensAddress == null))) { return; }
errors.throwError(
'provider mismatch',

View File

@@ -6,11 +6,14 @@ import { BaseProvider } from './base-provider';
import { Signer } from '../abstract-signer';
import * as errors from '../errors';
import { getAddress } from '../utils/address';
import { BigNumber } from '../utils/bignumber';
import { hexlify, hexStripZeros } from '../utils/bytes';
import { getNetwork } from '../utils/networks';
import { defineReadOnly, resolveProperties, shallowCopy } from '../utils/properties';
import { checkProperties, defineReadOnly, shallowCopy } from '../utils/properties';
import { populateTransaction } from '../utils/transaction';
import { toUtf8Bytes } from '../utils/utf8';
import { fetchJson, poll } from '../utils/web';
@@ -18,9 +21,8 @@ import { fetchJson, poll } from '../utils/web';
import { Arrayish } from '../utils/bytes';
import { Network, Networkish } from '../utils/networks';
import { ConnectionInfo } from '../utils/web';
import { BlockTag, TransactionRequest, TransactionResponse } from '../providers/abstract-provider';
import * as errors from '../errors';
import { BlockTag, TransactionRequest, TransactionResponse } from '../providers/abstract-provider';
function timer(timeout: number): Promise<any> {
return new Promise(function(resolve) {
@@ -78,15 +80,6 @@ export class JsonRpcSigner extends Signer {
}
}
/* May add back in the future; for now it is considered confusing. :)
get address(): string {
if (!this._address) {
errors.throwError('no sync sync address available; use getAddress', errors.UNSUPPORTED_OPERATION, { operation: 'address' });
}
return this._address
}
*/
getAddress(): Promise<string> {
if (this._address) {
return Promise.resolve(this._address);
@@ -110,21 +103,18 @@ export class JsonRpcSigner extends Signer {
}
sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse> {
let tx: TransactionRequest = shallowCopy(transaction);
if (tx.from == null) {
tx.from = this.getAddress().then((address) => {
if (!address) { return null; }
return address.toLowerCase();
});
}
// Once populateTransaction resolves, the from address will be populated from getAddress
let from: string = null;
let getAddress = this.getAddress().then((address) => {
if (address) { from = address.toLowerCase(); }
return from;
});
if (transaction.gasLimit == null) {
tx.gasLimit = this.provider.estimateGas(tx);
}
return resolveProperties(tx).then((tx) => {
return this.provider.send('eth_sendTransaction', [ JsonRpcProvider.hexlifyTransaction(tx) ]).then((hash) => {
return populateTransaction(transaction, this.provider, getAddress).then((tx) => {
let hexTx = JsonRpcProvider.hexlifyTransaction(tx);
hexTx.from = from;
return this.provider.send('eth_sendTransaction', [ hexTx ]).then((hash) => {
return poll(() => {
return this.provider.getTransaction(hash).then((tx: TransactionResponse) => {
if (tx === null) { return undefined; }
@@ -176,6 +166,10 @@ export class JsonRpcSigner extends Signer {
}
}
const allowedTransactionKeys: { [ key: string ]: boolean } = {
chainId: true, data: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true
}
export class JsonRpcProvider extends BaseProvider {
readonly connection: ConnectionInfo;
@@ -236,14 +230,22 @@ export class JsonRpcProvider extends BaseProvider {
}
send(method: string, params: any): Promise<any> {
var request = {
let request = {
method: method,
params: params,
id: 42,
jsonrpc: "2.0"
};
return fetchJson(this.connection, JSON.stringify(request), getResult);
return fetchJson(this.connection, JSON.stringify(request), getResult).then((result) => {
this.emit('debug', {
action: 'send',
request: request,
response: result,
provider: this
});
return result;
});
}
perform(method: string, params: any): Promise<any> {
@@ -300,10 +302,10 @@ export class JsonRpcProvider extends BaseProvider {
return this.send('eth_getTransactionReceipt', [ params.transactionHash ]);
case 'call':
return this.send('eth_call', [ JsonRpcProvider.hexlifyTransaction(params.transaction), 'latest' ]);
return this.send('eth_call', [ JsonRpcProvider.hexlifyTransaction(params.transaction, { from: true }), params.blockTag ]);
case 'estimateGas':
return this.send('eth_estimateGas', [ JsonRpcProvider.hexlifyTransaction(params.transaction) ]);
return this.send('eth_estimateGas', [ JsonRpcProvider.hexlifyTransaction(params.transaction, { from: true }) ]);
case 'getLogs':
if (params.filter && params.filter.address != null) {
@@ -333,6 +335,7 @@ export class JsonRpcProvider extends BaseProvider {
var seq = Promise.resolve();
hashes.forEach(function(hash) {
// @TODO: This should be garbage collected at some point... How? When?
self._emitted['t:' + hash.toLowerCase()] = 'pending';
seq = seq.then(function() {
return self.getTransaction(hash).then(function(tx) {
@@ -369,11 +372,22 @@ export class JsonRpcProvider extends BaseProvider {
// - gasLimit => gas
// - All values hexlified
// - All numeric values zero-striped
// @TODO: Not any, a dictionary of string to strings
static hexlifyTransaction(transaction: TransactionRequest): any {
var result: any = {};
// NOTE: This allows a TransactionRequest, but all values should be resolved
// before this is called
static hexlifyTransaction(transaction: TransactionRequest, allowExtra?: { [key: string]: boolean }): { [key: string]: string } {
// Some nodes (INFURA ropsten; INFURA mainnet is fine) don't like extra zeros.
// Check only allowed properties are given
let allowed = shallowCopy(allowedTransactionKeys);
if (allowExtra) {
for (let key in allowExtra) {
if (allowExtra[key]) { allowed[key] = true; }
}
}
checkProperties(transaction, allowed);
let result: { [key: string]: string } = {};
// Some nodes (INFURA ropsten; INFURA mainnet is fine) don't like leading zeros.
['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(function(key) {
if ((<any>transaction)[key] == null) { return; }
let value = hexStripZeros(hexlify((<any>transaction)[key]));
@@ -384,7 +398,7 @@ export class JsonRpcProvider extends BaseProvider {
['from', 'to', 'data'].forEach(function(key) {
if ((<any>transaction)[key] == null) { return; }
result[key] = hexlify((<any>transaction)[key]);
});
});
return result;
}

View File

@@ -844,7 +844,7 @@ class CoderArray extends Coder {
result = uint256Coder.encode(count);
}
errors.checkArgumentCount(count, value.length, 'in coder array' + (this.localName? (" "+ this.localName): ""));
errors.checkArgumentCount(count, value.length, ' in coder array' + (this.localName? (" "+ this.localName): ""));
var coders = [];
for (var i = 0; i < value.length; i++) { coders.push(this.coder); }

View File

@@ -19,7 +19,7 @@ import { toUtf8Bytes, UnicodeNormalizationForm } from './utf8';
import { pbkdf2 } from './pbkdf2';
import { computeHmac, SupportedAlgorithms } from './hmac';
import { defineReadOnly, isType, setType } from './properties';
import { KeyPair } from './secp256k1';
import { computeAddress, KeyPair } from './secp256k1';
import { sha256 } from './sha2';
const N = bigNumberify("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141");
@@ -54,6 +54,8 @@ export class HDNode {
readonly privateKey: string;
readonly publicKey: string;
readonly address: string;
readonly mnemonic: string;
readonly path: string;
@@ -81,6 +83,8 @@ export class HDNode {
defineReadOnly(this, 'privateKey', this.keyPair.privateKey);
defineReadOnly(this, 'publicKey', this.keyPair.compressedPublicKey);
defineReadOnly(this, 'address', computeAddress(this.publicKey));
defineReadOnly(this, 'chainCode', hexlify(chainCode));
defineReadOnly(this, 'index', index);
@@ -197,6 +201,8 @@ export function mnemonicToSeed(mnemonic: string, password?: string): string {
export function mnemonicToEntropy(mnemonic: string, wordlist?: Wordlist): string {
if (!wordlist) { wordlist = langEn; }
errors.checkNormalize();
var words = wordlist.split(mnemonic);
if ((words.length % 3) !== 0) { throw new Error('invalid mnemonic'); }

View File

@@ -14,10 +14,11 @@ import { sha256 } from './sha2';
import { keccak256 as solidityKeccak256, pack as solidityPack, sha256 as soliditySha256 } from './solidity';
import { randomBytes } from './random-bytes';
import { getNetwork } from './networks';
import { deepCopy, defineReadOnly, resolveProperties, shallowCopy } from './properties';
import { checkProperties, deepCopy, defineReadOnly, resolveProperties, shallowCopy } from './properties';
import * as RLP from './rlp';
import { computeAddress, computePublicKey, recoverAddress, recoverPublicKey, verifyMessage } from './secp256k1';
import { SigningKey } from './signing-key';
import { populateTransaction } from './transaction';
import { parse as parseTransaction, serialize as serializeTransaction } from './transaction';
import { formatBytes32String, parseBytes32String, toUtf8Bytes, toUtf8String } from './utf8';
import { commify, formatEther, parseEther, formatUnits, parseUnits } from './units';
@@ -60,6 +61,7 @@ export {
fetchJson,
getNetwork,
checkProperties,
deepCopy,
defineReadOnly,
resolveProperties,
@@ -122,6 +124,7 @@ export {
joinSignature,
parseTransaction,
populateTransaction,
serializeTransaction,
getJsonWalletAddress,

View File

@@ -103,7 +103,7 @@ class _DeployDescription extends Description implements DeployDescription {
});
}
errors.checkArgumentCount(params.length, this.inputs.length, 'in Interface constructor');
errors.checkArgumentCount(params.length, this.inputs.length, ' in Interface constructor');
try {
return (bytecode + defaultAbiCoder.encode(this.inputs, params).substring(2));
@@ -132,7 +132,7 @@ class _FunctionDescription extends Description implements FunctionDescription {
readonly gas: BigNumber;
encode(params: Array<any>): string {
errors.checkArgumentCount(params.length, this.inputs.length, 'in interface function ' + this.name);
errors.checkArgumentCount(params.length, this.inputs.length, ' in interface function ' + this.name);
try {
return this.sighash + defaultAbiCoder.encode(this.inputs, params).substring(2);
@@ -182,19 +182,21 @@ class _EventDescription extends Description implements EventDescription {
let topics: Array<string> = [];
if (!this.anonymous) { topics.push(this.topic); }
params.forEach((arg, index) => {
if (arg === null) {
topics.push(null);
return;
}
let param = this.inputs[index];
if (!param.indexed) {
errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
if (arg != null) {
errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
}
return;
}
if (param.type === 'string') {
if (arg == null) {
topics.push(null);
} else if (param.type === 'string') {
topics.push(id(arg));
} else if (param.type === 'bytes') {
topics.push(keccak256(arg));

View File

@@ -7,33 +7,69 @@ export type Network = {
name: string,
chainId: number,
ensAddress?: string,
_defaultProvider?: (providers: any) => any
}
export type Networkish = Network | string | number;
function ethDefaultProvider(network: string): (providers: any) => any {
return function(providers: any): any {
let providerList: Array<any> = [];
if (providers.InfuraProvider) {
providerList.push(new providers.InfuraProvider(network));
}
if (providers.EtherscanProvider) {
providerList.push(new providers.EtherscanProvider(network));
}
if (providerList.length === 0) { return null; }
if (providers.FallbackProvider) {
return new providers.FallbackProvider(providerList);;
}
return providerList[0];
}
}
function etcDefaultProvider(url: string, network: string): (providers: any) => any {
return function(providers: any): any {
if (providers.JsonRpcProvider) {
return new providers.JsonRpcProvider(url, network);
}
return null;
}
}
const homestead: Network = {
chainId: 1,
ensAddress: "0x314159265dd8dbb310642f98f50c066173c1259b",
name: "homestead"
name: "homestead",
_defaultProvider: ethDefaultProvider('homestead')
};
const ropsten: Network = {
chainId: 3,
ensAddress: "0x112234455c3a32fd11230c42e7bccd4a84e02010",
name: "ropsten"
name: "ropsten",
_defaultProvider: ethDefaultProvider('ropsten')
};
const networks: { [name: string]: { chainId: number, ensAddress?: string } } = {
const networks: { [name: string]: Network } = {
unspecified: {
chainId: 0
chainId: 0,
name: 'unspecified'
},
homestead: homestead,
mainnet: homestead,
morden: {
chainId: 2
chainId: 2,
name: 'morden'
},
ropsten: ropsten,
@@ -41,19 +77,27 @@ const networks: { [name: string]: { chainId: number, ensAddress?: string } } = {
rinkeby: {
chainId: 4,
ensAddress: "0xe7410170f87102DF0055eB195163A03B7F2Bff4A"
ensAddress: "0xe7410170f87102DF0055eB195163A03B7F2Bff4A",
name: 'rinkeby',
_defaultProvider: ethDefaultProvider('rinkeby')
},
kovan: {
chainId: 42
chainId: 42,
name: 'kovan',
_defaultProvider: ethDefaultProvider('kovan')
},
classic: {
chainId: 61
chainId: 61,
name: 'classic',
_defaultProvider: etcDefaultProvider('https://web3.gastracker.io', 'classic')
},
classicTestnet: {
chainId: 62
chainId: 62,
name: 'classicTestnet',
_defaultProvider: etcDefaultProvider('https://web3.gastracker.io/morden', 'classicTestnet')
}
}
@@ -64,17 +108,18 @@ const networks: { [name: string]: { chainId: number, ensAddress?: string } } = {
* and verifies a network is a valid Network..
*/
export function getNetwork(network: Networkish): Network {
// No network (null) or unspecified (chainId = 0)
if (!network) { return null; }
// No network (null)
if (network == null) { return null; }
if (typeof(network) === 'number') {
for (var name in networks) {
for (let name in networks) {
let n = networks[name];
if (n.chainId === network) {
return {
name: name,
name: n.name,
chainId: n.chainId,
ensAddress: n.ensAddress
ensAddress: (n.ensAddress || null),
_defaultProvider: (n._defaultProvider || null)
};
}
}
@@ -89,9 +134,10 @@ export function getNetwork(network: Networkish): Network {
let n = networks[network];
if (n == null) { return null; }
return {
name: network,
name: n.name,
chainId: n.chainId,
ensAddress: n.ensAddress
ensAddress: n.ensAddress,
_defaultProvider: (n._defaultProvider || null)
};
}
@@ -110,10 +156,11 @@ export function getNetwork(network: Networkish): Network {
errors.throwError('network chainId mismatch', errors.INVALID_ARGUMENT, { arg: 'network', value: network });
}
// Standard Network
// Standard Network (allow overriding the ENS address)
return {
name: network.name,
chainId: n.chainId,
ensAddress: n.ensAddress
ensAddress: (network.ensAddress || n.ensAddress || null),
_defaultProvider: (network._defaultProvider || n._defaultProvider || null)
};
}

View File

@@ -1,5 +1,7 @@
'use strict';
import * as errors from '../errors';
export function defineReadOnly(object: any, name: string, value: any): void {
Object.defineProperty(object, name, {
enumerable: true,
@@ -42,6 +44,25 @@ export function resolveProperties(object: any): Promise<any> {
});
}
export function checkProperties(object: any, properties: { [ name: string ]: boolean }): void {
if (!object || typeof(object) !== 'object') {
errors.throwError('invalid object', errors.INVALID_ARGUMENT, {
argument: 'object',
value: object
});
}
Object.keys(object).forEach((key) => {
if (!properties[key]) {
errors.throwError('invalid object key - ' + key, errors.INVALID_ARGUMENT, {
argument: 'transaction',
value: object,
key: key
});
}
});
}
export function shallowCopy(object: any): any {
let result: any = {};
for (var key in object) { result[key] = object[key]; }

View File

@@ -9,6 +9,7 @@ import { getAddress } from './address';
import { BigNumber, bigNumberify } from './bignumber';
import { arrayify, hexlify, hexZeroPad, splitSignature, stripZeros, } from './bytes';
import { keccak256 } from './keccak256';
import { checkProperties, resolveProperties, shallowCopy } from './properties';
import * as RLP from './rlp';
@@ -19,6 +20,8 @@ import * as RLP from './rlp';
import { Arrayish, Signature } from './bytes';
import { BigNumberish } from './bignumber';
import { Provider } from '../providers/abstract-provider';
///////////////////////////////
// Exported Types
@@ -65,7 +68,7 @@ function handleNumber(value: string): BigNumber {
return bigNumberify(value);
}
var transactionFields = [
const transactionFields = [
{ name: 'nonce', maxLength: 32 },
{ name: 'gasPrice', maxLength: 32 },
{ name: 'gasLimit', maxLength: 32 },
@@ -74,8 +77,14 @@ var transactionFields = [
{ name: 'data' },
];
const allowedTransactionKeys: { [ key: string ]: boolean } = {
chainId: true, data: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true
}
export function serialize(transaction: UnsignedTransaction, signature?: Arrayish | Signature): string {
var raw: Array<string | Uint8Array> = [];
checkProperties(transaction, allowedTransactionKeys);
let raw: Array<string | Uint8Array> = [];
transactionFields.forEach(function(fieldInfo) {
let value = (<any>transaction)[fieldInfo.name] || ([]);
@@ -115,7 +124,7 @@ export function serialize(transaction: UnsignedTransaction, signature?: Arrayish
let sig = splitSignature(signature);
// We pushed a chainId and null r, s on for hashing only; remove those
var v = 27 + sig.recoveryParam
let v = 27 + sig.recoveryParam
if (raw.length === 9) {
raw.pop();
raw.pop();
@@ -171,7 +180,7 @@ export function parse(rawTransaction: Arrayish): Transaction {
tx.chainId = Math.floor((tx.v - 35) / 2);
if (tx.chainId < 0) { tx.chainId = 0; }
var recoveryParam = tx.v - 27;
let recoveryParam = tx.v - 27;
let raw = transaction.slice(0, 6);
@@ -182,7 +191,7 @@ export function parse(rawTransaction: Arrayish): Transaction {
recoveryParam -= tx.chainId * 2 + 8;
}
var digest = keccak256(RLP.encode(raw));
let digest = keccak256(RLP.encode(raw));
try {
tx.from = recoverAddress(digest, { r: hexlify(tx.r), s: hexlify(tx.s), recoveryParam: recoveryParam });
} catch (error) {
@@ -194,3 +203,41 @@ export function parse(rawTransaction: Arrayish): Transaction {
return tx;
}
export function populateTransaction(transaction: any, provider: Provider, from: string | Promise<string>): Promise<Transaction> {
if (!Provider.isProvider(provider)) {
errors.throwError('missing provider', errors.INVALID_ARGUMENT, {
argument: 'provider',
value: provider
});
}
checkProperties(transaction, allowedTransactionKeys);
let tx = shallowCopy(transaction);
if (tx.to != null) {
tx.to = provider.resolveName(tx.to);
}
if (tx.gasPrice == null) {
tx.gasPrice = provider.getGasPrice();
}
if (tx.nonce == null) {
tx.nonce = provider.getTransactionCount(from);
}
if (tx.gasLimit == null) {
let estimate = shallowCopy(tx);
estimate.from = from;
tx.gasLimit = provider.estimateGas(estimate);
}
if (tx.chainId == null) {
tx.chainId = provider.getNetwork().then((network) => network.chainId);
}
return resolveProperties(tx);
}

View File

@@ -1,7 +1,7 @@
'use strict';
import { HashZero } from '../constants';
import { checkNormalize } from '../errors';
import { arrayify, concat, hexlify } from './bytes';
///////////////////////////////
@@ -23,6 +23,7 @@ export enum UnicodeNormalizationForm {
export function toUtf8Bytes(str: string, form: UnicodeNormalizationForm = UnicodeNormalizationForm.current): Uint8Array {
if (form != UnicodeNormalizationForm.current) {
checkNormalize();
str = str.normalize(form);
}

View File

@@ -3,6 +3,7 @@
import { XMLHttpRequest } from 'xmlhttprequest';
import { encode as base64Encode } from './base64';
import { shallowCopy } from './properties';
import { toUtf8Bytes } from './utf8';
import * as errors from '../errors';
@@ -180,6 +181,7 @@ export function fetchJson(connection: string | ConnectionInfo, json: string, pro
export function poll(func: () => Promise<any>, options?: PollOptions): Promise<any> {
if (!options) { options = {}; }
options = shallowCopy(options);
if (options.floor == null) { options.floor = 0; }
if (options.ceiling == null) { options.ceiling = 10000; }
if (options.interval == null) { options.interval = 250; }

View File

@@ -10,7 +10,7 @@ import { defineReadOnly, resolveProperties, shallowCopy } from './utils/properti
import { randomBytes } from './utils/random-bytes';
import * as secretStorage from './utils/secret-storage';
import { SigningKey } from './utils/signing-key';
import { serialize as serializeTransaction } from './utils/transaction';
import { populateTransaction, serialize as serializeTransaction } from './utils/transaction';
import { Wordlist } from './utils/wordlist';
// Imported Abstracts
@@ -24,7 +24,6 @@ import { BlockTag, TransactionRequest, TransactionResponse } from './providers/a
import * as errors from './errors';
export class Wallet extends AbstractSigner {
readonly provider: Provider;
@@ -72,7 +71,7 @@ export class Wallet extends AbstractSigner {
return resolveProperties(transaction).then((tx) => {
let rawTx = serializeTransaction(tx);
let signature = this.signingKey.signDigest(keccak256(rawTx));
return Promise.resolve(serializeTransaction(tx, signature));
return serializeTransaction(tx, signature);
});
}
@@ -92,36 +91,11 @@ export class Wallet extends AbstractSigner {
}
sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse> {
if (!this.provider) { throw new Error('missing provider'); }
if (!transaction || typeof(transaction) !== 'object') {
throw new Error('invalid transaction object');
}
var tx = shallowCopy(transaction);
if (tx.to != null) {
tx.to = this.provider.resolveName(tx.to);
}
if (tx.gasLimit == null) {
tx.from = this.getAddress();
tx.gasLimit = this.provider.estimateGas(tx);
}
if (tx.gasPrice == null) {
tx.gasPrice = this.provider.getGasPrice();
}
if (tx.nonce == null) {
tx.nonce = this.getTransactionCount();
}
if (tx.chainId == null) {
tx.chainId = this.provider.getNetwork().then((network) => network.chainId);
}
return this.provider.sendTransaction(this.sign(tx));
return populateTransaction(transaction, this.provider, this.address).then((tx) => {
return this.sign(tx).then((signedTransaction) => {
return this.provider.sendTransaction(signedTransaction);
});
});
}
encrypt(password: Arrayish | string, options?: any, progressCallback?: ProgressCallback): Promise<string> {
@@ -165,7 +139,7 @@ export class Wallet extends AbstractSigner {
return Wallet.fromMnemonic(mnemonic, options.path, options.locale);
}
static fromEncryptedJson(json: string, password: Arrayish, progressCallback: ProgressCallback): Promise<Wallet> {
static fromEncryptedJson(json: string, password: Arrayish, progressCallback?: ProgressCallback): Promise<Wallet> {
if (isCrowdsaleWallet(json)) {
try {
if (progressCallback) { progressCallback(0); }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -132,9 +132,7 @@ class LangJa extends Wordlist {
}
split(mnemonic: string): Array<string> {
if (!mnemonic.normalize) {
errors.throwError('Japanese is unsupported on this platform; missing String.prototype.normalize', errors.UNSUPPORTED_OPERATION, { operation: 'String.prototype.normalize' });
}
errors.checkNormalize();
return mnemonic.split(/(?:\u3000| )+/g);
}

55
tests/local-tests.js Normal file
View File

@@ -0,0 +1,55 @@
// These are test cases that cannot be run on Travis CI, but running them locally can
// help prevent certain bugs from getting committed.
var assert = require('assert');
var ethers = require('..');
var wallet = new ethers.Wallet("0x0123456789012345678901234567890123456789012345678901234567890123");
describe("Local JSON-RPC", function() {
// https://github.com/ethers-io/ethers.js/issues/306
it ("sends a transaction", function() {
this.timeout(10000);
var provider = new ethers.providers.JsonRpcProvider();
var signer = provider.getSigner(1);
return signer.sendTransaction({
to: wallet.address,
value: 1
}).then(function(tx) {
console.log(tx);
return tx.wait().then(() => {
console.log("Mined", provider);
});
}, function(error) {
console.log(error);
assert.ok(false, "throws an error");
});
});
it("sends a wallet transactin", function() {
this.timeout(10000);
var provider = new ethers.providers.JsonRpcProvider();
var signer = wallet.connect(provider);
return signer.sendTransaction({
to: provider.getSigner(1).getAddress(),
value: 2
}).then(function(tx) {
console.log(tx);
return tx.wait().then(() => {
console.log("Mined");
});
}, function(error) {
console.log(error);
assert.ok(false, "throws an error");
});
});
});

99
tests/shims/base64.js Normal file
View File

@@ -0,0 +1,99 @@
/**
* See: https://github.com/MaxArt2501/base64-js
* The MIT License (MIT)
*
* Copyright (c) 2014 MaxArt2501
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function() {factory(root);});
} else factory(root);
// node.js has always supported base64 conversions, while browsers that support
// web workers support base64 too, but you may never know.
})(typeof exports !== "undefined" ? exports : this, function(root) {
if (root.atob) {
// Some browsers' implementation of atob doesn't support whitespaces
// in the encoded string (notably, IE). This wraps the native atob
// in a function that strips the whitespaces.
// The original function can be retrieved in atob.original
try {
root.atob(" ");
} catch(e) {
root.atob = (function(atob) {
var func = function(string) {
return atob(String(string).replace(/[\t\n\f\r ]+/g, ""));
};
func.original = atob;
return func;
})(root.atob);
}
return;
}
// base64 character set, plus padding character (=)
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
// Regular expression to check formal correctness of base64 encoded strings
b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
root.btoa = function(string) {
string = String(string);
var bitmap, a, b, c,
result = "", i = 0,
rest = string.length % 3; // To determine the final padding
for (; i < string.length;) {
if ((a = string.charCodeAt(i++)) > 255
|| (b = string.charCodeAt(i++)) > 255
|| (c = string.charCodeAt(i++)) > 255)
throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
bitmap = (a << 16) | (b << 8) | c;
result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63)
+ b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63);
}
// If there's need of padding, replace the last 'A's with equal signs
return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result;
};
root.atob = function(string) {
// atob can work with strings with whitespaces, even inside the encoded part,
// but only \t, \n, \f, \r and ' ', which can be stripped.
string = String(string).replace(/[\t\n\f\r ]+/g, "");
if (!b64re.test(string))
throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
// Adding the padding if missing, for semplicity
string += "==".slice(2 - (string.length & 3));
var bitmap, result = "", r1, r2, i = 0;
for (; i < string.length;) {
bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12
| (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));
result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255)
: r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255)
: String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);
}
return result;
};
});

73
tests/shims/index.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
var shims = [];
// Shim String.prototype.normalize
try {
var missing = [];
// Some platforms are missing certain normalization forms
var forms = ["NFD", "NFC", "NFKD", "NFKC"];
for (var i = 0; i < forms.length; i++) {
try {
"test".normalize(forms[i]);
} catch(error) {
missing.push(forms[i]);
}
}
if (missing.length) {
shims.push("String.prototype.normalize (missing: " + missing.join(", ") + ")");
throw new Error('bad normalize');
}
// Some platforms have a native normalize, but it is broken; so we force our shim
if (String.fromCharCode(0xe9).normalize('NFD') !== String.fromCharCode(0x65, 0x0301)) {
shims.push("String.prototype.normalize (broken)");
throw new Error('bad normalize');
}
} catch (error) {
var unorm = require('./unorm.js');
String.prototype.normalize = function(form) {
var func = unorm[(form || 'NFC').toLowerCase()];
if (!func) { throw new RangeError('invalid form - ' + form); }
return func(this);
}
}
// Shim atob and btoa
var base64 = require('./base64.js');
if (!global.atob) {
shims.push("atob");
global.atob = base64.atob;
}
if (!global.btoa) {
shims.push("btoa");
global.btoa = base64.btoa;
}
// Shim Promise
// @TODO: Check first?
var promise = require('./es6-promise.auto.js');
// Shim ArrayBuffer.isView
if (!ArrayBuffer.isView) {
shims.push("ArrayBuffer.isView");
ArrayBuffer.isView = function(obj) {
// @TODO: This should probably check various instanceof aswell
return !!(obj.buffer);
}
}
// Shim nextTick
if (!global.nextTick) {
shims.push("nextTick");
global.nextTick = function (callback) { setTimeout(callback, 0); }
}
if (shims.length) {
console.log("Shims Injected:");
for (var i = 0; i < shims.length; i++) {
console.log(' - ' + shims[i]);
}
}

View File

@@ -441,3 +441,72 @@ describe('Test Fixed Bytes Coder', function() {
});
});
});
describe('Test Filters', function() {
// @TODO: Add a LOT more tests here
function doTest(test) {
it(test.name, function() {
var iface = new ethers.utils.Interface([ test.signature ]);
var eventDescription = iface.events[test.event];
var filter = eventDescription.encodeTopics(test.args);
assert.equal(filter.length, test.expected.length, 'filter length matches - ' + test.name);
filter.forEach(function(expected, index) {
assert.equal(expected, test.expected[index], 'signature topic matches - ' + index + ' - ' + test.name);
});
});
}
var Tests = [
// Skips null in non-indexed fields
// See: https://github.com/ethers-io/ethers.js/issues/305
{
name: "creates correct filters for null non-indexed fields",
args: [ null, 2, null, null ],
event: "LogSomething",
signature: "event LogSomething(int hup, int indexed two, bool three, address indexed four)",
expected: [
"0xf6b983969813047dce97b9ff8a48cfb0a13306eb2caae2ef186b280bc27491c8",
"0x0000000000000000000000000000000000000000000000000000000000000002"
]
},
// https://etherscan.io/tx/0x820cc57bc77be44d8f4f024a18e18f64a8b6e62a82a3d7897db5970dbe181ba1
{
name: "transfer filtering from",
args: [
"0x59DEa134510ebce4a0c7146595dc8A61Eb9D0D79"
],
event: "Transfer",
signature: "event Transfer(address indexed from, address indexed to, uint value)",
expected: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x00000000000000000000000059dea134510ebce4a0c7146595dc8a61eb9d0d79"
]
},
{
name: "transfer filtering to",
args: [
null,
"0x851b9167B7cbf772D38eFaf89705b35022880A07"
],
event: "Transfer",
signature: "event Transfer(address indexed from, address indexed to, uint value)",
expected: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
null,
"0x000000000000000000000000851b9167b7cbf772d38efaf89705b35022880a07"
]
}
];
Tests.forEach(function(test) {
doTest(test);
});
});

View File

@@ -22,6 +22,8 @@ describe('Test HD Node Derivation', function(test) {
var wallet = new ethers.Wallet(node.privateKey);
assert.equal(wallet.address.toLowerCase(), nodeTest.address,
'Generates address - ' + nodeTest.privateKey);
assert.equal(node.address, (new ethers.Wallet(node)).address, 'HDNode address matches - ' + nodeTest.privateKey);
});
});
});
@@ -64,6 +66,8 @@ function testEasySeed(lang, locale) {
}
testEasySeed(ethers.wordlists.en, 'en');
testEasySeed(ethers.wordlists.es, 'es');
testEasySeed(ethers.wordlists.fr, 'fr');
testEasySeed(ethers.wordlists.ja, 'ja');
testEasySeed(ethers.wordlists.zh_cn, 'zh_cn');
testEasySeed(ethers.wordlists.zh_tw, 'zh_tw');

View File

@@ -11,37 +11,7 @@
<body>
<div id="mocha"></div>
<script src="../node_modules/mocha/mocha.js"></script>
<!--
Shim for PhantomJS: Promise
See: https://github.com/stefanpenner/es6-promise
-->
<script src="./dist/es6-promise.auto.js"></script>
<!--
Shim for String.prototype.normalize
See: https://github.com/walling/unorm
-->
<script src="./dist/unorm.js"></script>
<!--
Shims for PhantomJS
-->
<script type="text/javascript">
// ArrayBuffer.isView
if (!ArrayBuffer.isView) {
ArrayBuffer.isView = function(obj) {
// @TODO: This should probably check various instanceof aswell
return !!(obj.buffer);
}
}
// nextTick
if (!window.nextTick) {
window.nextTick = function (callback) { setTimeout(callback, 0); }
}
</script>
<script src="../dist/shims.js"></script>
<!-- Inject the mocha describe and it functions -->
<script type="text/javascript">

View File

@@ -733,7 +733,7 @@ var CoderArray = /** @class */ (function (_super) {
count = value.length;
result = uint256Coder.encode(count);
}
errors.checkArgumentCount(count, value.length, 'in coder array' + (this.localName ? (" " + this.localName) : ""));
errors.checkArgumentCount(count, value.length, ' in coder array' + (this.localName ? (" " + this.localName) : ""));
var coders = [];
for (var i = 0; i < value.length; i++) {
coders.push(this.coder);

1
utils/hdnode.d.ts vendored
View File

@@ -5,6 +5,7 @@ export declare class HDNode {
private readonly keyPair;
readonly privateKey: string;
readonly publicKey: string;
readonly address: string;
readonly mnemonic: string;
readonly path: string;
readonly chainCode: string;

View File

@@ -54,6 +54,7 @@ var HDNode = /** @class */ (function () {
properties_1.defineReadOnly(this, 'keyPair', new secp256k1_1.KeyPair(privateKey));
properties_1.defineReadOnly(this, 'privateKey', this.keyPair.privateKey);
properties_1.defineReadOnly(this, 'publicKey', this.keyPair.compressedPublicKey);
properties_1.defineReadOnly(this, 'address', secp256k1_1.computeAddress(this.publicKey));
properties_1.defineReadOnly(this, 'chainCode', bytes_1.hexlify(chainCode));
properties_1.defineReadOnly(this, 'index', index);
properties_1.defineReadOnly(this, 'depth', depth);
@@ -165,6 +166,7 @@ function mnemonicToEntropy(mnemonic, wordlist) {
if (!wordlist) {
wordlist = lang_en_1.langEn;
}
errors.checkNormalize();
var words = wordlist.split(mnemonic);
if ((words.length % 3) !== 0) {
throw new Error('invalid mnemonic');

5
utils/index.d.ts vendored
View File

@@ -12,10 +12,11 @@ import { sha256 } from './sha2';
import { keccak256 as solidityKeccak256, pack as solidityPack, sha256 as soliditySha256 } from './solidity';
import { randomBytes } from './random-bytes';
import { getNetwork } from './networks';
import { deepCopy, defineReadOnly, resolveProperties, shallowCopy } from './properties';
import { checkProperties, deepCopy, defineReadOnly, resolveProperties, shallowCopy } from './properties';
import * as RLP from './rlp';
import { computeAddress, computePublicKey, recoverAddress, recoverPublicKey, verifyMessage } from './secp256k1';
import { SigningKey } from './signing-key';
import { populateTransaction } from './transaction';
import { parse as parseTransaction, serialize as serializeTransaction } from './transaction';
import { formatBytes32String, parseBytes32String, toUtf8Bytes, toUtf8String } from './utf8';
import { commify, formatEther, parseEther, formatUnits, parseUnits } from './units';
@@ -31,4 +32,4 @@ import { Transaction, UnsignedTransaction } from './transaction';
import { ConnectionInfo, OnceBlockable, PollOptions } from './web';
import { EncryptOptions, ProgressCallback } from './secret-storage';
import { Wordlist } from './wordlist';
export { AbiCoder, defaultAbiCoder, formatSignature, formatParamType, parseSignature, parseParamType, RLP, fetchJson, getNetwork, deepCopy, defineReadOnly, resolveProperties, shallowCopy, arrayify, concat, padZeros, stripZeros, HDNode, SigningKey, Interface, base64, BigNumber, bigNumberify, hexlify, hexStripZeros, hexZeroPad, hexDataLength, hexDataSlice, toUtf8Bytes, toUtf8String, formatBytes32String, parseBytes32String, hashMessage, namehash, id, getAddress, getIcapAddress, getContractAddress, formatEther, parseEther, formatUnits, parseUnits, commify, keccak256, sha256, randomBytes, solidityPack, solidityKeccak256, soliditySha256, splitSignature, joinSignature, parseTransaction, serializeTransaction, getJsonWalletAddress, computeAddress, computePublicKey, recoverAddress, recoverPublicKey, verifyMessage, SupportedAlgorithms, UnicodeNormalizationForm, CoerceFunc, EventFragment, FunctionFragment, ParamType, BigNumberish, Arrayish, Hexable, Signature, Indexed, DeployDescription, EventDescription, FunctionDescription, LogDescription, TransactionDescription, Network, Networkish, Transaction, UnsignedTransaction, ConnectionInfo, OnceBlockable, PollOptions, EncryptOptions, ProgressCallback, Wordlist, };
export { AbiCoder, defaultAbiCoder, formatSignature, formatParamType, parseSignature, parseParamType, RLP, fetchJson, getNetwork, checkProperties, deepCopy, defineReadOnly, resolveProperties, shallowCopy, arrayify, concat, padZeros, stripZeros, HDNode, SigningKey, Interface, base64, BigNumber, bigNumberify, hexlify, hexStripZeros, hexZeroPad, hexDataLength, hexDataSlice, toUtf8Bytes, toUtf8String, formatBytes32String, parseBytes32String, hashMessage, namehash, id, getAddress, getIcapAddress, getContractAddress, formatEther, parseEther, formatUnits, parseUnits, commify, keccak256, sha256, randomBytes, solidityPack, solidityKeccak256, soliditySha256, splitSignature, joinSignature, parseTransaction, populateTransaction, serializeTransaction, getJsonWalletAddress, computeAddress, computePublicKey, recoverAddress, recoverPublicKey, verifyMessage, SupportedAlgorithms, UnicodeNormalizationForm, CoerceFunc, EventFragment, FunctionFragment, ParamType, BigNumberish, Arrayish, Hexable, Signature, Indexed, DeployDescription, EventDescription, FunctionDescription, LogDescription, TransactionDescription, Network, Networkish, Transaction, UnsignedTransaction, ConnectionInfo, OnceBlockable, PollOptions, EncryptOptions, ProgressCallback, Wordlist, };

View File

@@ -58,6 +58,7 @@ exports.randomBytes = random_bytes_1.randomBytes;
var networks_1 = require("./networks");
exports.getNetwork = networks_1.getNetwork;
var properties_1 = require("./properties");
exports.checkProperties = properties_1.checkProperties;
exports.deepCopy = properties_1.deepCopy;
exports.defineReadOnly = properties_1.defineReadOnly;
exports.resolveProperties = properties_1.resolveProperties;
@@ -73,8 +74,10 @@ exports.verifyMessage = secp256k1_1.verifyMessage;
var signing_key_1 = require("./signing-key");
exports.SigningKey = signing_key_1.SigningKey;
var transaction_1 = require("./transaction");
exports.parseTransaction = transaction_1.parse;
exports.serializeTransaction = transaction_1.serialize;
exports.populateTransaction = transaction_1.populateTransaction;
var transaction_2 = require("./transaction");
exports.parseTransaction = transaction_2.parse;
exports.serializeTransaction = transaction_2.serialize;
var utf8_1 = require("./utf8");
exports.formatBytes32String = utf8_1.formatBytes32String;
exports.parseBytes32String = utf8_1.parseBytes32String;

View File

@@ -56,7 +56,7 @@ var _DeployDescription = /** @class */ (function (_super) {
value: bytecode
});
}
errors.checkArgumentCount(params.length, this.inputs.length, 'in Interface constructor');
errors.checkArgumentCount(params.length, this.inputs.length, ' in Interface constructor');
try {
return (bytecode + abi_coder_1.defaultAbiCoder.encode(this.inputs, params).substring(2));
}
@@ -77,7 +77,7 @@ var _FunctionDescription = /** @class */ (function (_super) {
return _super !== null && _super.apply(this, arguments) || this;
}
_FunctionDescription.prototype.encode = function (params) {
errors.checkArgumentCount(params.length, this.inputs.length, 'in interface function ' + this.name);
errors.checkArgumentCount(params.length, this.inputs.length, ' in interface function ' + this.name);
try {
return this.sighash + abi_coder_1.defaultAbiCoder.encode(this.inputs, params).substring(2);
}
@@ -128,15 +128,17 @@ var _EventDescription = /** @class */ (function (_super) {
topics.push(this.topic);
}
params.forEach(function (arg, index) {
if (arg === null) {
topics.push(null);
return;
}
var param = _this.inputs[index];
if (!param.indexed) {
errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
if (arg != null) {
errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
}
return;
}
if (param.type === 'string') {
if (arg == null) {
topics.push(null);
}
else if (param.type === 'string') {
topics.push(hash_1.id(arg));
}
else if (param.type === 'bytes') {

1
utils/networks.d.ts vendored
View File

@@ -2,6 +2,7 @@ export declare type Network = {
name: string;
chainId: number;
ensAddress?: string;
_defaultProvider?: (providers: any) => any;
};
export declare type Networkish = Network | string | number;
/**

View File

@@ -8,39 +8,78 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
var errors = __importStar(require("../errors"));
function ethDefaultProvider(network) {
return function (providers) {
var providerList = [];
if (providers.InfuraProvider) {
providerList.push(new providers.InfuraProvider(network));
}
if (providers.EtherscanProvider) {
providerList.push(new providers.EtherscanProvider(network));
}
if (providerList.length === 0) {
return null;
}
if (providers.FallbackProvider) {
return new providers.FallbackProvider(providerList);
;
}
return providerList[0];
};
}
function etcDefaultProvider(url, network) {
return function (providers) {
if (providers.JsonRpcProvider) {
return new providers.JsonRpcProvider(url, network);
}
return null;
};
}
var homestead = {
chainId: 1,
ensAddress: "0x314159265dd8dbb310642f98f50c066173c1259b",
name: "homestead"
name: "homestead",
_defaultProvider: ethDefaultProvider('homestead')
};
var ropsten = {
chainId: 3,
ensAddress: "0x112234455c3a32fd11230c42e7bccd4a84e02010",
name: "ropsten"
name: "ropsten",
_defaultProvider: ethDefaultProvider('ropsten')
};
var networks = {
unspecified: {
chainId: 0
chainId: 0,
name: 'unspecified'
},
homestead: homestead,
mainnet: homestead,
morden: {
chainId: 2
chainId: 2,
name: 'morden'
},
ropsten: ropsten,
testnet: ropsten,
rinkeby: {
chainId: 4,
ensAddress: "0xe7410170f87102DF0055eB195163A03B7F2Bff4A"
ensAddress: "0xe7410170f87102DF0055eB195163A03B7F2Bff4A",
name: 'rinkeby',
_defaultProvider: ethDefaultProvider('rinkeby')
},
kovan: {
chainId: 42
chainId: 42,
name: 'kovan',
_defaultProvider: ethDefaultProvider('kovan')
},
classic: {
chainId: 61
chainId: 61,
name: 'classic',
_defaultProvider: etcDefaultProvider('https://web3.gastracker.io', 'classic')
},
classicTestnet: {
chainId: 62
chainId: 62,
name: 'classicTestnet',
_defaultProvider: etcDefaultProvider('https://web3.gastracker.io/morden', 'classicTestnet')
}
};
/**
@@ -50,18 +89,19 @@ var networks = {
* and verifies a network is a valid Network..
*/
function getNetwork(network) {
// No network (null) or unspecified (chainId = 0)
if (!network) {
// No network (null)
if (network == null) {
return null;
}
if (typeof (network) === 'number') {
for (var name in networks) {
var n_1 = networks[name];
for (var name_1 in networks) {
var n_1 = networks[name_1];
if (n_1.chainId === network) {
return {
name: name,
name: n_1.name,
chainId: n_1.chainId,
ensAddress: n_1.ensAddress
ensAddress: (n_1.ensAddress || null),
_defaultProvider: (n_1._defaultProvider || null)
};
}
}
@@ -76,9 +116,10 @@ function getNetwork(network) {
return null;
}
return {
name: network,
name: n_2.name,
chainId: n_2.chainId,
ensAddress: n_2.ensAddress
ensAddress: n_2.ensAddress,
_defaultProvider: (n_2._defaultProvider || null)
};
}
var n = networks[network.name];
@@ -93,11 +134,12 @@ function getNetwork(network) {
if (network.chainId !== 0 && network.chainId !== n.chainId) {
errors.throwError('network chainId mismatch', errors.INVALID_ARGUMENT, { arg: 'network', value: network });
}
// Standard Network
// Standard Network (allow overriding the ENS address)
return {
name: network.name,
chainId: n.chainId,
ensAddress: n.ensAddress
ensAddress: (network.ensAddress || n.ensAddress || null),
_defaultProvider: (network._defaultProvider || n._defaultProvider || null)
};
}
exports.getNetwork = getNetwork;

View File

@@ -2,6 +2,9 @@ export declare function defineReadOnly(object: any, name: string, value: any): v
export declare function setType(object: any, type: string): void;
export declare function isType(object: any, type: string): boolean;
export declare function resolveProperties(object: any): Promise<any>;
export declare function checkProperties(object: any, properties: {
[name: string]: boolean;
}): void;
export declare function shallowCopy(object: any): any;
export declare function deepCopy(object: any, frozen?: boolean): any;
export declare function inheritable(parent: any): (child: any) => void;

View File

@@ -1,5 +1,13 @@
'use strict';
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
var errors = __importStar(require("../errors"));
function defineReadOnly(object, name, value) {
Object.defineProperty(object, name, {
enumerable: true,
@@ -38,6 +46,24 @@ function resolveProperties(object) {
});
}
exports.resolveProperties = resolveProperties;
function checkProperties(object, properties) {
if (!object || typeof (object) !== 'object') {
errors.throwError('invalid object', errors.INVALID_ARGUMENT, {
argument: 'object',
value: object
});
}
Object.keys(object).forEach(function (key) {
if (!properties[key]) {
errors.throwError('invalid object key - ' + key, errors.INVALID_ARGUMENT, {
argument: 'transaction',
value: object,
key: key
});
}
});
}
exports.checkProperties = checkProperties;
function shallowCopy(object) {
var result = {};
for (var key in object) {

View File

@@ -1,6 +1,7 @@
import { BigNumber } from './bignumber';
import { Arrayish, Signature } from './bytes';
import { BigNumberish } from './bignumber';
import { Provider } from '../providers/abstract-provider';
export declare type UnsignedTransaction = {
to?: string;
nonce?: number;
@@ -26,3 +27,4 @@ export interface Transaction {
}
export declare function serialize(transaction: UnsignedTransaction, signature?: Arrayish | Signature): string;
export declare function parse(rawTransaction: Arrayish): Transaction;
export declare function populateTransaction(transaction: any, provider: Provider, from: string | Promise<string>): Promise<Transaction>;

View File

@@ -14,7 +14,9 @@ var address_1 = require("./address");
var bignumber_1 = require("./bignumber");
var bytes_1 = require("./bytes");
var keccak256_1 = require("./keccak256");
var properties_1 = require("./properties");
var RLP = __importStar(require("./rlp"));
var abstract_provider_1 = require("../providers/abstract-provider");
///////////////////////////////
function handleAddress(value) {
if (value === '0x') {
@@ -36,7 +38,11 @@ var transactionFields = [
{ name: 'value', maxLength: 32 },
{ name: 'data' },
];
var allowedTransactionKeys = {
chainId: true, data: true, gasLimit: true, gasPrice: true, nonce: true, to: true, value: true
};
function serialize(transaction, signature) {
properties_1.checkProperties(transaction, allowedTransactionKeys);
var raw = [];
transactionFields.forEach(function (fieldInfo) {
var value = transaction[fieldInfo.name] || ([]);
@@ -139,3 +145,32 @@ function parse(rawTransaction) {
return tx;
}
exports.parse = parse;
function populateTransaction(transaction, provider, from) {
if (!abstract_provider_1.Provider.isProvider(provider)) {
errors.throwError('missing provider', errors.INVALID_ARGUMENT, {
argument: 'provider',
value: provider
});
}
properties_1.checkProperties(transaction, allowedTransactionKeys);
var tx = properties_1.shallowCopy(transaction);
if (tx.to != null) {
tx.to = provider.resolveName(tx.to);
}
if (tx.gasPrice == null) {
tx.gasPrice = provider.getGasPrice();
}
if (tx.nonce == null) {
tx.nonce = provider.getTransactionCount(from);
}
if (tx.gasLimit == null) {
var estimate = properties_1.shallowCopy(tx);
estimate.from = from;
tx.gasLimit = provider.estimateGas(estimate);
}
if (tx.chainId == null) {
tx.chainId = provider.getNetwork().then(function (network) { return network.chainId; });
}
return properties_1.resolveProperties(tx);
}
exports.populateTransaction = populateTransaction;

View File

@@ -1,6 +1,7 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
var constants_1 = require("../constants");
var errors_1 = require("../errors");
var bytes_1 = require("./bytes");
///////////////////////////////
var UnicodeNormalizationForm;
@@ -16,6 +17,7 @@ var UnicodeNormalizationForm;
function toUtf8Bytes(str, form) {
if (form === void 0) { form = UnicodeNormalizationForm.current; }
if (form != UnicodeNormalizationForm.current) {
errors_1.checkNormalize();
str = str.normalize(form);
}
var result = [];

View File

@@ -9,6 +9,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
var xmlhttprequest_1 = require("xmlhttprequest");
var base64_1 = require("./base64");
var properties_1 = require("./properties");
var utf8_1 = require("./utf8");
var errors = __importStar(require("../errors"));
function fetchJson(connection, json, processFunc) {
@@ -82,6 +83,9 @@ function fetchJson(connection, json, processFunc) {
// @TODO: not any!
var error = new Error('invalid response - ' + request.status);
error.statusCode = request.status;
if (request.responseText) {
error.responseText = request.responseText;
}
reject(error);
return;
}
@@ -141,6 +145,7 @@ function poll(func, options) {
if (!options) {
options = {};
}
options = properties_1.shallowCopy(options);
if (options.floor == null) {
options.floor = 0;
}

2
wallet.d.ts vendored
View File

@@ -30,6 +30,6 @@ export declare class Wallet extends AbstractSigner {
* Static methods to create Wallet instances.
*/
static createRandom(options?: any): Wallet;
static fromEncryptedJson(json: string, password: Arrayish, progressCallback: ProgressCallback): Promise<Wallet>;
static fromEncryptedJson(json: string, password: Arrayish, progressCallback?: ProgressCallback): Promise<Wallet>;
static fromMnemonic(mnemonic: string, path?: string, wordlist?: Wordlist): Wallet;
}

View File

@@ -83,7 +83,7 @@ var Wallet = /** @class */ (function (_super) {
return properties_1.resolveProperties(transaction).then(function (tx) {
var rawTx = transaction_1.serialize(tx);
var signature = _this.signingKey.signDigest(keccak256_1.keccak256(rawTx));
return Promise.resolve(transaction_1.serialize(tx, signature));
return transaction_1.serialize(tx, signature);
});
};
Wallet.prototype.signMessage = function (message) {
@@ -102,30 +102,12 @@ var Wallet = /** @class */ (function (_super) {
return this.provider.getTransactionCount(this.address, blockTag);
};
Wallet.prototype.sendTransaction = function (transaction) {
if (!this.provider) {
throw new Error('missing provider');
}
if (!transaction || typeof (transaction) !== 'object') {
throw new Error('invalid transaction object');
}
var tx = properties_1.shallowCopy(transaction);
if (tx.to != null) {
tx.to = this.provider.resolveName(tx.to);
}
if (tx.gasLimit == null) {
tx.from = this.getAddress();
tx.gasLimit = this.provider.estimateGas(tx);
}
if (tx.gasPrice == null) {
tx.gasPrice = this.provider.getGasPrice();
}
if (tx.nonce == null) {
tx.nonce = this.getTransactionCount();
}
if (tx.chainId == null) {
tx.chainId = this.provider.getNetwork().then(function (network) { return network.chainId; });
}
return this.provider.sendTransaction(this.sign(tx));
var _this = this;
return transaction_1.populateTransaction(transaction, this.provider, this.address).then(function (tx) {
return _this.sign(tx).then(function (signedTransaction) {
return _this.provider.sendTransaction(signedTransaction);
});
});
};
Wallet.prototype.encrypt = function (password, options, progressCallback) {
if (typeof (options) === 'function' && !progressCallback) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -131,9 +131,7 @@ var LangJa = /** @class */ (function (_super) {
return wordlist.indexOf(word);
};
LangJa.prototype.split = function (mnemonic) {
if (!mnemonic.normalize) {
errors.throwError('Japanese is unsupported on this platform; missing String.prototype.normalize', errors.UNSUPPORTED_OPERATION, { operation: 'String.prototype.normalize' });
}
errors.checkNormalize();
return mnemonic.split(/(?:\u3000| )+/g);
};
LangJa.prototype.join = function (words) {