Skip to content

Commit

Permalink
Merge pull request #39 from decentraland/feature/watch-blockchain-events
Browse files Browse the repository at this point in the history
feat: Introduce the Event class to watch/get contract events
  • Loading branch information
Juan Cazala authored Feb 13, 2018
2 parents ddc1aec + 08f77e1 commit 2a7a1b7
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 26 deletions.
7 changes: 6 additions & 1 deletion src/ethereum/Contract.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { promisify } from '../utils'
import { abiDecoder } from './abi-decoder'
import { Event } from './Event'

/** Class to work with Ethereum contracts */
export class Contract {
Expand Down Expand Up @@ -30,7 +31,7 @@ export class Contract {
/**
* Checks if an address is actually 0 in hex or a falsy value
* @param {string} address
* @return {Boolean}
* @return {boolean}
*/
static isEmptyAddress(address) {
return !address || address === '0x0000000000000000000000000000000000000000'
Expand Down Expand Up @@ -160,4 +161,8 @@ export class Contract {
return param.value
}
}

getEvent(eventName) {
return new Event(this, eventName)
}
}
51 changes: 51 additions & 0 deletions src/ethereum/Event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/** Event class */
export class Event {
constructor(contract, eventName) {
this.contract = contract
this.name = eventName

if (!this.instance) {
throw new Error(
`Could not find event "${eventName}" for ${contract.constructor.getContractName()} contract`
)
}
}

get instance() {
return this.contract.instance[this.name]
}

/**
* Register a callback for each time this event appears for the contract
* @param {object|function} [options] - If options is a function it'll be used as callback, ignoring the second argument
* @param {object} [options.args] - Indexed return values you want to filter the logs by
* @param {object} [options.opts] - Additional filter options, fordwarded to {@link https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethfilter}
* @param {number|string} [options.fromBlock='latest'] - The number of the earliest block. latest means the most recent and pending currently mining, block
* @param {number|string} [options.toBlock='latest'] - The number of the latest block latest means the most recent and pending currently mining, block
* @param {string} [options.address] - An address or a list of addresses to only get logs from particular account(s).
* @param {Array<strings>} [options.topics] - An array of values which must each appear in the log entries. The order is important, if you want to leave topics out use null.
* @param {Function} callback - Callback function for each event found
*/
watch(options, callback) {
const { args, opts } = typeof options === 'function' ? {} : options
const func = typeof options === 'function' ? options : callback
this.instance(args, opts).watch(func)
}

/**
* Get all the historical events
* @param {object|function} [options] - If options is a function it'll be used as callback, ignoring the second argument
* @param {object} [options.args] - Indexed return values you want to filter the logs by
* @param {object} [options.opts] - Additional filter options, fordwarded to {@link https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethfilter}
* @param {number|string} [options.fromBlock='latest'] - The number of the earliest block. latest means the most recent and pending currently mining, block
* @param {number|string} [options.toBlock='latest'] - The number of the latest block latest means the most recent and pending currently mining, block
* @param {string} [options.address] - An address or a list of addresses to only get logs from particular account(s).
* @param {Array<strings>} [options.topics] - An array of values which must each appear in the log entries. The order is important, if you want to leave topics out use null.
* @param {Function} callback - Callback function for each event found
*/
getAll(options, callback) {
const { args, opts } = typeof options === 'function' ? {} : options
const func = typeof options === 'function' ? options : callback
this.instance(args, opts).watch(func)
}
}
48 changes: 23 additions & 25 deletions src/ethereum/eth.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,30 +85,19 @@ export const eth = {
return this.wallet.getAccount()
},

/**
* Get a contract instance built on {@link eth#setContracts}
* It'll throw if the contract is not found on the `contracts` mapping
* @param {string} name - Contract name
* @return {object} contract
*/
getContract(name) {
if (!this.contracts[name]) {
const contractNames = Object.keys(this.contracts)
throw new Error(
`The contract ${name} not found.\nDid you add it to the '.connect()' call?\nAvailable contracts are ${contractNames}`
)
}

return this.contracts[name]
},

/**
* Set the Ethereum contracts to use on the `contracts` property. It builds a map of
* { [Contract Name]: Contract instance }
* usable later via `.getContract`. Check {@link https://github.com/decentraland/commons/tree/master/src/ethereum} for more info
* @param {array<Contract|object>} contracts - An array comprised of a wide variety of options: objects defining contracts, Contract subclasses or Contract instances.
*/
setContracts(contracts) {
if (!this.isConnected()) {
throw new Error(
'Tried to set eth contracts without connecting successfully first'
)
}

for (const contractData of contracts) {
let contract = null
let contractName = null
Expand All @@ -119,9 +108,9 @@ export const eth = {
contractName = contractData.getContractName()
} else if (
typeof contractData === 'object' &&
!this.isContractOptions(contractData)
!contract.constructor !== Object
) {
// contractData is an instance of Contract or of one of its children
// contractData is an instance of Contract or of one of its subclasses
contract = contractData
contractName = contractData.constructor.getContractName()
} else {
Expand All @@ -142,12 +131,21 @@ export const eth = {
}
},

isContractOptions(contractData) {
return (
'name' in contractData &&
'address' in contractData &&
'abi' in contractData
)
/**
* Get a contract instance built on {@link eth#setContracts}
* It'll throw if the contract is not found on the `contracts` mapping
* @param {string} name - Contract name
* @return {object} contract
*/
getContract(name) {
if (!this.contracts[name]) {
const contractNames = Object.keys(this.contracts)
throw new Error(
`The contract ${name} not found.\nDid you add it to the '.connect()' call?\nAvailable contracts are ${contractNames}`
)
}

return this.contracts[name]
},

/**
Expand Down
1 change: 1 addition & 0 deletions src/ethereum/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { eth } from './eth'
export { Contract } from './Contract'
export { Event } from './Event'
export { SignedMessage } from './SignedMessage'
export { txUtils } from './txUtils'

0 comments on commit 2a7a1b7

Please sign in to comment.