Home Reference Source

packages/oo7-parity/src/index.js

// (C) Copyright 2016-2017 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//         http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/* eslint-disable no-return-assign */
/* eslint-disable no-proto */

// TODO [Document auxilary types]

const oo7 = require('oo7');
const ParityApi = require('@parity/api');

const {
	asciiToHex,
	bytesToHex,
	hexToAscii,
	isAddressValid,
	toChecksumAddress,
	sha3,
	capitalizeFirstLetter,
	singleton,
	denominations,
	denominationMultiplier,
	interpretRender,
	combineValue,
	defDenom,
	formatValue,
	formatValueNoDenom,
	formatToExponential,
	interpretQuantity,
	splitValue,
	formatBalance,
	formatBlockNumber,
	isNullData,
	splitSignature,
	removeSigningPrefix,
	cleanup
} = require('./utils');

const {
	abiPolyfill,
	RegistryABI,
	RegistryExtras,
	GitHubHintABI,
	OperationsABI,
	BadgeRegABI,
	TokenRegABI,
	BadgeABI,
	TokenABI
} = require('./abis');

function defaultProvider () {
	if (typeof window !== 'undefined' && window.ethereum) {
		return window.ethereum;
	}

	try {
		if (typeof window !== 'undefined' && window.parent && window.parent.ethereum) {
			return window.parent.ethereum;
		}
	} catch (e) {}

	return new ParityApi.Provider.Http('http://localhost:8545');
}

