In Part 1, we identified Jupiter Lend’s liquidation instructions, mapped their account arguments, and decoded the raw amounts of debt repaid and collateral seized.
In this walkthrough, we’ll combine those findings into a single DuneSQL query using common table expressions (CTEs). The query reconstructs liquidation records from raw instructions and joins Dune’s prices.hour table to calculate the USD value of debt repaid and collateral seized.
We’ll work through the query in four phases, following how each CTE transforms and passes data to the next. Select a CTE in the interactive query below to explore its role in building the final dataset.
You can view the complete query on Dune or explore the source code and exported dataset on GitHub.
-- JUPITER LEND LIQUIDATIONS (BASE TABLE)
WITHPhase 1 Identify liquidation contexts
SELECT
block_time,
outer_instruction_index,
tx_id
FROM solana.instruction_calls
WHERE executing_account = 'jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi'
AND substr("data", 1, 8) = from_hex('dfb3e27d302e274a')
--AND inner_instruction_index IS NULL
AND tx_success = TRUE
AND block_time >= TIMESTAMP '2025-10-10 00:00:00 UTC'
AND block_time < TIMESTAMP '2025-10-11 00:00:00 UTC'
),Phase 2 Reconstruct the amount legs
SELECT
a.block_time,
a.tx_id,
a.account_arguments,
a.outer_instruction_index,
a.inner_instruction_index,
a."data" AS inner_data
FROM solana.instruction_calls a
JOIN liq_keys b
ON a.tx_id = b.tx_id
AND a.outer_instruction_index = b.outer_instruction_index
WHERE a.inner_instruction_index IS NOT NULL
AND a.block_time >= TIMESTAMP '2025-10-10 00:00:00 UTC'
AND a.block_time < TIMESTAMP '2025-10-11 00:00:00 UTC'
), SELECT
tx_id,
outer_instruction_index,
inner_instruction_index,
inner_data,
ROW_NUMBER() OVER (
PARTITION BY tx_id, outer_instruction_index
ORDER BY inner_instruction_index ASC
) AS rn
FROM inners_raw
WHERE substr(inner_data, 1, 8) = from_hex('d96ad06374972a87')
), SELECT
tx_id,
outer_instruction_index,
rn,
CAST(
from_big_endian_64(
reverse(substr(inner_data, 9, 8))
) AS DECIMAL(38, 0)
) AS arg1_i128,
CAST(
from_big_endian_64(
reverse(substr(inner_data, 25, 8))
) AS DECIMAL(38, 0)
) AS arg2_i128
FROM operate_ranked
), SELECT
tx_id,
outer_instruction_index,
rn,
CASE
WHEN rn = 1 THEN
CASE WHEN arg1_i128 <> 0 THEN arg1_i128 ELSE arg2_i128 END
END AS borrow_raw_i128,
CASE
WHEN rn = 2 THEN
CASE WHEN arg1_i128 <> 0 THEN arg1_i128 ELSE arg2_i128 END
END AS supply_raw_i128
FROM op_decoded
), SELECT
tx_id,
outer_instruction_index,
MAX(borrow_raw_i128) AS borrow_raw_i128,
MAX(supply_raw_i128) AS supply_raw_i128
FROM op_ui_per_rn
GROUP BY 1, 2
),Phase 3 Attach liquidation metadata
SELECT
sc.block_time,
sc.tx_id,
sc.outer_instruction_index,
sc.tx_signer AS liquidator,
sc.account_arguments,
sc.account_arguments[7] AS supply_mint,
sc.account_arguments[8] AS borrow_mint,
sc.account_arguments[14] AS position,
sc.executing_account,
to_hex(substr(sc."data", 1, 8)) AS discr_hex
FROM solana.instruction_calls sc
JOIN liq_keys lk
ON sc.tx_id = lk.tx_id
AND sc.outer_instruction_index = lk.outer_instruction_index
--WHERE sc.inner_instruction_index IS NULL
WHERE substr(sc."data", 1, 8) = from_hex('dfb3e27d302e274a')
AND sc.block_time >= TIMESTAMP '2025-10-10 00:00:00 UTC'
AND sc.block_time < TIMESTAMP '2025-10-11 00:00:00 UTC'
)Phase 4 Value and present the results
SELECT
lm.block_time AS block_time,
lm.tx_id,
lm.outer_instruction_index,
lm.borrow_mint AS debt_mint,
pb.symbol AS debt_symbol,
lm.supply_mint AS collateral_mint,
ps.symbol AS collateral_symbol,
lm.liquidator,
lm.position,
ABS(CAST(ou.supply_raw_i128 AS DOUBLE))
/ POWER(10, ps.decimals) AS collateral_seized_token,
ABS(CAST(ou.supply_raw_i128 AS DOUBLE))
/ POWER(10, ps.decimals)
* ps.price AS collateral_seized_usd,
ABS(CAST(ou.borrow_raw_i128 AS DOUBLE))
/ POWER(10, pb.decimals) AS debt_repaid_token,
ABS(CAST(ou.borrow_raw_i128 AS DOUBLE))
/ POWER(10, pb.decimals)
* pb.price AS debt_repaid_usd,
SUM(
ABS(CAST(ou.supply_raw_i128 AS DOUBLE))
/ POWER(10, ps.decimals)
* ps.price
) OVER (
ORDER BY lm.block_time, lm.tx_id, lm.outer_instruction_index
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_collateral_seized_usd,
SUM(
ABS(CAST(ou.borrow_raw_i128 AS DOUBLE))
/ POWER(10, pb.decimals)
* pb.price
) OVER (
ORDER BY lm.block_time, lm.tx_id, lm.outer_instruction_index
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_debt_repaid_usd
FROM liq_meta lm
LEFT JOIN op_ui ou
ON lm.tx_id = ou.tx_id
AND lm.outer_instruction_index = ou.outer_instruction_index
LEFT JOIN prices.hour pb
ON pb.contract_address = from_base58(lm.borrow_mint)
AND pb.timestamp = DATE_TRUNC('hour', lm.block_time)
AND pb.blockchain = 'solana'
LEFT JOIN prices.hour ps
ON ps.contract_address = from_base58(lm.supply_mint)
AND ps.timestamp = DATE_TRUNC('hour', lm.block_time)
AND ps.blockchain = 'solana';Pipeline overview
From raw instructions to liquidation rows
The query reconstructs one event-level record for each observed liquidation context.
- Identify liquidation contexts
Find successfulliquidateinstructions. - Reconstruct the amount legs
Decode the debt repaid and collateral seized. - Attach liquidation metadata
Add the signer, assets, position, and timestamp. - Value and present the results
Convert raw amounts to token units and USD.
Select a CTE to see how it advances the data through its phase.
Phase 1 of 4 · Identify liquidation contexts
liq_keys
This phase finds successful Jupiter Lend liquidate instructions in the analysis window. liq_keys keeps the transaction ID, containing outer-instruction index, and timestamp as the key for downstream reconstruction.
- Input
- Instruction-call rows
- Output
- One row per matched liquidation instruction
- Filter
- Program ID, liquidation discriminator, success flag, and UTC time window.
The dataset includes top-level and nested liquidation rows; the inner-index filter is intentionally not applied.
Phase 2 of 4 · Reconstruct the amount legs
inners_raw
This phase turns the CPI sequence into one debt-repayment amount and one collateral-withdrawal amount for each liquidation. inners_raw collects the child instructions belonging to each liquidation context.
- Input
- One row per liquidation context
- Output
- One row per inner instruction in those contexts
- Join key
tx_idplusouter_instruction_index
Phase 2 of 4 · Reconstruct the amount legs
operate_ranked
This CTE keeps inner rows matching the operate discriminator and ranks the amount calls within each liquidation context.
- Partition
tx_idplusouter_instruction_index- Order
inner_instruction_index ASC- Observed assignment
rn = 1is debt repayment;rn = 2is collateral withdrawal.
This ordering is observed in the analyzed transactions; it is not a universal protocol guarantee.
Phase 2 of 4 · Reconstruct the amount legs
op_decoded
op_decoded applies the byte offsets and little-endian conversion established in Part 1, carrying the two serialized amount arguments forward for leg assignment.
- Input
- Ranked
operateinstructions - Output
- Decoded
arg1andarg2values
The query reads eight bytes from each serialized 16-byte amount field. It is not a full arbitrary-range i128 decoder.
Phase 2 of 4 · Reconstruct the amount legs
op_ui_per_rn
This CTE assigns each decoded amount to its observed economic leg: debt repaid or collateral seized. The outer CASE uses the rank; the inner CASE selects the non-zero argument.
- Rank 1
- Sets
borrow_raw_i128(debt repaid). - Rank 2
- Sets
supply_raw_i128(collateral seized).
The leg labels depend on the observed ordering rule described above.
Phase 2 of 4 · Reconstruct the amount legs
op_ui
op_ui groups the ranked rows by liquidation context and uses MAX() to carry each non-null leg onto the same row.
- Input
- Two ranked amount rows per observed context
- Output
- One row with debt and collateral raw amounts per context
Phase 3 of 4 · Attach liquidation metadata
liq_meta
This phase reconnects the liquidation instruction to the metadata mapped in Part 1. liq_meta supplies the timestamp, signer, token mints, and position for the final event row.
- Account arguments
[7]supply mint,[8]borrow mint,[14]position- Liquidator field
tx_signer, the dataset's operational definition
Phase 4 of 4 · Value and present the results
Final projection
This phase combines the reconstructed amounts, liquidation metadata, and hourly token prices to produce one valued row per liquidation.
- Value amounts
- Join
prices.hourfor each mint, divide by token decimals, and multiply by the hourly price. - Cumulative totals
- Order by
block_time,tx_id, andouter_instruction_index.
Missing hourly price rows remain visible because the price joins are left joins, but their USD fields are null.