## USER
Assume that I am working with Dune Analytics, which uses DuneSQL, a fork of TrinoSQL. Please keep this context in mind when generating SQL queries or providing assistance related to Dune Analytics and its data analysis.
For reference, here is a DuneSQL query that produces daily holder counts of the RPT token
with holder_balance as (
with address_date_list as (
with min_max_date as (
select min(date(evt_block_time)) as min_date,
max(date(evt_block_time)) as max_date
from erc721_ethereum.evt_Transfer
where contract_address = {{nft_contract}}
),
date_series as (
select block_date
from unnest(sequence(date('2018-01-01'), date(now()), interval '1' day)) as s(block_date)
),
holder_address_list as (
select distinct address from
(
select "from" as address from erc721_ethereum.evt_Transfer
where contract_address = {{nft_contract}}
union
select "to" as address from erc721_ethereum.evt_Transfer
where contract_address = {{nft_contract}}
) t
)
select block_date, address
from date_series
full outer join holder_address_list on true
),
daily_transfer_summary as (
with transfers_details as (
select date_trunc('day', evt_block_time) as block_date, "to" as address, count(evt_tx_hash) as transaction_count
from erc721_ethereum.evt_Transfer
where contract_address = {{nft_contract}}
group by 1, 2
union all
select date_trunc('day', evt_block_time) as block_date, "from" as address, count(evt_tx_hash)*(-1) as transaction_count
from erc721_ethereum.evt_Transfer
where contract_address = {{nft_contract}}
group by 1, 2
)
select block_date, address, sum(transaction_count) as transaction_count
from transfers_details
group by block_date, address
)
select a.block_date,
a.address,
sum(coalesce(transaction_count, 0)) over (partition by a.address order by a.block_date) as hold_count
from address_date_list a
left join daily_transfer_summary t on a.block_date = t.block_date and a.address = t.address
),
token_holder_summary as (
select block_date,
count(address) filter (where hold_count > 0) as unique_holder_count,
(count(address) filter (where hold_count > 0) - lag(count(address) filter (where hold_count > 0), 1) over (order by block_date desc))*-1 as daily_holder_change
from holder_balance
group by 1
order by 1 desc
),
diff as (
select block_date,
unique_holder_count,
lag(unique_holder_count) over (order by block_date) as lag_user
from token_holder_summary
order by block_date asc)
select
block_date,
unique_holder_count,
lag_user,
(unique_holder_count - lag_user) as diff_users,
(cast((unique_holder_count - lag_user) as double)) / (cast(lag_user as double)) as growth_rate
from diff
where block_date > date('2022-11-12')
I will provide subsequent requests for your assistance in construction queries. For now, please simply acknolwedge this message and await my further instructions
## ASSISTANT
Understood. I'm ready to assist with your DuneSQL queries or any Dune Analytics related inquiries. Please feel free to provide the details of the subsequent requests whenever you're ready.
## USER
I want the query results to display the cumulative supply of different RTokens over time, grouped by day.
The schema of the erc20_ethereum.evt_Transfer table is as follows (grouped in pairs of column header/type):
contract_address
varbinary
from
varbinary
to
varbinary
value
uint256
evt_block_number
bigint
evt_block_time
timestamp
evt_index
bigint
evt_tx_hash
varbinary
Transactions where the "from" address is 0x0000000000000000000000000000000000000000 represents an increase in supply
Where the "to" address is 0x0000000000000000000000000000000000000000 means that the supply has decreased
The results should be restricted to RTokens - their addresses of each RToken can be found based on an event indexed in Dune based on the event RTokenCreated and exists in the table reserve_protocol_ethereum.DeployerP1_evt_RTokenCreated
contract_address
varbinary
main
varbinary
owner
varbinary
rToken
varbinary
stRSR
varbinary
evt_block_number
bigint
evt_block_time
timestamp
evt_index
bigint
evt_tx_hash
varbinary
version
varchar
Where values can become negative, you should cast the value as a double. Values should also be divided by 1e18 because all relevant ERC20s are denominated in 18 decimal places
For example:
-CAST(value AS double) / 1e18
## ASSISTANT
Based on your description, here's a SQL query that calculates the daily cumulative supply of each RToken. The query starts by identifying RTokens from the reserve_protocol_ethereum.DeployerP1_evt_RTokenCreated table. It then collects the token transfer events for these tokens and calculates the daily cumulative supply based on the rules you've mentioned.
```sql
WITH rtoken_list AS (
SELECT rToken
FROM reserve_protocol_ethereum.DeployerP1_evt_RTokenCreated
),
daily_token_transfers AS (
SELECT date_trunc('day', evt_block_time) AS day,
contract_address AS rToken,
CASE
WHEN "from" = '0x0000000000000000000000000000000000000000' THEN CAST(value AS DOUBLE) / 1e18
WHEN "to" = '0x0000000000000000000000000000000000000000' THEN -CAST(value AS DOUBLE) / 1e18
ELSE 0
END AS daily_supply_change
FROM erc20_ethereum.evt_Transfer
WHERE contract_address IN (SELECT rToken FROM rtoken_list)
),
cumulative_supply AS (
SELECT day,
rToken,
SUM(daily_supply_change) OVER (PARTITION BY rToken ORDER BY day) AS cumulative_supply
FROM daily_token_transfers
)
SELECT *
FROM cumulative_supply
ORDER BY day, rToken;
```
This query results in a table where each row represents a single day for a single RToken, and the 'cumulative_supply' column indicates the cumulative supply of that RToken on that day.
Please note, you need to replace `'0x0000000000000000000000000000000000000000'` with the actual 'from' and 'to' addresses representing increase and decrease in supply, if they're different. I've assumed the addresses based on your description.
Additionally, since the data types and the table schemas for DuneSQL might be different from what I have worked with, please adapt the query accordingly if there are any errors. The exact structure and naming of tables and columns can vary between databases.
## USER
Error: Line 10:28: Cannot apply operator: varbinary = varchar(42) at line 10, position 28. Did you mean to write a bytearray literal? Use the syntax 0x1234 instead of '0x1234' [Execution ID: 01H4XZ6X60P73Y87EV5PP2G3SC].
## ASSISTANT
Apologies for the confusion. The error is due to the incorrect comparison of a binary and a string. We need to make sure we are comparing varbinary types with varbinary types.
To fix this, please replace the '0x0000000000000000000000000000000000000000' addresses with its binary equivalent. Here's the corrected SQL query.
```sql
WITH rtoken_list AS (
SELECT rToken
FROM reserve_protocol_ethereum.DeployerP1_evt_RTokenCreated
),
daily_token_transfers AS (
SELECT date_trunc('day', evt_block_time) AS day,
contract_address AS rToken,
CASE
WHEN "from" = 0x0000000000000000000000000000000000000000 THEN CAST(value AS DOUBLE) / 1e18
WHEN "to" = 0x0000000000000000000000000000000000000000 THEN -CAST(value AS DOUBLE) / 1e18
ELSE 0
END AS daily_supply_change
FROM erc20_ethereum.evt_Transfer
WHERE contract_address IN (SELECT rToken FROM rtoken_list)
),
cumulative_supply AS (
SELECT day,
rToken,
SUM(daily_supply_change) OVER (PARTITION BY rToken ORDER BY day) AS cumulative_supply
FROM daily_token_transfers
)
SELECT *
FROM cumulative_supply
ORDER BY day, rToken;
```
This corrected version of the query should work, given that `0x0000000000000000000000000000000000000000` is the correct binary equivalent of your placeholder address. Please replace this with the correct value if needed.
## USER
The cumulative supply of an asset appears to go to 0 if there are no subsequent transactions. Please modify the query so that there is a persistent entry for every non-zero supply even in the absence of supply changes on a particular day
## ASSISTANT
We need to generate a date series and cross join it with the rToken list to ensure we have an entry for each date and token combination. Then we'll use a window function to carry over the last known non-zero supply to subsequent days with no supply changes. Here's the updated query:
```sql
WITH rtoken_list AS (
SELECT DISTINCT rToken
FROM reserve_protocol_ethereum.DeployerP1_evt_RTokenCreated
),
date_series AS (
SELECT block_date
FROM unnest(sequence(date('2018-01-01'), date(now()), interval '1' day)) AS s(block_date)
),
token_date AS (
SELECT rToken, block_date
FROM rtoken_list
CROSS JOIN date_series
),
daily_token_transfers AS (
SELECT date_trunc('day', evt_block_time) AS day,
contract_address AS rToken,
CASE
WHEN "from" = 0x0000000000000000000000000000000000000000 THEN CAST(value AS DOUBLE) / 1e18
WHEN "to" = 0x0000000000000000000000000000000000000000 THEN -CAST(value AS DOUBLE) / 1e18
ELSE 0
END AS daily_supply_change
FROM erc20_ethereum.evt_Transfer
WHERE contract_address IN (SELECT rToken FROM rtoken_list)
),
daily_supply AS (
SELECT td.rToken,
td.block_date as day,
COALESCE(dtt.daily_supply_change, 0) as daily_supply_change
FROM token_date td
LEFT JOIN daily_token_transfers dtt
ON td.block_date = dtt.day AND td.rToken = dtt.rToken
),
cumulative_supply AS (
SELECT day,
rToken,
SUM(daily_supply_change) OVER (PARTITION BY rToken ORDER BY day) AS cumulative_supply
FROM daily_supply
),
final_result AS (
SELECT day,
rToken,
MAX(cumulative_supply) OVER (PARTITION BY rToken ORDER BY day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS adjusted_cumulative_supply
FROM cumulative_supply
WHERE cumulative_supply > 0
)
SELECT *
FROM final_result
ORDER BY day, rToken;
```
In this query, `date_series` generates a series of dates from 2018-01-01 to today, and `token_date` creates a cross join between these dates and the rToken list, ensuring we have an entry for each date and token combination.
The `daily_supply` CTE is then adjusted to join this `token_date` table with the `daily_token_transfers`, ensuring that every day and token combination is represented, even if there was no transaction for a given token on a certain day.
Finally, `final_result` uses a window function to carry forward the last non-zero cumulative supply to days with no supply changes. The `MAX` function is used instead of `LAST_VALUE` or `LAG` because it considers all preceding rows in the partition, not just the immediate previous row.
## USER
Great, now please provide me a query that identifies unique RToken depositors based on their aggregate deposit amounts. This is based on the unique "recipient" addresses and the amount in the "baskets" parameter in this event reserve_protocol_ethereum.RTokenP1_evt_Issuance. The schema is
amount
uint256
baskets
uint256
contract_address
varbinary
issuer
varbinary
evt_block_number
bigint
evt_block_time
timestamp
evt_index
bigint
evt_tx_hash
varbinary
recipient
varbinary
0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f is the address for eUSD, so you can filter contract addresses for this
## ASSISTANT
Assuming that we are interested in the `recipient` and the aggregate of the `baskets` field for each unique depositor, here is a query that should fulfill this requirement:
```sql
SELECT recipient,
SUM(CAST(baskets AS DOUBLE) / 1e18) AS total_baskets_deposited
FROM reserve_protocol_ethereum.RTokenP1_evt_Issuance
WHERE contract_address = 0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f
GROUP BY recipient
ORDER BY total_baskets_deposited DESC;
```
This query will group depositors by their `recipient` address and sum the `baskets` amount they've deposited, in total. Results are ordered in descending order by the total amount deposited, to list depositors who have deposited the most at the top. Note that the `baskets` values are divided by 1e18 to adjust for the 18 decimal places as previously mentioned.
Remember to replace `0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f` with the correct binary equivalent if needed.
## USER
Can you give me a query that groups the total deplsits to show me, for different depositor ranges:
1) The number of depositors in each range
2) The aggregate amount or baskets of eUSD that is minted in each range
The ranges are as follows:
<1000
1-10k
10-100k
100-300k
300k-1M
`>1M`
## ASSISTANT
To address your requirement, we can create bins for the total deposit ranges and then group depositors based on these bins. Here's a query that might fulfill this requirement:
```sql
WITH deposit_summary AS (
SELECT recipient,
SUM(CAST(baskets AS DOUBLE) / 1e18) AS total_baskets_deposited
FROM reserve_protocol_ethereum.RTokenP1_evt_Issuance
WHERE contract_address = 0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f
GROUP BY recipient
),
binned_deposits AS (
SELECT recipient,
total_baskets_deposited,
CASE
WHEN total_baskets_deposited < 1000 THEN '<1000'
WHEN total_baskets_deposited BETWEEN 1000 AND 10000 THEN '1k-10k'
WHEN total_baskets_deposited BETWEEN 10000 AND 100000 THEN '10k-100k'
WHEN total_baskets_deposited BETWEEN 100000 AND 300000 THEN '100k-300k'
WHEN total_baskets_deposited BETWEEN 300000 AND 1000000 THEN '300k-1M'
ELSE '>1M'
END AS deposit_range
FROM deposit_summary
)
SELECT deposit_range,
COUNT(recipient) AS num_depositors,
SUM(total_baskets_deposited) AS total_baskets_in_range
FROM binned_deposits
GROUP BY deposit_range
ORDER BY MIN(total_baskets_deposited);
```
In this query, the `deposit_summary` common table expression (CTE) first computes the total deposit for each depositor. The `binned_deposits` CTE then categorizes these total deposits into the specified ranges.
Finally, we count the number of depositors (`num_depositors`) and sum the total deposited eUSD (`total_baskets_in_range`) in each deposit range.
Again, remember to replace `0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f` with the correct binary equivalent if needed.
## USER
The produced results are no ordered by the actual depositor range. Below is how the ranges should be ordered, from smallest to largest:
<1000
1-10k
10-100k
100-300k
300k-1M
>1M
Following is the query I ended up using:
WITH deposit_totals AS (
SELECT recipient,
SUM(CAST(baskets AS DOUBLE) / 1e18) AS total_baskets_deposited
FROM reserve_protocol_ethereum.RTokenP1_evt_Issuance
WHERE contract_address = 0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f
GROUP BY recipient
)
SELECT deposit_range,
COUNT(*) AS number_of_depositors,
SUM(total_baskets_deposited) AS total_baskets_deposited
FROM (
SELECT total_baskets_deposited,
CASE
WHEN total_baskets_deposited < 1000 THEN '<1000'
WHEN total_baskets_deposited BETWEEN 1000 AND 10000 THEN '1k-10k'
WHEN total_baskets_deposited BETWEEN 10000 AND 100000 THEN '10k-100k'
WHEN total_baskets_deposited BETWEEN 100000 AND 300000 THEN '100k-300k'
WHEN total_baskets_deposited BETWEEN 300000 AND 1000000 THEN '300k-1M'
ELSE '>1M'
END AS deposit_range
FROM deposit_totals
) sub
GROUP BY deposit_range
ORDER BY deposit_range;
## ASSISTANT
I see where the misunderstanding is, you're right. SQL doesn't know how to order those string values to reflect the logical order of the ranges.
One way around this is to manually define the order using a CASE statement in the ORDER BY clause:
```sql
WITH deposit_totals AS (
SELECT recipient,
SUM(CAST(baskets AS DOUBLE) / 1e18) AS total_baskets_deposited
FROM reserve_protocol_ethereum.RTokenP1_evt_Issuance
WHERE contract_address = 0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f
GROUP BY recipient
)
SELECT deposit_range,
COUNT(*) AS number_of_depositors,
SUM(total_baskets_deposited) AS total_baskets_deposited
FROM (
SELECT total_baskets_deposited,
CASE
WHEN total_baskets_deposited < 1000 THEN '<1000'
WHEN total_baskets_deposited BETWEEN 1000 AND 10000 THEN '1k-10k'
WHEN total_baskets_deposited BETWEEN 10000 AND 100000 THEN '10k-100k'
WHEN total_baskets_deposited BETWEEN 100000 AND 300000 THEN '100k-300k'
WHEN total_baskets_deposited BETWEEN 300000 AND 1000000 THEN '300k-1M'
ELSE '>1M'
END AS deposit_range
FROM deposit_totals
) sub
GROUP BY deposit_range
ORDER BY
CASE
WHEN deposit_range = '<1000' THEN 1
WHEN deposit_range = '1k-10k' THEN 2
WHEN deposit_range = '10k-100k' THEN 3
WHEN deposit_range = '100k-300k' THEN 4
WHEN deposit_range = '300k-1M' THEN 5
ELSE 6
END;
```
This manually defines the order of the deposit_range based on its value. In this way, the result will be ordered correctly according to the logical ordering of the ranges.
Again, remember to replace `0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f` with the correct binary equivalent if needed.
## USER
Can you additionally give me the total sum of depositors?
## ASSISTANT
Sure, to get the total sum of depositors, you can add a simple SQL `SUM()` function over the `number_of_depositors` alias. You can use the `ROLLUP()` function to get the subtotal and total grouping. Here is the updated query:
```sql
WITH deposit_totals AS (
SELECT recipient,
SUM(CAST(baskets AS DOUBLE) / 1e18) AS total_baskets_deposited
FROM reserve_protocol_ethereum.RTokenP1_evt_Issuance
WHERE contract_address = 0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f
GROUP BY recipient
)
SELECT
CASE WHEN GROUPING(deposit_range) = 1 THEN 'All Ranges'
ELSE deposit_range END AS deposit_range,
COUNT(*) AS number_of_depositors,
SUM(total_baskets_deposited) AS total_baskets_deposited
FROM (
SELECT total_baskets_deposited,
CASE
WHEN total_baskets_deposited < 1000 THEN '<1000'
WHEN total_baskets_deposited BETWEEN 1000 AND 10000 THEN '1k-10k'
WHEN total_baskets_deposited BETWEEN 10000 AND 100000 THEN '10k-100k'
WHEN total_baskets_deposited BETWEEN 100000 AND 300000 THEN '100k-300k'
WHEN total_baskets_deposited BETWEEN 300000 AND 1000000 THEN '300k-1M'
ELSE '>1M'
END AS deposit_range
FROM deposit_totals
) sub
GROUP BY ROLLUP(deposit_range)
ORDER BY
CASE
WHEN deposit_range = '<1000' THEN 1
WHEN deposit_range = '1k-10k' THEN 2
WHEN deposit_range = '10k-100k' THEN 3
WHEN deposit_range = '100k-300k' THEN 4
WHEN deposit_range = '300k-1M' THEN 5
WHEN deposit_range = '>1M' THEN 6
ELSE 7
END;
```
This query will produce an additional row at the end with a `deposit_range` of `'All Ranges'` and `number_of_depositors` being the total sum of depositors.
Again, remember to replace `0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f` with the correct binary equivalent if needed.
## USER
I want the total number of depositors as a separate column (all the same value for each row)
## ASSISTANT
In that case, you can calculate the total number of depositors separately in a subquery and join this value to your main result set. Here is how you can do it:
```sql
WITH deposit_totals AS (
SELECT recipient,
SUM(CAST(baskets AS DOUBLE) / 1e18) AS total_baskets_deposited
FROM reserve_protocol_ethereum.RTokenP1_evt_Issuance
WHERE contract_address = 0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f
GROUP BY recipient
),
total_depositor_count AS (
SELECT COUNT(DISTINCT recipient) as total_depositors
FROM reserve_protocol_ethereum.RTokenP1_evt_Issuance
WHERE contract_address = 0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f
)
SELECT deposit_range,
COUNT(*) AS number_of_depositors,
SUM(total_baskets_deposited) AS total_baskets_deposited,
total_depositors
FROM (
SELECT total_baskets_deposited,
CASE
WHEN total_baskets_deposited < 1000 THEN '<1000'
WHEN total_baskets_deposited BETWEEN 1000 AND 10000 THEN '1k-10k'
WHEN total_baskets_deposited BETWEEN 10000 AND 100000 THEN '10k-100k'
WHEN total_baskets_deposited BETWEEN 100000 AND 300000 THEN '100k-300k'
WHEN total_baskets_deposited BETWEEN 300000 AND 1000000 THEN '300k-1M'
ELSE '>1M'
END AS deposit_range
FROM deposit_totals
) sub
CROSS JOIN total_depositor_count
GROUP BY deposit_range, total_depositors
ORDER BY
CASE
WHEN deposit_range = '<1000' THEN 1
WHEN deposit_range = '1k-10k' THEN 2
WHEN deposit_range = '10k-100k' THEN 3
WHEN deposit_range = '100k-300k' THEN 4
WHEN deposit_range = '300k-1M' THEN 5
ELSE 6
END;
```
In this modified version of the query, I added a second CTE (`total_depositor_count`) to calculate the total number of distinct depositors in the `reserve_protocol_ethereum.RTokenP1_evt_Issuance` table where the contract address matches the specified value. This value is then added to each row of the main result set using a `CROSS JOIN`.
Again, remember to replace `0xa0d69e286b938e21cbf7e51d71f6a4c8918f482f` with the correct binary equivalent if needed.