class Bonds {
	/**
	 * Creates a new oo7-parity bonds aggregate object with given ethereum provider.
	 *
	 * Additional documentation can be found at https://wiki.parity.io/oo7-Parity-Reference.html
	 *
	 * @param {?Provider} provider Web3-compatible transport Provider (i.e. `window.ethereum`). Uses a sane default if not provided.
	 * @returns {Bonds}
	 */
	constructor (provider = defaultProvider()) {
		if (!this) {
			return createBonds({ api: new ParityApi(provider) });
		}

		/**
		 *
		 * A {@link Bond} representing latest time. Updated every second.
		 *
		 * @type {TimeBond}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.time
		 *	.tie(console.log) // prints time periodically
		 */
		this.time = null;

		/**
		 * A {@link Bond} representing latest block number.
		 * Alias for {@link Bonds.blockNumber}
		 *
		 * @type {Bond.<Number>}
		 */
		this.height = null;

		/**
		 * A {@link Bond} representing latest block number.
		 *
		 * @type {Bond.<Number>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.blockNumber
		 *	.tie(console.log) // prints latest block number when it changes
		 */
		this.blockNumber = null;

		/**
		 * A function returning bond that represents given block content.
		 *
		 * @param {string|number|Bond} number block number
		 * @returns {Bond.<Block>} block bond
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.blockByNumber(bonds.height)
		 *	.tie(console.log) // prints latest block
		 */
		this.blockByNumber = null;

		/**
		 * A function returning bond that represents given block content.
		 *
		 * @param {string|number|Bond} hash block hash
		 * @returns {Bond.<Block>} block bond
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.blockByHash('0x2b23d04567313fa141ca396f1e2620b62ab0c5d69f8c77157118f8d7671e1f4d')
		 *	.tie(console.log) // prints block with given hash
		 */
		this.blockByHash = null;

		/**
		 * Similar to {@link Bonds.blockByNumber} and {@link Bonds.blockByHash},
		 * but accepts both hashes and numbers as arguments.
		 *
		 * @param {string|number|Bond} hashOrNumber block hash or block number
		 * @returns {Bond.<Block>} block bond
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.findBlock('0x2b23d04567313fa141ca396f1e2620b62ab0c5d69f8c77157118f8d7671e1f4d')
		 *	.tie(console.log) // prints block with given hash
		 */
		this.findBlock = null;

		/**
		 * A subscriptable version of {@link Bonds.findBlock}
		 *
		 * You can retrieve bonds given block numbers or hashes or other Bonds.
		 *
		 * @type {Object.<string|number|Bond, Bond>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.blocks['0x2b23d04567313fa141ca396f1e2620b62ab0c5d69f8c77157118f8d7671e1f4d']
		 *	.tie(console.log) // prints block with given hash
		 *
		 * bonds
		 *	.blocks[bonds.height]
		 *	.tie(console.log) // prints latest block every time it changes
		 */
		this.blocks = null;

		/**
		 * A {@link Bond} for latest block.
		 *
		 * @type {Bond.<Block>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.head
		 *	.tie(console.log) // prints latest block every time it changes
		 *
		 */
		this.head = null;

		/**
		 * A {@link Bond} for currently set block author.
		 * Represents a result of `eth_coinbase` RPC call.
		 *
		 * @type {Bond.<Address>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.author
		 *	.tie(console.log) // prints currently set block author (coinbase/miner) every time it changes
		 *
		 */
		this.author = null;

		/**
		 * List of accounts managed by the node.
		 *
		 * @type {Bond.<Address[]>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.accounts
		 *	.tie(console.log) // prints accounts list every time it changes
		 *
		 */
		this.accounts = null;

		/**
		 * User-selected default account for this dapp.
		 *
		 * @type {Bond.<Address>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.defaultAccount
		 *	.tie(console.log) // prints default account every time it changes
		 *
		 */
		this.defaultAccount = null;

		/**
		 * Alias for {@link Bonds.defaultAccount}
		 *
		 * @type {Bond.<Address>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.me
		 *	.tie(console.log) // prints default account every time it changes
		 *
		 */
		this.me = null;
		/**
		 * Posts a transaction to the network.
		 *
		 * @param {TransactionRequest} tx Transaction details
		 * @returns {ReactivePromise.<TransactionStatus>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.post({ to: bonds.me, value: 0  })
		 *	.tie(console.log) // Reports transaction progress
		 */
		this.post = null;
		/**
		 * Returns a signature of given message
		 *
		 * @param {Hash|Bond} hash Hash to sign
		 * @param {?Address|Bond} from Optional account that should be used for signing.
		 * @returns {ReactivePromise.<SignStatus>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.sign('0x2ea2e504d09c458dbadc703112125564d53ca03c27a5b28e7b3e2b5804289c45')
		 *	.tie(console.log) // Reports signing progress
		 */
		this.sign = null;

		/**
		 * Returns balance of given address.
		 *
		 * @param {string|Bond.<Address>} address
		 * @returns {Bond.<BigNumber>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.balance(bonds.me)
		 *	.tie(console.log) // prints default account balance every time any of them changes
		 *
		 */
		this.balance = null;

		/**
		 * Returns code of given address.
		 *
		 * @param {string|Bond.<Address>} address
		 * @returns {Bond.<Bytes>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.code(bonds.me)
		 *	.tie(console.log) // prints default account code every time any of them changes
		 *
		 */
		this.code = null;

		/**
		 * Returns the nonce of given address.
		 *
		 * @param {string|Bond.<Address>} address
		 * @returns {Bond.<BigNumber>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.nonce(bonds.me)
		 *	.tie(console.log) // prints default account nonce every time any of them changes
		 *
		 */
		this.nonce = null;

		/**
		 * Returns storage at given index of an address.
		 *
		 * @param {string|Bond.<Address>} address Contract address
		 * @param {string|number|Bond.<H256>} storageIdx Contract storage index
		 * @returns {Bond.<BigNumber>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.storageAt(bonds.me, 0)
		 *	.tie(console.log) // prints default account storage at position 0 every time any of them changes
		 *
		 */
		this.storageAt = null;

		/**
		 * Returns node's syncing status.
		 * If the node is fully synced this will return `false`.
		 *
		 * @type {Bond.<bool>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.syncing
		 *	.tie(console.log) // prints sync status every time it changes
		 *
		 */
		this.syncing = null;
		/**
		 * Returns node's authoring status.
		 * If the node is not authoring blocks this will return `false`.
		 *
		 * @type {Bond.<bool>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.authoring
		 *	.tie(console.log) // prints authoring status every time it changes
		 *
		 */
		this.authoring = null;
		/**
		 * Reported hashrate.
		 * If there is an external miner connected to the node it will return reported values.
		 *
		 * @type {Bond.<BigNumber>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.hashrate
		 *	.tie(console.log) // prints current average hashrate
		 *
		 */
		this.hashrate = null;
		this.ethProtocolVersion = null;
		/**
		 * Suggested gas price value. (Gas Price Oracle)
		 * This returns a suggested gas price for next transaction. The estimation is based on statistics from last blocks.
		 *
		 * @type {Bond.<BigNumber>}
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.gasPrice
		 *	.tie(console.log) // prints current gas price suggestion
		 *
		 */
		this.gasPrice = null;
		/**
		 * Estimates gas required to execute given transaction
		 *
		 * @param {{ from: ?Address, to: ?Address, data: ?Bytes }} call Transaction request
		 * @returns {Bond.<BigNumber>} gas estimate
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.estimateGas({ from: bonds.me, to: '0x00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED' })
		 *	.tie(console.log) // prints current gas estimate
		 *
		 */
		this.estimateGas = null;

		/**
		 * Returns block transaction count given block number or hash.
		 *
		 * @param {string|number|Bond} block block number or hash
		 * @returns {Bond.<Number>} number of transactions in block
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.blockTransactionCount(bonds.blockNumber)
		 *	.tie(console.log) // prints number of transactions in latest block
		 *
		 */
		this.blockTransactionCount = null;
		/**
		 * Returns uncle count given block number or hash.
		 *
		 * @param {string|number|Bond} block block number or hash
		 * @returns {Bond.<Number>} number of uncles in a block
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.uncleCount(bonds.blockNumber)
		 *	.tie(console.log) // prints number of uncles in latest block
		 *
		 */
		this.uncleCount = null;
		/**
		 * Returns uncle given block number or hash and uncle index
		 *
		 * @param {string|number|Bond} block block number or hash
		 * @param {string|number|Bond} index index of an uncle within a block
		 * @returns {Bond.<Header>} uncle header at that index
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.uncle(bonds.blockNumber, 0)
		 *	.tie(console.log) // prints the first uncle in latest block
		 *
		 */
		this.uncle = null;
		/**
		 * Returns transaction given block number or hash and transaction index
		 *
		 * @param {string|number|Bond} block block number or hash
		 * @param {string|number|Bond} index index of a transaction within a block
		 * @returns {Bond.<Transaction>} transaction at that index
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.transaction(bonds.blockNumber, 0)
		 *	.tie(console.log) // prints the first uncle in latest block
		 *
		 */
		this.transaction = null;
		/**
		 * Returns receipt given transaction hash.
		 *
		 * @param {string|number|Bond} hash transaction hash
		 * @returns {Bond.<TransactionReceipt>} transaction at that index
		 *
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.receipt(bonds.transaction(bonds.height, 0).map(x => x ? x.hash : undefined))
		 *	.tie(console.log) // prints receipt of first transaction in latest block
		 *
		 */
		this.receipt = null;

		/**
		 * Returns client version string. (`web3_clientVersion`).
		 *
		 * @type {Bond.<String>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.clientVersion
		 *	.tie(console.log)
		 *
		 */
		this.clientVersion = null;

		/**
		 * Returns current peer count. (`net_peerCount`).
		 *
		 * @type {Bond.<Number>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.peerCount
		 *	.tie(console.log)
		 *
		 */
		this.peerCount = null;
		/**
		 * Returns true if the node is actively listening for network connections.
		 *
		 * @type {Bond.<bool>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.listening
		 *	.tie(console.log)
		 *
		 */
		this.listening = null;
		/**
		 * Returns chain id (used for chain replay protection).
		 * NOTE: It's _not_ network id.
		 *
		 * @type {Bond.<Number>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.chainId
		 *	.tie(console.log)
		 *
		 */
		this.chainId = null;

		/**
		 * Returns a hash of content under given URL.
		 *
		 * @param {string|Bond} url URL of the content
		 * @returns {Bond.<string>} hash of the content
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.hashContent('https://google.com')
		 *	.tie(console.log)
		 *
		 */
		this.hashContent = null;
		this.gasPriceHistogram = null;
		this.accountsInfo = null;
		this.allAccountsInfo = null;
		this.hardwareAccountsInfo = null;
		this.mode = null;

		this.defaultExtraData = null;
		this.extraData = null;
		this.gasCeilTarget = null;
		this.gasFloorTarget = null;
		this.minGasPrice = null;
		this.transactionsLimit = null;
		/**
		 * Returns a string name of currently connected chain.
		 *
		 * @type {Bond.<string>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.chainName
		 *	.tie(console.log)
		 */
		this.chainName = null;
		/**
		 * Returns a status of currently connected chain.
		 *
		 * @type {Bond.<object>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.chainStatus
		 *	.tie(console.log)
		 */
		this.chainStatus = null;

		this.peers = null;
		this.enode = null;
		this.nodePort = null;
		this.nodeName = null;
		this.signerPort = null;
		this.dappsPort = null;
		this.dappsInterface = null;

		this.nextNonce = null;
		this.pending = null;
		this.local = null;
		this.future = null;
		this.pendingStats = null;
		this.unsignedCount = null;

		this.releaseInfo = null;
		this.versionInfo = null;
		this.consensusCapability = null;
		this.upgradeReady = null;

		/**
		 * Replays (re-executes) a transaction. Returns requested traces of execution.
		 *
		 * @param {string} hash Transaction hash
		 * @param {String[]} traces Any subset of `trace`,`vmTrace`,`stateDiff`.
		 * @returns {Bond.<object>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.replayTx('0x2ea2e504d09c458dbadc703112125564d53ca03c27a5b28e7b3e2b5804289c45', ['trace'])
		 *	.tie(console.log)
		 */
		this.replayTx = null;
		/**
		 * Executs a transaction and collects traces.
		 *
		 * @param {TransactionRequest} transaction Transaction request
		 * @param {String[]} traces Any subset of `trace`,`vmTrace`,`stateDiff`.
		 * @param {string|number|Bond} block Block number or hash
		 * @returns {Bond.<object>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.callTx({
		 *		from: bonds.me,
		 *		to: bonds.registry.address
		 *	}, ['trace'], 'latest')
		 *	.tie(console.log)
		 */
		this.callTx = null;

		/**
		 * Deploys a new contract
		 *
		 * @param {string|Bytes} init Initialization bytecode
		 * @param {ABI} abi Contract ABI
		 * @param {{from: ?Address, gas: ?BigNumber, gasPrice: ?BigNumber, nonce: ?BigNumber}} options Deployment options
		 * @returns {ReactivePromise.<DeployStatus>}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.deployContract('0x1234', abi, {})
		 *	.tie(console.log) // Reports deployment progress
		 */
		this.deployContract = null;
		/**
		 * Creates bond-enabled contract object for existing contract.
		 *
		 * @param {string|Bond} address Contract address
		 * @param {ABI} abi Contract ABI
		 * @param {?ABI} extras Additional methods not defined in the ABI.
		 * @returns {Contract}
		 * @example
		 * const { bonds } = require('oo7-parity')
		 *
		 * bonds
		 *	.makeContract(bonds.me, abi)
		 *	.someMethod()
		 *	.tie(console.log) // returns a result of someMethod call
		 */
		this.makeContract = null;

		/**
		 * Parity registry contract instance.
		 * @type {Contract.<Registry>}
		 */
		this.registry = null;

		/**
		 * Parity registry contract instance.
		 * @type {Contract.<GithubHint>}
		 */
		this.githubhint = null;
		/**
		 * Parity registry contract instance.
		 * @type {Contract.<Operations>}
		 */
		this.operations = null;
		/**
		 * Parity registry contract instance.
		 * @type {Contract.<BadgeReg>}
		 */
		this.badgereg = null;
		/**
		 * Parity registry contract instance.
		 * @type {Contract.<TokenReg>}
		 */
		this.tokenreg = null;

		/**
		 * A {@link Bond} representing all currently registered badges from BadgeReg.
		 *
		 * @type {Bond.<{id:string,name:string,img:string,caption:string,badge:Contract}[]>}
		 */
		this.badges = null;
		/**
		 * Returns a list of badges for given address.
		 *
		 * @param {Address} address
		 * @returns {Bond.<Badge[]>} see {@link Bonds.badges}
		 */
		this.badgesOf = null;

		/**
		 * A {@link Bond} representing all currently registered tokens from TokenReg.
		 *
		 * @type {Bond.<{id:string,tla:string,base:string,name:string,owner:address,img:string,caption:string}[]>}
		 */
		this.tokens = null;
		/**
		 * Returns a list of tokens with a non-empty balance for given address.
		 *
		 * @param {Address} address
		 * @returns {Bond.<Token[]>} see {@link Bonds.tokens}
		 */
		this.tokensOf = null;

		return this;
	}
}

