Event queries
One of the challenges of interacting with smart contracts is computing the current state of a contract.
For example, the ERC20 token standard defines a “balanceOf” method for a given input address, but does not dictate how those balances are to be stored or computed. On a token smart contract with hundreds of thousands or millions of balances holders, it is impractical to call “getBalance” for all accounts on every block.
MultiBaas implements a general solution called “Event Queries”. They allow a user to define a set of events and how they should be enumerated or aggregated, for example a list of balances or the most recent status per address.
An Event Query consists of:
- Events : A list of events to query e.g. "Transfer"
- Group by : The field under which to aggregate the results e.g. "account"
-
Order by
: The field by which the results should be ordered (ascending by default) e.g. "balance"
- This can be overridden with a url parameter e.g. ?order_by=balance-desc
- Order : This field is used with Order By to order the results in a specific order e.g. "DESC, ASC"
Each Event is defined by:
- Event Name : The name of the event
- Select : A list of Fields to return from the event
- Filter : A set of Filters used to include or exclude events in the query
A Field contains:
- Type : The required type of field. A field's type can be input for the event's input or contract_label , block_number , etc for the event's metadata
-
Name
: The
optional
name for the event's input/metadata e.g. "value, contract_label"
- Note: This field exists for readability and is discarded in processing if Alias is specified
-
Aggregator
: An aggregate operation to perform on the field when combined with a
Group by
clause
- Possible aggregators are: add, subtract, last, first, min, max
- InputIndex : The required zero-indexed input number of the parameter e.g. 2
- Alias : An optional name to use for the field instead of its ABI name e.g. "balance". If Alias is not specified, Name will be used to display the column name in the query's return results
A Filter contains:
- FieldType and InputIndex : Data to represent event's input/metadata (similar to Type and InputIndex in the Field section).
- Rule : The type of filter to apply e.g. "And, Or"
-
Operator
: The type of comparison to use
- Possible operators are: equal, notEqual, lessThan, greaterThan, lessThanOrEqual, greaterThanOrEqual
- Value : The value to compare against when using a filter operator with the selected event's input/metadata.
- Children : A set of filters combined when the rule is "and" or "or" using the corresponding boolean logic
ERC20 Example
Consider an ERC20 token smart contract "curvetoken".
If the following Transfer(address indexed _from, address indexed _to, uint256 _value) events are emitted by the contract:
- Transfer( 0x0, 0xA, 100 ) // A "Mint" action
- Transfer( 0xA, 0xB, 20 )
- Transfer( 0xB, 0xC, 5 )
Then the state of the contract, if we were to call balanceOf(owner) for each of owner = 0x0, 0xA, 0xB, and 0xC, would be:
owner (Ethereum address) | balanceOf(owner) |
---|---|
0x0 | 0 |
0xA | 80 |
0xB | 15 |
0xC | 5 |
Event Query API
To retrieve that data using an event query, we can define one using the API as follows:
Request:
PUT .../queries/curvetoken_balance
{
"events": [
{
"eventName": "Transfer(address,address,uint256)",
"select": [
{
"alias": "account",
"type": "input",
"name": "from",
"inputIndex": 0
},
{
"alias": "balance",
"aggregator": "subtract",
"type": "input",
"name": "value",
"inputIndex": 2
}
],
"filter": {
"rule": "And",
"children": [
{
"operator": "Equal",
"value": "curvetoken",
"fieldType": "contract_address_label"
}
]
}
},
{
"eventName": "Transfer(address,address,uint256)",
"select": [
{
"alias": "account",
"type": "input",
"name": "to",
"inputIndex": 1
},
{
"alias": "balance",
"aggregator": "add",
"type": "input",
"name": "value",
"inputIndex": 2
}
],
"filter": {
"rule": "And",
"children": [
{
"operator": "Equal",
"value": "autotoken",
"fieldType": "contract_address_label"
}
]
}
}
],
"groupBy": "account",
"orderBy": "balance",
"order": "DESC"
}
Response:
{
"status": 200,
"message": "success"
}
We can then execute the query as follows:
Request:
GET .../queries/curvetoken_balance/results
Response:
{
"status": 200,
"message": "success",
"result": {
"rows": [
{
"account": "0xA",
"balance": 80
},
{
"account": "0xB",
"balance": 15
},
{
"account": "0xC",
"balance": 5
},
{
"account": "0x0",
"balance": -100
}
]
}
}
Note the "zero" account has a balance of -100. This is a special case as we are simply adding or subtracting all transfers to and from the account.
You can also preview a query by making a POST request.
Request:
POST .../queries
{
"events": [
{
"eventName": "Transfer",
"select": [
{
"type": "input",
"name": "value",
"inputIndex": 2
}
]
}
]
}
Response:
{
"status": 200,
"message": "success",
"result": {
"rows": [
{
"value": 100
},
{
"value": 20
},
{
"value": 5
}
]
}
}
Event Query UI
Event queries are accessed in the UI via the Contracts > Event Queries menu.
The main screen of the event queries management UI displays a list of existing queries, a paginated sample of their results, and actions that can be taken.
Clicking Edit or New Event Query brings up the event query edit screen. The top half of the page allows the user to define the events that will be aggregated into the event query. For each event, one or more fields may be added. For each event query, a field may be selected to group by and order by.
When you update the query, a sample of the results will appear below the query editor.
You can also preview the generated JSON definition of the query by expanding the Query JSON section.