A collection of BigQuery queries that I, Leigh, have found personally helpful when using Stellar Hubble. Stellar Hubble is the public BigQuery repository containing historical/archive data for the Stellar Network. These queries are not intended for general consumption and collect data specific to the use cases I have encountered. Use at your own risk.
contract data key types
queries/contract_data_key_types.sql
View on GitHub-- Query to extract and summarize types from the key_decoded field
-- Analyzes the type distribution in contract events data
WITH
-- Extract types from key_decoded field
key_decoded_types AS (
SELECT
-- Extract the type from key_decoded, or use value if type is empty
CASE
WHEN JSON_VALUE(key_decoded, '$.type') = ''
THEN REGEXP_EXTRACT(JSON_VALUE(key_decoded, '$.value'), r'\(([^)]+)\)')
ELSE JSON_VALUE(key_decoded, '$.type')
END AS key_type,
key_decoded
FROM
`crypto-stellar.crypto_stellar.contract_data`
WHERE
key_decoded IS NOT NULL
),
-- First, collect type counts
type_counts AS (
SELECT
key_type,
COUNT(*) AS count
FROM
key_decoded_types
GROUP BY
key_type
),
-- Get sample key_decoded for each type
type_samples AS (
SELECT
key_type,
ARRAY_AGG(key_decoded ORDER BY RAND() LIMIT 1)[OFFSET(0)] AS sample_key_decoded
FROM
key_decoded_types
GROUP BY
key_type
)
-- Join the results together
SELECT
c.key_type,
c.count,
s.sample_key_decoded
FROM
type_counts c
JOIN
type_samples s ON c.key_type = s.key_type
ORDER BY
c.count DESC;
contract data val types
queries/contract_data_val_types.sql
View on GitHub-- Query to extract and summarize types from the val_decoded field
-- Analyzes the type distribution in contract events data
WITH
-- Extract types from val_decoded field
val_decoded_types AS (
SELECT
-- Extract the type from val_decoded, or use value if type is empty
CASE
WHEN JSON_VALUE(val_decoded, '$.type') = ''
THEN REGEXP_EXTRACT(JSON_VALUE(val_decoded, '$.value'), r'\(([^)]+)\)')
ELSE JSON_VALUE(val_decoded, '$.type')
END AS val_type,
val_decoded
FROM
`crypto-stellar.crypto_stellar.contract_data`
WHERE
val_decoded IS NOT NULL
),
-- First, collect type counts
type_counts AS (
SELECT
val_type,
COUNT(*) AS count
FROM
val_decoded_types
GROUP BY
val_type
),
-- Get sample val_decoded for each type
type_samples AS (
SELECT
val_type,
ARRAY_AGG(val_decoded ORDER BY RAND() LIMIT 1)[OFFSET(0)] AS sample_val_decoded
FROM
val_decoded_types
GROUP BY
val_type
)
-- Join the results together
SELECT
c.val_type,
c.count,
s.sample_val_decoded
FROM
type_counts c
JOIN
type_samples s ON c.val_type = s.val_type
ORDER BY
c.count DESC;
events data types
queries/events_data_types.sql
View on GitHub-- Query to extract and summarize types from the data_decoded field
-- Analyzes the type distribution in contract events data
WITH
-- Extract types from data_decoded field
data_decoded_types AS (
SELECT
-- Extract the type from data_decoded
JSON_VALUE(data_decoded, '$.type') AS data_type,
data_decoded
FROM
`crypto-stellar.crypto_stellar.history_contract_events`
WHERE
data_decoded IS NOT NULL
AND in_successful_contract_call = TRUE
AND JSON_VALUE(data_decoded, '$.type') IS NOT NULL
AND type_string = 'ContractEventTypeContract'
),
-- First, collect type counts
type_counts AS (
SELECT
data_type,
COUNT(*) AS count
FROM
data_decoded_types
GROUP BY
data_type
),
-- Get sample data_decoded for each type
type_samples AS (
SELECT
data_type,
ARRAY_AGG(data_decoded ORDER BY RAND() LIMIT 1)[OFFSET(0)] AS sample_data_decoded
FROM
data_decoded_types
GROUP BY
data_type
)
-- Join the results together
SELECT
c.data_type,
c.count,
s.sample_data_decoded
FROM
type_counts c
JOIN
type_samples s ON c.data_type = s.data_type
ORDER BY
c.count DESC;
events topic types
queries/events_topic_types.sql
View on GitHub-- Query to extract topic types from topics_decoded field and count their occurrences
-- Groups by the combined type signature (e.g., "Sym,Address,Address")
-- Limited to ContractEventTypeContract events
WITH
-- Extract topics_decoded and create a string of all types in each event
events_with_type_signature AS (
SELECT
contract_id,
topics_decoded,
-- Extract each type from the topics_decoded array and join them with commas
(
SELECT STRING_AGG(
JSON_VALUE(topic, '$.type'),
','
ORDER BY OFFSET
)
FROM UNNEST(JSON_EXTRACT_ARRAY(topics_decoded, '$.topics_decoded')) AS topic WITH OFFSET
WHERE JSON_VALUE(topic, '$.type') IS NOT NULL
) AS type_signature
FROM
`crypto-stellar.crypto_stellar.history_contract_events`
WHERE
topics_decoded IS NOT NULL
AND in_successful_contract_call = TRUE
AND type_string = 'ContractEventTypeContract'
)
-- Group by type signature, get counts and a sample topics_decoded
SELECT
type_signature,
COUNT(*) AS count,
-- Get a sample of topics_decoded for each type signature
ARRAY_AGG(topics_decoded ORDER BY RAND() LIMIT 1)[OFFSET(0)] AS sample_topics_decoded
FROM
events_with_type_signature
WHERE
type_signature IS NOT NULL
GROUP BY
type_signature
ORDER BY
count DESC;
muxed usage with burns
queries/muxed_usage_with_burns.sql
View on GitHub-- Query to analyze payment operations to asset issuers with and without muxed senders
-- Only includes payments where the asset_issuer equals the to field
-- Counts operations with and without from_muxed, grouped by year
WITH burn_operations AS (
SELECT
o.id,
o.closed_at,
o.type_string,
t.transaction_hash,
JSON_VALUE(details_json.from) AS from_account,
JSON_VALUE(details_json.from_muxed) AS from_muxed,
JSON_VALUE(details_json.to) AS to_account,
JSON_VALUE(details_json.asset_issuer) AS asset_issuer,
FROM
`crypto-stellar.crypto_stellar.history_operations` AS o,
UNNEST([o.details_json]) AS details_json
LEFT JOIN
`crypto-stellar.crypto_stellar.history_transactions` AS t
ON o.transaction_id = t.id
WHERE
o.closed_at IS NOT NULL
AND o.type_string = 'payment'
AND JSON_VALUE(details_json.asset_issuer) IS NOT NULL
AND JSON_VALUE(details_json.to) IS NOT NULL
AND JSON_VALUE(details_json.asset_issuer) = JSON_VALUE(details_json.to)
)
-- Count payments to asset issuers with and without muxed senders by year
SELECT
EXTRACT(YEAR FROM closed_at) AS year,
COUNTIF(from_muxed IS NOT NULL) AS burns_with_muxed_sender,
COUNTIF(from_muxed IS NULL) AS burns_without_muxed_sender,
COUNT(*) AS total_burns,
-- Get the transaction_hash from the most recent burn
ARRAY_AGG(
IF(from_muxed IS NOT NULL, transaction_hash, NULL)
IGNORE NULLS
ORDER BY closed_at DESC
LIMIT 1
)[OFFSET(0)] AS sample_muxed_burn_tx_hash
FROM
burn_operations
GROUP BY
year
ORDER BY
year;
muxed usage with clawback
queries/muxed_usage_with_clawback.sql
View on GitHub-- Query to analyze clawback operations' from and from_muxed fields
-- Grouped by year, showing counts and latest transaction with muxed
WITH clawback_operations AS (
SELECT
o.id,
o.closed_at,
o.type_string,
t.transaction_hash,
JSON_VALUE(details_json.from_muxed) AS from_muxed
FROM
`crypto-stellar.crypto_stellar.history_operations` AS o,
UNNEST([o.details_json]) AS details_json
LEFT JOIN
`crypto-stellar.crypto_stellar.history_transactions` AS t
ON o.transaction_id = t.id
WHERE
o.closed_at IS NOT NULL
AND o.type_string = 'clawback'
)
-- Get aggregations and latest transaction hash with from_muxed account per group
SELECT
EXTRACT(YEAR FROM closed_at) AS year,
COUNTIF(from_muxed IS NOT NULL) AS clawbacks_with_from_muxed,
COUNTIF(from_muxed IS NULL) AS clawbacks_without_from_muxed,
COUNT(*) AS total_clawbacks,
-- Get the transaction_hash from the most recent clawback with a from_muxed account in each year
ARRAY_AGG(
IF(from_muxed IS NOT NULL, transaction_hash, NULL)
IGNORE NULLS
ORDER BY closed_at DESC
LIMIT 1
)[OFFSET(0)] AS sample_muxed_clawback_tx_hash
FROM
clawback_operations
GROUP BY
year
ORDER BY
year;
muxed usage with source account
queries/muxed_usage_with_source_account.sql
View on GitHub-- Query to summarize operations with and without source_account_muxed
-- Grouped by year and operation type_string
-- Includes the most recent transaction_hash that has a muxed account for each group
-- If an operation's source_account is NULL, it falls back to the transaction's account and account_muxed
WITH operations_with_source AS (
SELECT
o.id,
o.closed_at,
o.type_string,
t.transaction_hash,
-- If operation source_account is NULL, use transaction account instead
COALESCE(o.source_account, t.account) AS effective_source_account,
-- If operation source_account_muxed is NULL but source_account is NULL too,
-- then use transaction account_muxed
CASE
WHEN o.source_account IS NULL THEN t.account_muxed
ELSE o.source_account_muxed
END AS effective_source_account_muxed
FROM
`crypto-stellar.crypto_stellar.history_operations` AS o
LEFT JOIN
`crypto-stellar.crypto_stellar.history_transactions` AS t
ON o.transaction_id = t.id
WHERE
o.closed_at IS NOT NULL
)
-- Get aggregations and latest transaction hash with muxed account per group
SELECT
EXTRACT(YEAR FROM closed_at) AS year,
type_string,
COUNTIF(effective_source_account_muxed IS NOT NULL) AS operations_with_muxed,
COUNTIF(effective_source_account_muxed IS NULL) AS operations_without_muxed,
COUNT(*) AS total_operations,
-- Get the transaction_hash from the most recent operation with a muxed account in each group
ARRAY_AGG(
IF(effective_source_account_muxed IS NOT NULL, transaction_hash, NULL)
IGNORE NULLS
ORDER BY closed_at DESC
LIMIT 1
)[OFFSET(0)] AS example_muxed_transaction_hash
FROM
operations_with_source
GROUP BY
year,
type_string
ORDER BY
year,
type_string;