function isNumber (n) {
	return typeof (n) === 'number' || (typeof (n) === 'string' && n.match(/^[0-9]+$/));
}

function memoized (f) {
	var memo;
	return function () {
		if (memo === undefined) { memo = f(); }
		return memo;
	};
}

function overlay (base, top) {
	Object.keys(top).forEach(k => {
		base[k] = top[k];
	});
	return base;
}

function transactionPromise (api, tx, progress, f) {
	progress({ initialising: null });
	let condition = tx.condition || null;
	Promise.all([api().eth.accounts(), api().eth.gasPrice()])
		.then(([a, p]) => {
			progress({ estimating: null });
			tx.from = tx.from || a[0];
			tx.gasPrice = tx.gasPrice || p;
			return tx.gas || api().eth.estimateGas(tx);
		})
		.then(g => {
			progress({ estimated: g });
			tx.gas = tx.gas || g;
			return api().parity.postTransaction(tx);
		})
		.then(signerRequestId => {
			progress({ requested: signerRequestId });
			return api().pollMethod('parity_checkRequest', signerRequestId);
		})
		.then(transactionHash => {
			if (condition) {
				progress(f({ signed: transactionHash, scheduled: condition }));
				return { signed: transactionHash, scheduled: condition };
			} else {
				progress({ signed: transactionHash });
				return api()
					.pollMethod('eth_getTransactionReceipt', transactionHash, (receipt) => receipt && receipt.blockNumber && !receipt.blockNumber.eq(0))
					.then(receipt => {
						progress(f({ confirmed: receipt }));
						return receipt;
					});
			}
		})
		.catch(error => {
			progress({ failed: error });
		});
}

class DeployContract extends oo7.ReactivePromise {
	constructor (initBond, abiBond, optionsBond, api) {
		super([initBond, abiBond, optionsBond, bonds.registry], [], ([init, abi, options, registry]) => {
			options.data = init;
			delete options.to;
			let progress = this.trigger.bind(this);
			transactionPromise(api, options, progress, status => {
				if (status.confirmed) {
					status.deployed = bonds.makeContract(status.confirmed.contractAddress, abi, options.extras || []);
				}
				return status;
			});
			// TODO: consider allowing registry of the contract here.
		}, false);
		this.then(_ => null);
	}
	isDone (s) {
		return !!(s.failed || s.confirmed);
	}
}

class Transaction extends oo7.ReactivePromise {
	constructor (tx, api) {
		super([tx], [], ([tx]) => {
			let progress = this.trigger.bind(this);
			transactionPromise(api, tx, progress, _ => _);
		}, false);
		this.then(_ => null);
	}
	isDone (s) {
		return !!(s.failed || s.confirmed);
	}
}

/**
 * @param {{api: ParityApi}} Options object
 * @returns {Bonds}
 */
