Skip to main content

Wallet Overlap with array_intersect

array_intersect answers one question well: which entities appear against every member of a set? The common form is "which wallets traded all of these tokens" — a single request, no client-side joins, and the result is exact.

Trades only

array_intersect exists on all four Trading cubes, but wallet overlap needs Trader_Address, and that field exists only in Trading_Trade_StringFields. The other three cubes have no trader axis at all:

EnumContains Trader_Address?
Trading_Trade_StringFieldsyes — also the only enum with TransactionHeader_Sender / _FeePayer / _To / _Hash
Trading_Pair_StringFieldsno
Trading_Token_StringFieldsno
Trading_Currency_StringFieldsno

side1 and side2 are non-null enums, so naming a field the cube does not have is a schema error rather than a silently wrong answer:

Argument "side1" has invalid value Trader_Address.
Expected type "Trading_Pair_StringFields"

The query

Wallets that traded both WSOL and USDC on Solana in a window:

{
Trading {
Trades(
where: {
Pair: {
Market: { Network: { is: "Solana" } }
Token: { Id: { in: [
"bid:solana:So11111111111111111111111111111111111111112",
"bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
] } }
}
Block: { Time: { since: "2026-09-01T12:00:00Z", till: "2026-09-01T12:01:00Z" } }
}
limit: { count: 1 }
) {
overlap: array_intersect(
side1: Trader_Address
side2: Pair_Token_Id
intersectWith: [
"bid:solana:So11111111111111111111111111111111111111112",
"bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
]
)
n: count
}
}
}

You get one row containing an array of the wallet addresses that traded every listed token. The same shape works on Ethereum, BSC and Base — swap the network and the token ids.

Verify it yourself, rather than trusting the array: pull the raw trades for the same window, bucket Trader.Address by Pair.Token.Id, and intersect the buckets in your client. The result is set-equal.

How it works

Understanding the mechanism explains every surprise below. For each value v in intersectWith, the engine builds:

groupUniqArrayIf(if(side1 = v, side2, side1), side1 = v OR side2 = v)

— that is, the other side of every row where v appears on either side — and then intersects those per-value sets.

Two consequences worth internalising:

  • It is not "collect side2 where side1 = v". A row matches when v is on either side, and the value contributed is whichever side v is not on.
  • side1 and side2 are therefore symmetric: swapping them returns the same set. This follows from the SQL, not just from testing.

Exact, and not truncated

The array is built with groupUniqArray, so it is an exact distinct set, and it is returned whole in one array rather than paged.

Prefer array_intersect over uniq when the two disagree

uniq() switches to an approximate estimator above 65,536 (2^16) distinct values, so on large windows uniq(of: Trader_Address) and a single-member array_intersect will differ slightly. array_intersect is the exact side. If you need an exact distinct count on a large population, take the array's length.

Gotchas

BehaviourWhat happensWhat to do
where excludes an intersectWith memberSilent empty array — populated row, count non-zero, HTTP 200, no errorMake the where clause span every member you intersect on
Empty or omitted intersectWithMalformed SQL: Code: 47. DB::Exception: Missing columns: 'AS'Always pass a non-empty list; the schema does not enforce it
selectWhere on the resultEvery operator fails with go-sqlbuilder: unsupported args when interpolatingYou cannot filter for "intersection is non-empty" server-side — filter in your client
orderBy on the aliasSorts arrays elementwise (lexicographic), not by lengthSort by length in your client
Element orderNot stable — reversing the intersectWith order returns the same set in a different orderTreat the result as a set and sort client-side
Used in a subscriptionAbsent from all four stream cubesQuery only — see what does not survive streaming

Beyond wallets

side1 / side2 accept any string field, so the same operator answers other "appears against every member" questions — for example which DEX venues quote every token in a basket, using Pair_Market_Name and Pair_Token_Symbol.