Leigh's Stellar BigQuery Queries

Updated: 5/2/2025, 4:38:29 PM

leighmcculloch/stellar-bigquery

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.sqlView on GitHub
Copy Query & Open BigQuery
-- 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.sqlView on GitHub
Copy Query & Open BigQuery
-- 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.sqlView on GitHub
Copy Query & Open BigQuery
-- 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.sqlView on GitHub
Copy Query & Open BigQuery
-- 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.sqlView 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.sqlView 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.sqlView 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;