function createBonds (options) {
	const bonds = new Bonds();

	// We only ever use api() at call-time of this function; this allows the
	// options (particularly the transport option) to be changed dynamically
	// and the datastructure to be reused.
	const api = () => options.api;
	const util = ParityApi.util;

	class TransformBond extends oo7.TransformBond {
		constructor (f, a = [], d = [], outResolveDepth = 0, resolveDepth = 1, latched = true, mayBeNull = true) {
			super(f, a, d, outResolveDepth, resolveDepth, latched, mayBeNull, api());
		}
		map (f, outResolveDepth = 0, resolveDepth = 1) {
			return new TransformBond(f, [this], [], outResolveDepth, resolveDepth);
		}
		sub (name, outResolveDepth = 0, resolveDepth = 1) {
			return new TransformBond((r, n) => r[n], [this, name], [], outResolveDepth, resolveDepth);
		}
		static all (list) {
			return new TransformBond((...args) => args, list);
		}
	}

	class SubscriptionBond extends oo7.Bond {
		constructor (module, rpcName, options = []) {
			super();
			this.module = module;
			this.rpcName = rpcName;
			this.options = [(_, n) => this.trigger(n), ...options];
		}
		initialise () {
			// promise instead of id because if a dependency triggers finalise() before id's promise is resolved the unsubscribing would call with undefined
			this.subscription = api().pubsub[this.module][this.rpcName](...this.options);
		}
		finalise () {
			this.subscription.then(id => api().pubsub.unsubscribe([id]));
		}
		map (f, outResolveDepth = 0, resolveDepth = 1) {
			return new TransformBond(f, [this], [], outResolveDepth, resolveDepth);
		}
		sub (name, outResolveDepth = 0, resolveDepth = 1) {
			return new TransformBond((r, n) => r[n], [this, name], [], outResolveDepth, resolveDepth);
		}
		static all (list) {
			return new TransformBond((...args) => args, list);
		}
	}

	class Signature extends oo7.ReactivePromise {
		constructor (message, from) {
			super([message, from], [], ([message, from]) => {
				api().parity.postSign(from, asciiToHex(message))
					.then(signerRequestId => {
						this.trigger({ requested: signerRequestId });
						return api().pollMethod('parity_checkRequest', signerRequestId);
					})
					.then(signature => {
						this.trigger({
							signed: splitSignature(signature)
						});
					})
					.catch(error => {
						console.error(error);
						this.trigger({ failed: error });
					});
			}, false);
			this.then(_ => null);
		}
		isDone (s) {
			return !!s.failed || !!s.signed;
		}
	}

	function call (addr, method, args, options) {
		let data = util.abiEncode(method.name, method.inputs.map(f => f.type), args);
		let decode = d => util.abiDecode(method.outputs.map(f => f.type), d);
		return api().eth.call(overlay({ to: addr, data: data }, options)).then(decode);
	}

	function post (addr, method, args, options) {
		let toOptions = (addr, method, options, ...args) => {
			return overlay({ to: addr, data: util.abiEncode(method.name, method.inputs.map(f => f.type), args) }, options);
		};
		// inResolveDepth is 2 to allow for Bonded `condition`values which are
		// object values in `options`.
		return new Transaction(new TransformBond(toOptions, [addr, method, options, ...args], [], 0, 2), api);
	}

	function presub (f) {
		return new Proxy(f, {
			get (receiver, name) {
				if (typeof (name) === 'string' || typeof (name) === 'number') {
					return typeof (receiver[name]) !== 'undefined' ? receiver[name] : receiver(name);
				} else if (typeof (name) === 'symbol' && oo7.Bond.knowSymbol(name)) {
					return receiver(oo7.Bond.fromSymbol(name));
				} else {
					throw new Error(`Weird value type to be subscripted by: ${typeof (name)}: ${JSON.stringify(name)}`);
				}
			}
		});
	}

	let useSubs = false;

	bonds.time = new oo7.TimeBond();

	if (!useSubs) {
		bonds.height = new TransformBond(() => api().eth.blockNumber().then(_ => +_), [], [bonds.time]);

		let onAccountsChanged = bonds.time; // TODO: more accurate notification
		let onHardwareAccountsChanged = bonds.time; // TODO: more accurate notification
		let onHeadChanged = bonds.height;	// TODO: more accurate notification
		//	let onReorg = undefined;	// TODO make more accurate.
		let onSyncingChanged = bonds.time;
		let onAuthoringDetailsChanged = bonds.time;
		let onPeerNetChanged = bonds.time; // TODO: more accurate notification
		let onPendingChanged = bonds.time; // TODO: more accurate notification
		let onUnsignedChanged = bonds.time; // TODO: more accurate notification
		let onAutoUpdateChanged = bonds.height;

		// eth_
		bonds.blockNumber = bonds.height;
		bonds.blockByNumber = x => new TransformBond(x => api().eth.getBlockByNumber(x), [x], []).subscriptable();// TODO: chain reorg that includes number x
		bonds.blockByHash = x => new TransformBond(x => api().eth.getBlockByHash(x), [x]).subscriptable();
		bonds.findBlock = hashOrNumberBond => new TransformBond(hashOrNumber => isNumber(hashOrNumber)
			? api().eth.getBlockByNumber(hashOrNumber)
			: api().eth.getBlockByHash(hashOrNumber),
		[hashOrNumberBond], [/* onReorg */]).subscriptable();// TODO: chain reorg that includes number x, if x is a number
		bonds.blocks = presub(bonds.findBlock);
		bonds.block = bonds.blockByNumber(bonds.height);	// TODO: DEPRECATE AND REMOVE
		bonds.head = new TransformBond(() => api().eth.getBlockByNumber('latest'), [], [onHeadChanged]).subscriptable();// TODO: chain reorgs
		bonds.author = new TransformBond(() => api().eth.coinbase(), [], [onAccountsChanged]);
		bonds.accounts = new TransformBond(a => a.map(util.toChecksumAddress), [new TransformBond(() => api().eth.accounts(), [], [onAccountsChanged])]).subscriptable();
		bonds.defaultAccount = bonds.accounts[0];	// TODO: make this use its subscription
		bonds.me = bonds.accounts[0];
		// TODO [ToDr] document (Post & Sign)
		bonds.post = tx => new Transaction(tx, api);
		bonds.sign = (message, from = bonds.me) => new Signature(message, from);

		bonds.balance = x => new TransformBond(x => api().eth.getBalance(x), [x], [onHeadChanged]);
		bonds.code = x => new TransformBond(x => api().eth.getCode(x), [x], [onHeadChanged]);
		bonds.nonce = x => new TransformBond(x => api().eth.getTransactionCount(x).then(_ => +_), [x], [onHeadChanged]);
		bonds.storageAt = (x, y) => new TransformBond((x, y) => api().eth.getStorageAt(x, y), [x, y], [onHeadChanged]);

		bonds.syncing = new TransformBond(() => api().eth.syncing(), [], [onSyncingChanged]);
		bonds.hashrate = new TransformBond(() => api().eth.hashrate(), [], [onAuthoringDetailsChanged]);
		bonds.authoring = new TransformBond(() => api().eth.mining(), [], [onAuthoringDetailsChanged]);
		bonds.ethProtocolVersion = new TransformBond(() => api().eth.protocolVersion(), [], []);
		bonds.gasPrice = new TransformBond(() => api().eth.gasPrice(), [], [onHeadChanged]);
		bonds.estimateGas = x => new TransformBond(x => api().eth.estimateGas(x), [x], [onHeadChanged, onPendingChanged]);

		bonds.blockTransactionCount = hashOrNumberBond => new TransformBond(
			hashOrNumber => isNumber(hashOrNumber)
				? api().eth.getBlockTransactionCountByNumber(hashOrNumber).then(_ => +_)
				: api().eth.getBlockTransactionCountByHash(hashOrNumber).then(_ => +_),
			[hashOrNumberBond], [/* onReorg */]);
		bonds.uncleCount = hashOrNumberBond => new TransformBond(
			hashOrNumber => isNumber(hashOrNumber)
				? api().eth.getUncleCountByBlockNumber(hashOrNumber).then(_ => +_)
				: api().eth.getUncleCountByBlockHash(hashOrNumber).then(_ => +_),
			[hashOrNumberBond], [/* onReorg */]).subscriptable();
		bonds.uncle = (hashOrNumberBond, indexBond) => new TransformBond(
			(hashOrNumber, index) => isNumber(hashOrNumber)
				? api().eth.getUncleByBlockNumber(hashOrNumber, index)
				: api().eth.getUncleByBlockHash(hashOrNumber, index),
			[hashOrNumberBond, indexBond], [/* onReorg */]).subscriptable();
		bonds.transaction = (hashOrNumberBond, indexOrNullBond) => new TransformBond(
			(hashOrNumber, indexOrNull) =>
				indexOrNull === undefined || indexOrNull === null
					? api().eth.getTransactionByHash(hashOrNumber)
					: isNumber(hashOrNumber)
						? api().eth.getTransactionByBlockNumberAndIndex(hashOrNumber, indexOrNull)
						: api().eth.getTransactionByBlockHashAndIndex(hashOrNumber, indexOrNull),
			[hashOrNumberBond, indexOrNullBond], [/* onReorg */]).subscriptable();
		bonds.receipt = hashBond => new TransformBond(x => api().eth.getTransactionReceipt(x), [hashBond], []).subscriptable();

		// web3_
		bonds.clientVersion = new TransformBond(() => api().web3.clientVersion(), [], []);

		// net_
		bonds.peerCount = new TransformBond(() => api().net.peerCount().then(_ => +_), [], [onPeerNetChanged]);
		bonds.listening = new TransformBond(() => api().net.listening(), [], [onPeerNetChanged]);
		bonds.chainId = new TransformBond(() => api().net.version(), [], []);

		// parity_
		bonds.hashContent = u => new TransformBond(x => api().parity.hashContent(x), [u], [], false);
		bonds.gasPriceHistogram = new TransformBond(() => api().parity.gasPriceHistogram(), [], [onHeadChanged]).subscriptable();
		bonds.accountsInfo = new TransformBond(() => api().parity.accountsInfo(), [], [onAccountsChanged]).subscriptable(2);
		bonds.allAccountsInfo = new TransformBond(() => api().parity.allAccountsInfo(), [], [onAccountsChanged]).subscriptable(2);
		bonds.hardwareAccountsInfo = new TransformBond(() => api().parity.hardwareAccountsInfo(), [], [onHardwareAccountsChanged]).subscriptable(2);
		bonds.mode = new TransformBond(() => api().parity.mode(), [], [bonds.height]);

		// ...authoring
		bonds.defaultExtraData = new TransformBond(() => api().parity.defaultExtraData(), [], [onAuthoringDetailsChanged]);
		bonds.extraData = new TransformBond(() => api().parity.extraData(), [], [onAuthoringDetailsChanged]);
		bonds.gasCeilTarget = new TransformBond(() => api().parity.gasCeilTarget(), [], [onAuthoringDetailsChanged]);
		bonds.gasFloorTarget = new TransformBond(() => api().parity.gasFloorTarget(), [], [onAuthoringDetailsChanged]);
		bonds.minGasPrice = new TransformBond(() => api().parity.minGasPrice(), [], [onAuthoringDetailsChanged]);
		bonds.transactionsLimit = new TransformBond(() => api().parity.transactionsLimit(), [], [onAuthoringDetailsChanged]);

		// ...chain info
		bonds.chainName = new TransformBond(() => api().parity.netChain(), [], []);
		bonds.chainStatus = new TransformBond(() => api().parity.chainStatus(), [], [onSyncingChanged]).subscriptable();

		// ...networking
		bonds.peers = new TransformBond(() => api().parity.netPeers(), [], [onPeerNetChanged]).subscriptable(2);
		bonds.enode = new TransformBond(() => api().parity.enode(), [], []);
		bonds.nodePort = new TransformBond(() => api().parity.netPort().then(_ => +_), [], []);
		bonds.nodeName = new TransformBond(() => api().parity.nodeName(), [], []);
		bonds.signerPort = new TransformBond(() => api().parity.signerPort().then(_ => +_), [], []);
		bonds.dappsPort = new TransformBond(() => api().parity.dappsPort().then(_ => +_), [], []);
		bonds.dappsInterface = new TransformBond(() => api().parity.dappsInterface(), [], []);

		// ...transaction queue
		bonds.nextNonce = new TransformBond(() => api().parity.nextNonce().then(_ => +_), [], [onPendingChanged]);
		bonds.pending = new TransformBond(() => api().parity.pendingTransactions(), [], [onPendingChanged]);
		bonds.local = new TransformBond(() => api().parity.localTransactions(), [], [onPendingChanged]).subscriptable(3);
		bonds.future = new TransformBond(() => api().parity.futureTransactions(), [], [onPendingChanged]).subscriptable(2);
		bonds.pendingStats = new TransformBond(() => api().parity.pendingTransactionsStats(), [], [onPendingChanged]).subscriptable(2);
		bonds.unsignedCount = new TransformBond(() => api().parity.parity_unsignedTransactionsCount().then(_ => +_), [], [onUnsignedChanged]);

		// ...auto-update
		bonds.releasesInfo = new TransformBond(() => api().parity.releasesInfo(), [], [onAutoUpdateChanged]).subscriptable();
		bonds.versionInfo = new TransformBond(() => api().parity.versionInfo(), [], [onAutoUpdateChanged]).subscriptable();
		bonds.consensusCapability = new TransformBond(() => api().parity.consensusCapability(), [], [onAutoUpdateChanged]);
		bonds.upgradeReady = new TransformBond(() => api().parity.upgradeReady(), [], [onAutoUpdateChanged]).subscriptable();
	} else {
		bonds.height = new TransformBond(_ => +_, [new SubscriptionBond('eth', 'blockNumber')]).subscriptable();

		let onAutoUpdateChanged = bonds.height;

		// eth_
		bonds.blockNumber = bonds.height;
		bonds.blockByNumber = numberBond => new TransformBond(number => new SubscriptionBond('eth', 'getBlockByNumber', [number]), [numberBond]).subscriptable();
		bonds.blockByHash = x => new TransformBond(x => new SubscriptionBond('eth', 'getBlockByHash', [x]), [x]).subscriptable();
		bonds.findBlock = hashOrNumberBond => new TransformBond(hashOrNumber => isNumber(hashOrNumber)
			? new SubscriptionBond('eth', 'getBlockByNumber', [hashOrNumber])
			: new SubscriptionBond('eth', 'getBlockByHash', [hashOrNumber]),
		[hashOrNumberBond]).subscriptable();
		bonds.blocks = presub(bonds.findBlock);
		bonds.block = bonds.blockByNumber(bonds.height);	// TODO: DEPRECATE AND REMOVE
		bonds.head = new SubscriptionBond('eth', 'getBlockByNumber', ['latest']).subscriptable();
		bonds.author = new SubscriptionBond('eth', 'coinbase');
		bonds.me = new SubscriptionBond('parity', 'defaultAccount');
		bonds.defaultAccount = bonds.me;	// TODO: DEPRECATE
		bonds.accounts = new SubscriptionBond('eth', 'accounts').subscriptable();
		bonds.post = tx => new Transaction(tx, api);
		bonds.sign = (message, from = bonds.me) => new Signature(message, from);

		bonds.balance = x => new TransformBond(x => new SubscriptionBond('eth', 'getBalance', [x]), [x]);
		bonds.code = x => new TransformBond(x => new SubscriptionBond('eth', 'getCode', [x]), [x]);
		bonds.nonce = x => new TransformBond(x => new SubscriptionBond('eth', 'getTransactionCount', [x]), [x]); // TODO: then(_ => +_) Depth 2 if second TransformBond or apply to result
		bonds.storageAt = (x, y) => new TransformBond((x, y) => new SubscriptionBond('eth', 'getStorageAt', [x, y]), [x, y]);

		bonds.syncing = new SubscriptionBond('eth', 'syncing');
		bonds.hashrate = new SubscriptionBond('eth', 'hashrate');
		bonds.authoring = new SubscriptionBond('eth', 'mining');
		bonds.ethProtocolVersion = new SubscriptionBond('eth', 'protocolVersion');
		bonds.gasPrice = new SubscriptionBond('eth', 'gasPrice');
		bonds.estimateGas = x => new TransformBond(x => new SubscriptionBond('eth', 'estimateGas', [x]), [x]);

		bonds.blockTransactionCount = hashOrNumberBond => new TransformBond(
			hashOrNumber => isNumber(hashOrNumber)
				? new TransformBond(_ => +_, [new SubscriptionBond('eth', 'getBlockTransactionCountByNumber', [hashOrNumber])])
				: new TransformBond(_ => +_, [new SubscriptionBond('eth', 'getBlockTransactionCountByHash', [hashOrNumber])]),
			[hashOrNumberBond]);
		bonds.uncleCount = hashOrNumberBond => new TransformBond(
			hashOrNumber => isNumber(hashOrNumber)
				? new TransformBond(_ => +_, [new SubscriptionBond('eth', 'getUncleCountByBlockNumber', [hashOrNumber])])
				: new TransformBond(_ => +_, [new SubscriptionBond('eth', 'getUncleCountByBlockHash', [hashOrNumber])]),
			[hashOrNumberBond]).subscriptable();
		bonds.uncle = (hashOrNumberBond, indexBond) => new TransformBond(
			(hashOrNumber, index) => isNumber(hashOrNumber)
				? new SubscriptionBond('eth', 'getUncleByBlockNumberAndIndex', [hashOrNumber, index])
				: new SubscriptionBond('eth', 'getUncleByBlockHashAndIndex', [hashOrNumber, index]),
			[hashOrNumberBond, indexBond]).subscriptable();

		bonds.transaction = (hashOrNumberBond, indexOrNullBond) => new TransformBond(
			(hashOrNumber, indexOrNull) =>
				indexOrNull === undefined || indexOrNull === null
					? new SubscriptionBond('eth', 'getTransactionByHash', [hashOrNumber])
					: isNumber(hashOrNumber)
						? new SubscriptionBond('eth', 'getTransactionByBlockNumberAndIndex', [hashOrNumber, indexOrNull])
						: new SubscriptionBond('eth', 'getTransactionByBlockHashAndIndex', [hashOrNumber, indexOrNull]),
			[hashOrNumberBond, indexOrNullBond]).subscriptable();
		bonds.receipt = hashBond => new TransformBond(x => new SubscriptionBond('eth', 'getTransactionReceipt', [x]), [hashBond]).subscriptable();

		// web3_
		bonds.clientVersion = new TransformBond(() => api().web3.clientVersion(), [], []);

		// net_
		bonds.peerCount = new TransformBond(_ => +_, [new SubscriptionBond('net', 'peerCount')]);
		bonds.listening = new SubscriptionBond('net', 'listening');
		bonds.chainId = new SubscriptionBond('net', 'version');

		// parity_
		bonds.hashContent = u => new TransformBond(x => api().parity.hashContent(x), [u], [], false);
		bonds.gasPriceHistogram = new SubscriptionBond('parity', 'gasPriceHistogram').subscriptable();
		bonds.mode = new SubscriptionBond('parity', 'mode');
		bonds.accountsInfo = new SubscriptionBond('parity', 'accountsInfo').subscriptable(2);
		bonds.allAccountsInfo = new SubscriptionBond('parity', 'allAccountsInfo').subscriptable(2);
		bonds.hardwareAccountsInfo = new SubscriptionBond('parity', 'hardwareAccountsInfo').subscriptable(2);

		// ...authoring
		bonds.defaultExtraData = new SubscriptionBond('parity', 'defaultExtraData');
		bonds.extraData = new SubscriptionBond('parity', 'extraData');
		bonds.gasCeilTarget = new SubscriptionBond('parity', 'gasCeilTarget');
		bonds.gasFloorTarget = new SubscriptionBond('parity', 'gasFloorTarget');
		bonds.minGasPrice = new SubscriptionBond('parity', 'minGasPrice');
		bonds.transactionsLimit = new SubscriptionBond('parity', 'transactionsLimit');

		// ...chain info
		bonds.chainName = new SubscriptionBond('parity', 'netChain');
		bonds.chainStatus = new SubscriptionBond('parity', 'chainStatus').subscriptable();

		// ...networking
		bonds.peers = new SubscriptionBond('parity', 'netPeers').subscriptable(2);
		bonds.enode = new SubscriptionBond('parity', 'enode');
		bonds.nodePort = new TransformBond(_ => +_, [new SubscriptionBond('parity', 'netPort')]);
		bonds.nodeName = new SubscriptionBond('parity', 'nodeName');
		// Where defined ?
		bonds.signerPort = new TransformBond(() => api().parity.signerPort().then(_ => +_), [], []);
		bonds.dappsPort = new TransformBond(() => api().parity.dappsPort().then(_ => +_), [], []);
		bonds.dappsInterface = new TransformBond(() => api().parity.dappsInterface(), [], []);

		// ...transaction queue
		bonds.nextNonce = new TransformBond(_ => +_, [new SubscriptionBond('parity', 'nextNonce')]);
		bonds.pending = new SubscriptionBond('parity', 'pendingTransactions').subscriptable();
		bonds.local = new SubscriptionBond('parity', 'localTransactions').subscriptable(3);
		bonds.future = new SubscriptionBond('parity', 'futureTransactions').subscriptable(2);
		bonds.pendingStats = new SubscriptionBond('parity', 'pendingTransactionsStats').subscriptable(2);
		bonds.unsignedCount = new TransformBond(_ => +_, [new SubscriptionBond('parity', 'unsignedTransactionsCount')]);
		bonds.requestsToConfirm = new SubscriptionBond('signer', 'requestsToConfirm');

		// ...auto-update
		bonds.releasesInfo = new SubscriptionBond('parity', 'releasesInfo').subscriptable();
		bonds.versionInfo = new SubscriptionBond('parity', 'versionInfo').subscriptable();
		bonds.consensusCapability = new SubscriptionBond('parity', 'consensusCapability').subscriptable();
		bonds.upgradeReady = new TransformBond(() => api().parity.upgradeReady(), [], [onAutoUpdateChanged]).subscriptable();
	}

	// trace TODO: Implement contract object with new trace_many feature
	bonds.replayTx = (x, whatTrace) => new TransformBond((x, whatTrace) => api().trace.replayTransaction(x, whatTrace), [x, whatTrace], []).subscriptable();
	bonds.callTx = (x, whatTrace, blockNumber) => new TransformBond((x, whatTrace, blockNumber) => api().trace.call(x, whatTrace, blockNumber), [x, whatTrace, blockNumber], []).subscriptable();

	function traceCall (addr, method, args, options) {
		let data = util.abiEncode(method.name, method.inputs.map(f => f.type), args);
		let decode = d => util.abiDecode(method.outputs.map(f => f.type), d);
		let traceMode = options.traceMode;
		delete options.traceMode;
		return api().trace.call(overlay({ to: addr, data: data }, options), traceMode, 'latest').then(decode);
	}

	bonds.deployContract = function (init, abi, options = {}) {
		return new DeployContract(init, abi, options, api);
	};

	bonds.makeContract = function (address, abi, extras = [], debug = false) {
		var r = { address: address };
		let unwrapIfOne = a => a.length === 1 ? a[0] : a;
		abi.forEach(i => {
			if (i.type === 'function' && i.constant) {
				let f = function (...args) {
					var options = args.length === i.inputs.length + 1 ? args.pop() : {};
					if (args.length !== i.inputs.length) {
						throw new Error(`Invalid number of arguments to ${i.name}. Expected ${i.inputs.length}, got ${args.length}.`);
					}
					let f = (addr, ...fargs) => debug
						? traceCall(address, i, args, options)
						: call(addr, i, fargs, options)
							.then(rets => rets.map((r, o) => cleanup(r, i.outputs[o].type, api)))
							.then(unwrapIfOne);
					return new TransformBond(f, [address, ...args], [bonds.height]).subscriptable();	// TODO: should be subscription on contract events
				};
				r[i.name] = (i.inputs.length === 0) ? memoized(f) : (i.inputs.length === 1) ? presub(f) : f;
				r[i.name].args = i.inputs;
			}
		});
		extras.forEach(i => {
			let f = function (...args) {
				let expectedInputs = (i.numInputs || i.args.length);
				var options = args.length === expectedInputs + 1 ? args.pop() : {};
				if (args.length !== expectedInputs) {
					throw new Error(`Invalid number of arguments to ${i.name}. Expected ${expectedInputs}, got ${args.length}. ${args}`);
				}
				let c = abi.find(j => j.name === i.method);
				let f = (addr, ...fargs) => {
					let args = i.args.map((v, index) => v === null ? fargs[index] : typeof (v) === 'function' ? v(fargs[index]) : v);
					return debug
						? traceCall(address, i, args, options)
						: call(addr, c, args, options).then(unwrapIfOne);
				};
				return new TransformBond(f, [address, ...args], [bonds.height]).subscriptable();	// TODO: should be subscription on contract events
			};
			r[i.name] = (i.args.length === 1) ? presub(f) : f;
			r[i.name].args = i.args;
		});
		abi.forEach(i => {
			if (i.type === 'function' && !i.constant) {
				r[i.name] = function (...args) {
					var options = args.length === i.inputs.length + 1 ? args.pop() : {};
					if (args.length !== i.inputs.length) { throw new Error(`Invalid number of arguments to ${i.name}. Expected ${i.inputs.length}, got ${args.length}. ${args}`); }
					return debug
						? traceCall(address, i, args, options)
						: post(address, i, args, options).subscriptable();
				};
				r[i.name].args = i.inputs;
			}
		});
		var eventLookup = {};
		abi.filter(i => i.type === 'event').forEach(i => {
			eventLookup[util.abiSignature(i.name, i.inputs.map(f => f.type))] = i.name;
		});

		function prepareIndexEncode (v, t, top = true) {
			if (v instanceof Array) {
				if (top) {
					return v.map(x => prepareIndexEncode(x, t, false));
				} else {
					throw new Error('Invalid type');
				}
			}
			var val;
			if (t === 'string' || t === 'bytes') {
				val = util.sha3(v);
			} else {
				val = util.abiEncode(null, [t], [v]);
			}
			if (val.length !== 66) {
				throw new Error('Invalid length');
			}
			return val;
		}

		abi.forEach(i => {
			if (i.type === 'event') {
				r[i.name] = function (indexed = {}, params = {}) {
					return new TransformBond((addr, indexed) => {
						var topics = [util.abiSignature(i.name, i.inputs.map(f => f.type))];
						i.inputs.filter(f => f.indexed).forEach(f => {
							try {
								topics.push(indexed[f.name] ? prepareIndexEncode(indexed[f.name], f.type) : null);
							} catch (e) {
								throw new Error(`Couldn't encode indexed parameter ${f.name} of type ${f.type} with value ${indexed[f.name]}`);
							}
						});
						return api().eth.getLogs({
							address: addr,
							fromBlock: params.fromBlock || 0,
							toBlock: params.toBlock || 'pending',
							limit: params.limit || 10,
							topics: topics
						}).then(logs => logs.map(l => {
							l.blockNumber = +l.blockNumber;
							l.transactionIndex = +l.transactionIndex;
							l.logIndex = +l.logIndex;
							l.transactionLogIndex = +l.transactionLogIndex;
							var e = {};
							let unins = i.inputs.filter(f => !f.indexed);
							util.abiDecode(unins.map(f => f.type), l.data).forEach((v, j) => {
								let f = unins[j];
								if (v instanceof Array && !f.type.endsWith(']')) {
									v = util.bytesToHex(v);
								}
								if (f.type.substr(0, 4) === 'uint' && +f.type.substr(4) <= 48) {
									v = +v;
								}
								e[f.name] = v;
							});
							i.inputs.filter(f => f.indexed).forEach((f, j) => {
								if (f.type === 'string' || f.type === 'bytes') {
									e[f.name] = l.topics[1 + j];
								} else {
									var v = util.abiDecode([f.type], l.topics[1 + j])[0];
									if (v instanceof Array) {
										v = util.bytesToHex(v);
									}
									if (f.type.substr(0, 4) === 'uint' && +f.type.substr(4) <= 48) {
										v = +v;
									}
									e[f.name] = v;
								}
							});
							e.event = eventLookup[l.topics[0]];
							e.log = l;
							return e;
						}));
					}, [address, indexed], [bonds.height]).subscriptable();
				};
				r[i.name].args = i.inputs;
			}
		});
		return r;
	};

	if (useSubs) {
		bonds.registry = bonds.makeContract(new SubscriptionBond('parity', 'registryAddress'), RegistryABI, RegistryExtras);
	} else {
		bonds.registry = bonds.makeContract(new TransformBond(() => api().parity.registryAddress(), [], [bonds.time]), RegistryABI, RegistryExtras);
	}

	bonds.githubhint = bonds.makeContract(bonds.registry.lookupAddress('githubhint', 'A'), GitHubHintABI);
	bonds.operations = bonds.makeContract(bonds.registry.lookupAddress('operations', 'A'), OperationsABI);
	bonds.badgereg = bonds.makeContract(bonds.registry.lookupAddress('badgereg', 'A'), BadgeRegABI);
	bonds.tokenreg = bonds.makeContract(bonds.registry.lookupAddress('tokenreg', 'A'), TokenRegABI);

	bonds.badges = new TransformBond(n => {
		var ret = [];
		for (var i = 0; i < +n; ++i) {
			let id = i;
			ret.push(oo7.Bond.all([
				bonds.badgereg.badge(id),
				bonds.badgereg.meta(id, 'IMG'),
				bonds.badgereg.meta(id, 'CAPTION')
			]).map(([[addr, name, owner], img, caption]) => ({
				id,
				name,
				img,
				caption,
				badge: bonds.makeContract(addr, BadgeABI)
			}))
			);
		}
		return ret;
	}, [bonds.badgereg.badgeCount()], [], 1);

	bonds.badgesOf = address => new TransformBond(
		(addr, bads) => bads.map(b => ({
			certified: b.badge.certified(addr),
			badge: b.badge,
			id: b.id,
			img: b.img,
			caption: b.caption,
			name: b.name
		})),
		[address, bonds.badges], [], 2
	).map(all => all.filter(_ => _.certified));

	bonds.tokens = new TransformBond(n => {
		var ret = [];
		for (var i = 0; i < +n; ++i) {
			let id = i;
			ret.push(oo7.Bond.all([
				bonds.tokenreg.token(id),
				bonds.tokenreg.meta(id, 'IMG'),
				bonds.tokenreg.meta(id, 'CAPTION')
			]).map(([[addr, tla, base, name, owner], img, caption]) => ({
				id,
				tla,
				base,
				name,
				img,
				caption,
				token: bonds.makeContract(addr, TokenABI)
			}))
			);
		}
		return ret;
	}, [bonds.tokenreg.tokenCount()], [], 1);

	bonds.tokensOf = address => new TransformBond(
		(addr, bads) => bads.map(b => ({
			balance: b.token.balanceOf(addr),
			token: b.token,
			id: b.id,
			name: b.name,
			tla: b.tla,
			base: b.base,
			img: b.img,
			caption: b.caption
		})),
		[address, bonds.tokens], [], 2
	).map(all => all.filter(_ => _.balance.gt(0)));

	bonds.namesOf = address => new TransformBond((reg, addr, accs) => ({
		owned: accs[addr] ? accs[addr].name : null,
		registry: reg || null
	}), [bonds.registry.reverse(address), address, bonds.accountsInfo]);

	bonds.registry.names = oo7.Bond.mapAll([bonds.registry.ReverseConfirmed({}, { limit: 100 }), bonds.accountsInfo],
		(reg, info) => {
			let r = {};
			Object.keys(info).forEach(k => r[k] = info[k].name);
			reg.forEach(a => r[a.reverse] = bonds.registry.reverse(a.reverse));
			return r;
		}, 1);

	return bonds;
}

