Scale and Filtering
How many addresses a variable can hold, how rule conditions combine, and what's in the event envelope.
Scale
Event Streaming enforces per-organization limits on rules and targets. The free tier allows 3 rules and 1 target. The paid tier allows 10 targets and unlimited rules.
Important NoteVariable values act as a set, so duplicate values aren't stored twice.
Filtering: rule conditions are OR, not AND
A rule's conditions are evaluated as OR, not AND. Matching a specific wallet address on a specific contract, for example "only transfers of USDC involving a given address," cannot be expressed as a single rule today. Each condition references one variable, and there's no way to combine conditions from two different variable types.
An AND match can be approximated by splitting the two conditions across two rules pointing at the same target, then correlating the results using tx_id. Using "USDC transfers involving a given wallet" as the example, create two address-type variables, one for the wallet and one for the contract:
POST /variables
{ "name": "watch-wallet", "type": "string" }
POST /variables/{watch-wallet-id}/values
{ "value": "0x3A024581D57D017453a67CF961771829B7F608F8" }POST /variables
{ "name": "watch-usdc-contract", "type": "string" }
POST /variables/{watch-usdc-contract-id}/values
{ "value": "0xA0b86991c6218b36C1D19D4a2E9Eb0cE3606eB48" }Then create one rule per variable, both pointing at the same target:
POST /rules
{
"name": "wallet-match",
"protocol": "ethereum",
"network": "mainnet",
"condition_type": "match_var",
"condition": [{ "variable_type": "address", "variable_id": "{watch-wallet-id}" }],
"target": "{target_id}",
"template": "UNIFIED_V1"
}POST /rules
{
"name": "contract-match",
"protocol": "ethereum",
"network": "mainnet",
"condition_type": "match_var",
"condition": [{ "variable_type": "address", "variable_id": "{watch-usdc-contract-id}" }],
"target": "{target_id}",
"template": "UNIFIED_V1"
}Both rules deliver unified_confirmed_tx events to the same target, each carrying data.tx_id. On your side, hold recent events from each rule keyed by tx_id, and treat it as a match only once the same tx_id has arrived from both:
const walletHits = new Map(); // tx_id -> event, from wallet-match
const contractHits = new Map(); // tx_id -> event, from contract-match
function onEvent(event, source) {
const txId = event.data.tx_id;
const store = source === "wallet" ? walletHits : contractHits;
const other = source === "wallet" ? contractHits : walletHits;
store.set(txId, event);
if (other.has(txId)) {
handleMatch(walletHits.get(txId), contractHits.get(txId));
}
}The envelope has no field identifying which rule matched (see the next section), so source needs to come from wherever each rule's events are received, for example, two separate webhook endpoints, or two separate WebSocket connections, one per rule.
A contract's address can be matched using the standard address-type variable: matching against a contract address works the same way as matching against a sender or recipient address, and behaves identically across Ethereum and other EVM chains. As with any address-type variable, matching is case-sensitive, an EVM contract address must be entered in checksum case to match correctly. If a contract-address match needs to be combined with a wallet-address match, filter by one of the two using Event Streaming, and do the rest on the client side, as shown in the example above.
The event envelope doesn't identify which rule matched
The message envelope contains protocol, network, event_type, data, and optionally raw, but it doesn't include a rule_id or target_id. If multiple rules point at the same target, the envelope alone doesn't indicate which rule caused a given message to be delivered. Distinguishing between rules requires either a separate target per rule, or matching the event's contents against rule definitions maintained independently.
👋 Need Help?
Contact us through email or our support page for any issues, bugs, or assistance you may need.
Updated about 2 hours ago
