Activity taxonomy
The normalized cross-protocol activity feed — one label set for deposits, borrows, swaps, and more, with per-event USD valuation and keyset pagination.
Where the positions endpoints tell you what a wallet holds now, the activity feed tells you what it did — each on-chain action normalized to one label set that means the same thing across every protocol.
The label set
activity_type is a closed enum. A deposit into a Blend pool and a liquidity add
on Soroswap map to the same vocabulary, so you branch on one field:
activity_type | Meaning |
|---|---|
deposit | Supplied assets into a protocol. |
withdraw | Removed supplied assets. |
borrow | Took on debt. |
repay | Repaid debt. |
liquidation | Position was liquidated. |
claim_rewards | Claimed emissions / rewards. |
flash_loan | Flash loan taken. |
bad_debt | Debt written off as unrecoverable. |
swap | Token swap. |
add_liquidity | Added to an AMM pool. |
remove_liquidity | Removed from an AMM pool. |
mint | Minted shares / tokens. |
burn | Burned shares / tokens. |
contract_status_change | A tracked contract changed lifecycle state (e.g. a pool froze). |
contract_status_change is not a user action — for those rows the address field
is the contract whose status changed, not a wallet.
Read the feed
GET /v1/users/{address}/activities returns the wallet’s activity, newest first:
curl "https://query.orionhq.run/v1/users/GABC7XYZ.../activities?limit=50" \
-H "x-orion-api-key: $ORION_KEY"{
"data": {
"address": "GABC7XYZ...",
"activities": [
{
"id": "80421993",
"address": "GABC7XYZ...",
"protocol": "blend",
"activity_type": "borrow",
"contract": "CCLBPEYS...",
"asset_id": "CDLZFC3S...",
"asset_symbol": "USDC",
"asset_decimals": 7,
"amount": "50.0000000",
"share_amount": "49.8100000",
"usd_value": "50.00",
"counterparty": null,
"tx_hash": "3f9a...c1",
"ledger": 3356020,
"timestamp": "2026-07-23T08:41:12Z",
"metadata": null
}
],
"next_cursor": "eyJsZWRnZXIiOjMzNTYwMjB9"
},
"meta": { "...": "response metadata" }
}Each row carries the normalized activity_type, the protocol and contract it
touched, the asset (asset_id, asset_symbol, asset_decimals), on-chain
provenance (tx_hash, ledger, timestamp), and a per-event usd_value when a
price was available — null otherwise, with the row still counted and returned.
id is a string even though it’s numeric: the underlying sequence can exceed
JSON’s safe integer range, so treat it as an opaque string.
Pagination
The feed uses keyset (cursor) pagination. When more rows exist, next_cursor is
non-null. Pass it back verbatim as the cursor query parameter for the next
page — do not parse or construct it:
curl "https://query.orionhq.run/v1/users/GABC7XYZ.../activities?cursor=eyJsZWRnZXIiOjMzNTYwMjB9" \
-H "x-orion-api-key: $ORION_KEY"limit defaults to 50 and is capped at 100. A null next_cursor means you’ve
reached the end.
Filters
Narrow the feed with query parameters (combine freely):
| Parameter | Filters by |
|---|---|
protocol | Protocol id, e.g. blend. |
activity_type | One type, or a comma-separated list (e.g. deposit,withdraw). |
contract | The contract an activity touched. |
tx_hash | A specific transaction. |
from_ledger / to_ledger | An inclusive ledger range. |
as_of_ledger | Rows at or before a ledger. Cannot be combined with to_ledger. |
curl "https://query.orionhq.run/v1/users/GABC7XYZ.../activities?protocol=blend&activity_type=borrow,repay" \
-H "x-orion-api-key: $ORION_KEY"Aggregated series
GET /v1/users/{address}/activities/series buckets the feed into event counts and
USD volume, grouped by activity_type within each bucket — useful for charting
activity over time without paging the raw feed.
Choose one axis: time buckets (interval of 1h/4h/1d with from/to) or
ledger buckets (ledger_bucket with from_ledger/to_ledger).
curl "https://query.orionhq.run/v1/users/GABC7XYZ.../activities/series?interval=1d&from=2026-07-16T00:00:00Z&to=2026-07-23T00:00:00Z" \
-H "x-orion-api-key: $ORION_KEY"{
"data": {
"address": "GABC7XYZ...",
"points": [
{
"timestamp": "2026-07-22T00:00:00Z",
"ledger": 3356020,
"activity_type": "borrow",
"events": 3,
"usd_volume": "150.00"
}
]
},
"meta": { "...": "response metadata" }
}usd_volume sums only priced events, so a bucket of entirely unpriced events is
null — but events still counts every row. Gap-fill is not offered here: counts
and sums have no meaningful value to carry across an empty bucket.