const t = defaultProvider();
const options = t ? { api: new ParityApi(t) } : null;
/** @type {Bonds} */
const bonds = options ? createBonds(options) : null;

const isOwned = addr => oo7.Bond.mapAll([addr, bonds.accounts], (a, as) => as.indexOf(a) !== -1);
const isNotOwned = addr => oo7.Bond.mapAll([addr, bonds.accounts], (a, as) => as.indexOf(a) === -1);

module.exports = {
	// Bonds stuff
	// abiPolyfill,
	options,
	bonds,
	Bonds,
	createBonds,

	// Util functions
	isOwned,
	isNotOwned,
	asciiToHex,
	bytesToHex,
	hexToAscii,
	isAddressValid,
	toChecksumAddress,
	sha3,
	capitalizeFirstLetter,
	singleton,
	denominations,
	denominationMultiplier,
	interpretRender,
	combineValue,
	defDenom,
	formatValue,
	formatValueNoDenom,
	formatToExponential,
	interpretQuantity,
	splitValue,
	formatBalance,
	formatBlockNumber,
	isNullData,
	splitSignature,
	removeSigningPrefix,
	cleanup,

	// ABIs
	abiPolyfill,
	RegistryABI,
	RegistryExtras,
	GitHubHintABI,
	OperationsABI,
	BadgeRegABI,
	TokenRegABI,
	BadgeABI,
	TokenABI
};