<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Defi, Data, Degen</title>
        <link>https://paragraph.com/@evandekim</link>
        <description>undefined</description>
        <lastBuildDate>Thu, 21 May 2026 09:34:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[LLM Benchmark for Move Smart Contract Type Inhabitation Evaluation]]></title>
            <link>https://paragraph.com/@evandekim/llm-benchmark-for-move-smart-contract-type-inhabitation-evaluation</link>
            <guid>4NoQFX6zdDmORqsMBYbR</guid>
            <pubDate>Sun, 04 Jan 2026 18:30:40 GMT</pubDate>
            <description><![CDATA[This report evaluates Google Gemini 3 Flash,OpenAI GPT 5.1/5.2 Anthropic Claude Opus 4.5 and Anthropic Claude Sonnet 4.5 using the Top 25 Type Inhabitation Dataset. The primary objective was to assess the capability of Large Language Models (LLMs) to autonomously generate valid Programmable Transaction Blocks (PTBs) for Move smart contracts on the Sui mainnet.

The most significant finding of this study is the emergence of a "Fog of War" differentiator.]]></description>
            <content:encoded><![CDATA[<h2 style="text-align: justify" id="h-executive-summary" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Executive Summary</h2><p style="text-align: justify">This report evaluates <strong>Google Gemini 3 Flash</strong>, <strong>OpenAI GPT 5.1/5.2</strong>, <strong>Anthropic Claude Opus 4.5</strong>, and <strong>Anthropic Claude Sonnet 4.5</strong> using the <strong>Top 25 Type Inhabitation Dataset</strong>. The primary objective was to assess the capability of Large Language Models (LLMs) to autonomously generate valid Programmable Transaction Blocks (PTBs) for Move smart contracts on the Sui mainnet.</p><p style="text-align: center"><strong>The most significant finding of this study is the emergence of a "Fog of War" differentiator.</strong></p><p style="text-align: justify">The data reveals that the deciding factor between model tiers was not syntax proficiency, but <strong>epistemic management</strong>—the ability to act methodically in the face of incomplete information.</p><ul><li><p style="text-align: justify"><strong>Tier 1 (Claude Opus 4.5, GPT 5.1, GPT 5.2)</strong> models treated missing information as a <em>search problem</em>. They actively utilized progressive disclosure to build a complete mental map of the package dependencies before attempting construction. Consequently, these models achieved a <strong>100% success rate</strong>.</p></li><li><p style="text-align: justify"><strong>Tier 2 (Claude Sonnet 4.5, Gemini 3 Flash/Pro)</strong> models treated missing information as a <em>barrier</em>. When the full context was not immediately visible, these models reacted with hallucination (guessing) or abandonment (stopping early), resulting in success rates below 50%.</p></li></ul><p style="text-align: justify"><strong>Binary Reliability Cliff</strong> Crucially, the performance data exhibits a radical step-function rather than a gradient. There were no models in the "70-90%" range. This suggests that Move Smart Contract Inhabitation is a <strong>threshold capability</strong>: once a model possesses sufficient epistemic resilience to manage the "Fog of War," it solves the problem completely. Below that threshold, reliability collapses to unusable levels.</p><hr><h2 style="text-align: justify" id="h-benchmark-context-the-inhabitation-task" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Benchmark Context: The Inhabitation Task</h2><p style="text-align: justify">Unlike benchmarks that provide complete source code context, the <strong>Type Inhabitation</strong> benchmark evaluates a model's ability to operate in a low-information environment. The model is placed in a scenario similar to a developer interacting with a closed-source or unverified package: it is given only a package address and a goal.</p><p style="text-align: justify">To succeed, the model must autonomously discover interfaces, resolve type dependencies, and construct a valid Programmable Transaction Block (PTB). This design creates a "dynamic context retrieval" challenge, testing the model's reasoning and search capabilities rather than its ability to recall memorized patterns from open-source repositories.</p><h3 style="text-align: justify" id="h-technical-walkthrough-the-escrow-example" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Technical Walkthrough: The <code>Escrow</code> Example</h3><p style="text-align: justify">To illustrate the technical complexity, consider a specific test case from the dataset. The model is initialized with the following prompt:</p><blockquote><p style="text-align: justify"><strong>Goal:</strong> Inhabit the type <code>0x0f8343240d42fbefdb499f2c316f939aa168bf7b29ab63c31bf6c4bc0ba97fe0::escrow::Escrow</code>.</p></blockquote><p style="text-align: justify">The model has no prior knowledge of this package. The evaluation proceeds in specific technical phases:</p><ol><li><p style="text-align: justify"><strong>Interface Discovery:</strong> The model's first action is to query the interface for the target module. It executes <code>get_module_interface(package="0x0f83...fe0", module="escrow")</code>. The system returns a JSON representation of the bytecode interface, revealing the constructor function signature:</p><pre data-type="codeBlock" text="public fun create(
    arg0: Coin&lt;0x2::sui::SUI&gt;,
    arg1: 0x0f8343240d42fbefdb499f2c316f939aa168bf7b29ab63c31bf6c4bc0ba97fe0::lock::Lock
): Escrow
"><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">create</span><span class="hljs-params">(
    arg0: <span class="hljs-type">Coin</span>&lt;0<span class="hljs-type">x2</span>::<span class="hljs-type">sui</span>::<span class="hljs-type">SUI</span>&gt;,
    arg1: 0<span class="hljs-type">x0f8343240d42fbefdb499f2c316f939aa168bf7b29ab63c31bf6c4bc0ba97fe0</span>::<span class="hljs-type">lock</span>::<span class="hljs-type">Lock</span>
)</span></span>: Escrow
</code></pre></li><li><p style="text-align: justify"><strong>Recursive Dependency Resolution:</strong> The model identifies that while <code>Coin&lt;SUI&gt;</code> is a standard primitive, the second argument requires a custom type: <code>lock::Lock</code>. This type resides in a separate module within the same package. The model must recognize this dependency and trigger a secondary query: <code>get_module_interface(..., module="lock")</code>.</p></li><li><p style="text-align: justify"><strong>Dependency Graph Construction:</strong> Upon analyzing the <code>lock</code> module, the model locates the constructor for the <code>Lock</code> struct. It then synthesizes a Directed Acyclic Graph (DAG) of operations:</p><ul><li><p style="text-align: justify"><em>Node 1:</em> <code>SplitCoins</code> (Move primitive) → Generates <code>Coin&lt;SUI&gt;</code>.</p></li><li><p style="text-align: justify"><em>Node 2:</em> Call <code>lock::create()</code> → Generates <code>Lock</code>.</p></li><li><p style="text-align: justify"><em>Node 3:</em> Call <code>escrow::create(Node 1, Node 2)</code> → Generates <code>Escrow</code>.</p></li></ul></li><li><p style="text-align: justify"><strong>PTB Generation &amp; Verification:</strong> Finally, the model serializes this logic into a JSON-based PTB schema. The benchmark harness then dry-runs this transaction against the Sui Mainnet state. Success is defined strictly: the transaction must execute without VM errors (e.g., <code>Effects.status.status == success</code>).</p></li></ol><p style="text-align: justify">This workflow demonstrates that "inhabitation" is not merely about writing code; it is about autonomously traversing a graph of type constraints and determining the correct sequence of on-chain operations to satisfy them.</p><hr><h2 style="text-align: justify" id="h-technical-methodology" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Technical Methodology</h2><p style="text-align: justify">The benchmark environment is designed to ensure reproducibility and fair comparison across different models. The complete evaluation harness, including the Rust-based extractor and Docker configurations, is available in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/sui-move-interface-extractor">project repository</a>.</p><h3 style="text-align: justify" id="h-experimental-constraints" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Experimental Constraints</h3><p style="text-align: justify">To rigorously test efficiency and decision-making, each evaluation is subject to strict constraints:</p><ul><li><p style="text-align: justify"><strong>Single Attempt:</strong> The model is given only one opportunity to inhabit a specific package type. There are no "retries" if the final transaction fails.</p></li><li><p style="text-align: justify"><strong>Iteration Limit:</strong> The model is restricted to a maximum of <strong>10 turn-based cycles</strong> (thought/action loops). Within this budget, it must discover all necessary interfaces, resolve dependencies, and generate the final transaction. If it fails to produce a valid result within 10 turns, the run is marked as a failure.</p></li></ul><h3 style="text-align: justify" id="h-bytecode-derived-interface-analysis" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Bytecode-Derived Interface Analysis</h3><p style="text-align: justify">Source code is purposely withheld to simulate a rigorous "black box" environment. The system extracts interfaces directly from on-chain bytecode, presenting the model with raw struct definitions and function signatures. This tests the model's fundamental understanding of Move's type system and linear logic.</p><h3 style="text-align: justify" id="h-isolated-execution-environment" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Isolated Execution Environment</h3><p style="text-align: justify">Evaluations are conducted within ephemeral Docker containers. This approach ensures:</p><ul><li><p style="text-align: justify"><strong>State Consistency:</strong> The environment is reset after every run, preventing models from retaining information between attempts.</p></li><li><p style="text-align: justify"><strong>Uniform Tooling:</strong> All models interact with the chain using the same set of Rust extractors and Sui CLI binaries.</p></li><li><p style="text-align: justify"><strong>Realistic Network Conditions:</strong> Interaction occurs against the live Sui Mainnet, subjecting models to real-world latency and data structures.</p></li></ul><h3 style="text-align: justify" id="h-progressive-disclosure" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Progressive Disclosure</h3><p style="text-align: justify">A "Progressive Disclosure" strategy is employed where models must explicitly request module interfaces. This allows for observation of the model's search efficiency. It enables tracking whether a model navigates the dependency tree logically—requesting only what is necessary—or if it struggles to form a coherent mental map of the package structure.</p><hr><h2 style="text-align: justify" id="h-results" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Results</h2><table style="min-width: 75px"><colgroup><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p style="text-align: justify">Model</p></th><th colspan="1" rowspan="1"><p style="text-align: justify">Success Rate</p></th><th colspan="1" rowspan="1"><p style="text-align: justify">Avg Latency</p></th></tr><tr><td colspan="1" rowspan="1"><p style="text-align: justify"><strong>Claude Opus 4.5</strong></p></td><td colspan="1" rowspan="1"><p style="text-align: justify"><strong>100%</strong></p></td><td colspan="1" rowspan="1"><p style="text-align: justify"><strong>24.3s</strong></p></td></tr><tr><td colspan="1" rowspan="1"><p style="text-align: justify"><strong>OpenAI GPT 5.2</strong></p></td><td colspan="1" rowspan="1"><p style="text-align: justify"><strong>100%</strong></p></td><td colspan="1" rowspan="1"><p style="text-align: justify">60.0s</p></td></tr><tr><td colspan="1" rowspan="1"><p style="text-align: justify"><strong>OpenAI GPT 5.1</strong></p></td><td colspan="1" rowspan="1"><p style="text-align: justify"><strong>100%</strong></p></td><td colspan="1" rowspan="1"><p style="text-align: justify">51.7s</p></td></tr><tr><td colspan="1" rowspan="1"><p style="text-align: justify">Claude Sonnet 4.5</p></td><td colspan="1" rowspan="1"><p style="text-align: justify">48%</p></td><td colspan="1" rowspan="1"><p style="text-align: justify">40.1s</p></td></tr><tr><td colspan="1" rowspan="1"><p style="text-align: justify">Gemini 3 Flash</p></td><td colspan="1" rowspan="1"><p style="text-align: justify">44%</p></td><td colspan="1" rowspan="1"><p style="text-align: justify">12.1s</p></td></tr><tr><td colspan="1" rowspan="1"><p style="text-align: justify">Gemini 3 Pro</p></td><td colspan="1" rowspan="1"><p style="text-align: justify">8%</p></td><td colspan="1" rowspan="1"><p style="text-align: justify">45.8s</p></td></tr></tbody></table><h3 style="text-align: justify" id="h-performance-tiers" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Performance Tiers</h3><p style="text-align: justify">The results indicate a binary distribution in capabilities.</p><ul><li><p style="text-align: justify"><strong>Tier 1 (High Reliability):</strong> The GPT-5 series and Claude Opus 4.5 demonstrated robust reasoning capabilities, effectively navigating the "unknown" environment without hallucinations or logical errors.</p></li><li><p style="text-align: justify"><strong>Tier 2 (Emerging Capability):</strong> Other models showed promise on simpler tasks but faced significant difficulties when dependency chains extended beyond a single layer.</p></li></ul><hr><h2 style="text-align: justify" id="h-analysis-of-behavioral-patterns" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Analysis of Behavioral Patterns</h2><p style="text-align: justify">To better understand the divergence in performance, model behaviors are analyzed across two dimensions: by <strong>Model Family</strong> and by <strong>Performance Tier</strong>.</p><h3 style="text-align: justify" id="h-comparative-analysis-by-model-family" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Comparative Analysis by Model Family</h3><p style="text-align: justify"><strong>OpenAI (GPT-5 Series)</strong></p><ul><li><p style="text-align: justify"><strong>Search Strategy:</strong> Methodical and exhaustive. These models systematically request every potential dependency before attempting construction, favoring complete information over speed.</p></li><li><p style="text-align: justify"><strong>Error Mode:</strong> Over-caution. Occasionally, they refuse to proceed if a specific permission object is missing, marking the task as "uninhabitable" rather than attempting a partial solution.</p></li><li><p style="text-align: justify"><strong>Latency Profile:</strong> High (50-60s), reflecting the exhaustive verification process.</p></li></ul><p style="text-align: justify"><strong>Anthropic (Claude Series)</strong></p><ul><li><p style="text-align: justify"><strong>Search Strategy:</strong> Precision-targeted. These models request only the immediate dependencies. Opus resolves deeper layers effectively, while Sonnet often stops early if the path is not obvious.</p></li><li><p style="text-align: justify"><strong>Error Mode:</strong> Under-exploration (Sonnet). Sonnet tends to abandon complex dependency trees early. Opus shows no significant error mode.</p></li><li><p style="text-align: justify"><strong>Latency Profile:</strong> Varied. Opus is highly efficient (~24s), while Sonnet is slower (~40s) due to less decisive planning.</p></li></ul><p style="text-align: justify"><strong>Google (Gemini Series)</strong></p><ul><li><p style="text-align: justify"><strong>Search Strategy:</strong> Heuristic and speculative. These models frequently attempt to "guess" interfaces or parameters to save steps, leading to high speed but high hallucination rates.</p></li><li><p style="text-align: justify"><strong>Error Mode:</strong> Hallucination. They frequently invent function names or misinterprets parameter types (e.g., passing strings where objects are required).</p></li><li><p style="text-align: justify"><strong>Latency Profile:</strong> Bimodal. Flash is extremely fast (~12s) due to skipping verification, while Pro is slow (~45s) due to analysis loops.</p></li></ul><h3 style="text-align: justify" id="h-comparative-analysis-by-performance-tier" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Comparative Analysis by Performance Tier</h3><p style="text-align: justify"><strong>Context Management</strong></p><ul><li><p style="text-align: justify"><strong>Tier 1 (Opus 4.5, GPT 5.1/5.2):</strong> Maintains a dynamic map of the package structure. These models can backtrack and revise plans without losing context as new modules are revealed.</p></li><li><p style="text-align: justify"><strong>Tier 2 (Sonnet 4.5, Gemini Flash/Pro):</strong> Struggles with fragmented views. These models often fail to integrate new information with previous findings, "forgetting" dependencies discovered in earlier steps.</p></li></ul><p style="text-align: justify"><strong>Dependency Resolution</strong></p><ul><li><p style="text-align: justify"><strong>Tier 1:</strong> Employs a depth-first search approach. They successfully resolve nested dependencies (A requires B, B requires C) and treat the graph as a recursive logical puzzle.</p></li><li><p style="text-align: justify"><strong>Tier 2:</strong> Relies on shallow pattern matching. While effective at direct instantiations (A requires integer), they fail when dependencies are recursive or require cross-module lookups.</p></li></ul><p style="text-align: justify"><strong>Self-Correction</strong></p><ul><li><p style="text-align: justify"><strong>Tier 1:</strong> Proactive. These models catch schema errors <em>before</em> submitting the transaction, verifying that arguments match expected types.</p></li><li><p style="text-align: justify"><strong>Tier 2:</strong> Reactive or absent. They often submit invalid transactions and rely on error messages to guess the fix, or get stuck in a loop repeating the same mistake.</p></li></ul><hr><h2 style="text-align: justify" id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p style="text-align: justify">The data indicates that <strong>Tier 1 models (Claude Opus 4.5, GPT 5.1, GPT 5.2)</strong> have reached a maturity level suitable for production-grade automation of Move smart contracts. Among these, <strong>Claude Opus 4.5</strong> stands out for achieving this reliability with the lowest latency.</p><p style="text-align: justify">The central finding of this research is that the performance gap is not driven by coding ability, but by <strong>search strategy</strong>. Tier 1 models successfully leveraged progressive disclosure to illuminate the "Fog of War," constructing valid mental models from partial information. Tier 2 models, unable to sustain this state of uncertainty, resorted to guessing or abandonment. This suggests that for autonomous agents, <strong>epistemic resilience</strong>—the ability to act methodically in the face of incomplete information—is the critical capability gap to bridge.</p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>llm</category>
            <category>agentic</category>
            <category>evaluations</category>
            <category>move</category>
            <category>sui</category>
            <category>gemini</category>
            <category>opus</category>
            <category>openai</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/711170fc33a41b405359ee8dbc273eeda3c41973831b10e36c0c9b310885d7c4.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Formal Verification with Agentic Coding for Less Than $1]]></title>
            <link>https://paragraph.com/@evandekim/formal-verification-with-agentic-coding-for-less-than-dollar1</link>
            <guid>dbsJbt9x3D1UByoPbeNK</guid>
            <pubDate>Tue, 16 Dec 2025 00:06:44 GMT</pubDate>
            <description><![CDATA[The SetupTime: ~1 hour | Cost: < $1 Setup: Claude Code + Factory CLI (Big shoutout for the mathematical rendering!) Result: Mathematically rigorous proof of CDF monotonicity I built move-gaussian, an on-chain Gaussian distribution library for Sui Move. The library provides CDF, PDF, PPF (inverse CDF), and sampling functions—core statistical primitives living natively on-chain. I wanted to formally verify the library's mathematical properties. The sui-prover handled most verification tasks: ov...]]></description>
            <content:encoded><![CDATA[<h2 id="h-the-setup" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Setup</h2><p><strong>Time</strong>: ~1 hour | <strong>Cost</strong>: &lt; $1</p><p>Setup: Claude Code + <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://factory.ai/product/cli">Factory CLI</a> (Big shoutout for the mathematical rendering!)</p><p><strong>Result</strong>: Mathematically rigorous proof of CDF monotonicity</p><p>I built <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/move-gaussian/tree/main">move-gaussian</a>, an on-chain Gaussian distribution library for Sui Move. The library provides CDF, PDF, PPF (inverse CDF), and sampling functions—core statistical primitives living natively on-chain.</p><p>I wanted to formally verify the library's mathematical properties. The sui-prover handled most verification tasks: overflow safety (45 specs), arithmetic primitives, and point evaluations. But one property stood out as both critical and difficult: <strong>proving the CDF is monotonically increasing</strong>.</p><h3 id="h-why-monotonicity-matters" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Why Monotonicity Matters</h3><p>A CDF (Cumulative Distribution Function) answers: "What's the probability that a random variable is less than or equal to z?" By definition, this probability must increase as z increases—you're accumulating more probability mass as you move right on the number line, never less. If a CDF implementation <em>decreases</em> anywhere, it's not a valid CDF.</p><p>For a Gaussian CDF specifically, monotonicity is load-bearing. It guarantees the inverse CDF (PPF) is well-defined and unique, which means sampling via inverse transform actually works. It ensures probability intervals make sense—P(a &lt; X ≤ b) = CDF(b) - CDF(a) must be non-negative. Without monotonicity, the entire statistical machinery breaks down.</p><hr><h2 id="h-what-actually-happened" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What Actually Happened</h2><p>The implementation uses rational polynomial approximation: degree-12 polynomials P(z) and Q(z), evaluated via Horner's method in fixed-point arithmetic. My first attempt was direct SMT verification with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/asymptotic-code/sui-prover">sui-prover</a>, built on Boogie and Z3.</p><p>It failed immediately. The prover explores <em>all possible</em> intermediate values—not just the ones our coefficients produce—and found theoretical overflow paths:</p><pre data-type="codeBlock" text="error: code should not abort
    ┌─ ./sources/math.move:154:8
    │
154 │     (a * x) / SCALE
    │        ^
    │
    =     at ./sources/erf.move:233: horner_eval_p
    =         a = 115792089237316195423570985008687907853269984665640564039458...
"><code>error: code should not abort
    ┌─ ./sources/math.move:154:8
    │
154 │     (a * x) / SCALE
    │        ^
    │
    =     at ./sources/erf.move:233: <span class="hljs-attr">horner_eval_p</span>
    =         <span class="hljs-attr">a</span> = <span class="hljs-number">115792089237316195423570985008687907853269984665640564039458</span>...
</code></pre><p>That <code>a</code> value is near u256 max—a path the real code never takes, but Z3 doesn't know that. Even switching to real arithmetic (sui-prover's <code>to_real()</code> bypasses overflow), Z3's nonlinear real arithmetic theory times out around degree 6-8. Our problem is degree 22.</p><p>My mathematical intuition said: <em>stop verifying the computation, verify the algebra</em>. For monotonicity, we need CDF'(z) ≥ 0. For a rational function P/Q, this means proving N(z) = P'(z)·Q(z) - P(z)·Q'(z) ≥ 0—an algebraic problem, not a numerical one.</p><p>I prompted Claude toward algebraic approaches. It searched external documentation and surfaced <strong>Sturm's Theorem</strong> (1829)—a classical result for counting polynomial roots in an interval. The theorem is elegant: build the Sturm sequence (polynomial remainder sequence), count sign changes at interval endpoints, and the difference equals the number of roots. If N(z) has zero roots in [0, 6] and N(0) &gt; 0, then N(z) &gt; 0 everywhere.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e68496a38d14f24547256c9d31561878dfffd8a80767cd6b33a64f875686d10d.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAaCAIAAABZ+cloAAAACXBIWXMAABYlAAAWJQFJUiTwAAAFr0lEQVR4nLWWW2wbVR7Gx7eMJ5O5+Mz1HNsz47E9Ho/tjDuZ4rp1Ji7TpO6kxq2J01JCm9KSpbApKGhT0NJW3SC0fVikQqVy3X1opa52tS274qGlUlElKoEQEkJC4gHE5QWJF95QBQ9BidtK3S1I7GZ/+h5GOufM99f/fGfmYFld3dbyi6Zh26X7u+0Rp7p1q39/t00QcQzDwuFI+BahcAj7L4BQHq5YmqZqmgqhZOQzSSRpCjKy6QTL3Dk3FArFQyEqEgMxQoxTiGDScUYlEnpfcVqN02l8CMXiUiTGhSNsKERiCEqOXTTymbyuyrJsFtfnrcbm4MDuAyc2bp03Rg427lsK9p3Z8Ztzvcf+sWv+X/uOXD3w7LsHnnl3dvHa3qeuzhy+8uDhy7t/+1bv0IXp+Td7j/29O3d+++xfJna93Lzvhfr4EpavtFrdp+7tHDu4eOGPf/70/LUbf732w/Uvlj/4Zvnr5eXPbix/fmP5yx+X33zvx6XXPnrln1/NP3fpiecu7T98Zvfs0mTvmf2PHH9gZnH3wye6e4/ZjT1u86ERb0+1satS62VK25XiJHbs9Cdn3/7uzMWvzl355p2Pv7/84benz19/9W/X37jw/slXLy8sndv7xItbuk+7Y3PVjQ9tmjioFidzw51scVzPb9Ly9artlUsbzVJDNTYA2eChkRCzDK+xvEYDhQZpbGxy/ujJ88+/dLG3/+iZs1eC6YVqY7pSn9LtbWJ2lE668URxkCuysMxIRVos0Hw2PiTHB0WCkodYNEBKq+JjBEdQAkHxOAkGVhUjElGcxeTsaG9mcXbu2eFacPDxo9N7n0xAS0gP03yGAirJpmNxcaQ+/vzJ0797+sT8wtHZR540rPVmucZLSiRGx3A2htPRGBPDqWhsKLKqcJQMR4hwhAiFcYzmtd7Mo4/O/15WSu3uvj+den3d+s179j2eENJRPBEjQCTGiFCZaO3Y5E2oumlXa+MT2+/1t6GkimGxcJQIhQduC8OitxS+qQGS03KVtG4RlIBSOdNyAS+jVG4Ap8ORwZVyIoMrhWChW2tuRvZO9YdCd1E4Mri9vXPU20IzXMGs7Oz2SmVbltFYczyR4FcnYbIMg6Cdy+U9b6zbnZqYaKmKalmlVqsVBJOmaf7SQRskKVGUeV6ME+QQxcgylFaQBUEEgMPxOEkOURQlSRJYBULIrSKJIsdxgiAwzL+dxzvJ5QqbN29h2QRF0QQxSJKkYRSGbdu21/n+lqmpqWazWS6Xp6enO52Obdu+7zuO0+l0Wq2W67qO42iaFg7fbt1/kEqhDbWRkmValplMItsuZTS1apfXu9VcLoP97yAEHWc4n8tmdUWWpeGKlUohw8iWLENRUmtgILKUJvMMwzYaDc/zEEpallW0VgiCwDRNwzA8z3Mcp91uu65rmqbnedoqtVqNJMloNFqv19vtNkKoVqs1m03P82zb9jwPx3GMpig+wZIkCSEUBIEkSUkUIYQAAIQQx3EkSTIMAwCQRBEAIAgChJBl2f5WkyRJEAQAoP8sCALLsv2dBwBEo1Esl88vHjkSBEEqlfJ9f25urtVq+b5fr9dd19U0jWVZiqIEQVjJkigihBzH6Zfsuu6hQ4fq9brneSzL3r1FOI4LgoAQoijK9/2FhYVOpwMAWIPu98FxnKIoWZYBAPV6fWZmJggCjuOwtYIgCFmWFUUBAARBcOrUKV3XV3+WPx/tX0U0GmUYBkIEAPC8sePH/7DGBgzD3A5D32/NXt0HIaQoaj9z2P8DCOUNNadQMAqF3D33OBDKo6MbyuXimhnwPFcpm7quIoSKpiEIvGFkUyl0+5N/V37FDSkpJDJQqOrJSgYhIVFIQ1tDuZSUS0nlTDKfkmtFTYNiUUX5lKxBsZxJGgrcWNLLmaSOJCRyOpIa5axjKKYCy5kkPUg07Tygyb7BT4eEJrnzyKSdAAAAAElFTkSuQmCC" nextheight="1858" nextwidth="2246" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Claude generated a Python script using SymPy with exact rational arithmetic. No floating-point errors. No numerical instability. Just pure polynomial algebra: V(0) = 11 sign changes, V(6) = 11 sign changes, root count = 0, and N(0) &gt; 0.</p><p><strong>Theorem verified</strong>: N(z) &gt; 0 for all z ∈ [0, 6]. Therefore CDF is monotonically increasing. </p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/97d9555966d8acbd8567f4b414fe2448ad4a8ce3a54a95556755cbe921bd70c0.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAMCAIAAACMdijuAAAACXBIWXMAABYlAAAWJQFJUiTwAAADEElEQVR4nKWRv2/bRhTHKYWUfLIpE1JEnc6mZZ9MH02JsnkSrbClXJ+NKC3z45JA19S0ARqxWphdVKDx4MkoUBToj7F7tw4diwJZMuRv6GZ06JKhU+cCHVKYTNAE6dL2gy8e3vdw9/DeO+no5KuLZ89/fPLL46cXv//x/PHTi8+//v7X3/786cnPn3353Tff/mDT69Cw0HJbXyA6MnW0Wketir5Y0Re0amO+0pivQK0K5zVd1fSSWi2p1SLQikBTCqpSUKUVazA9/eL+fnJ7fLwX7g+GN1ukPwofXBuGa3Z/0xs2FluX7ytQ1epqWmVOrc6qFVDSZsB8EagzoDwDyrJSKhRmC4VZRQGpipmkir6wH388OfkkPjrJ5+dk5VK5XFFWZvN5kMsVZKWUCsgKuCIXJCmfKve6pJfxDdRydfctv9PuHsbJbf7+/sHRWBzGRx+Z5nrfG9y9O+57A8/bGg63Ke11nK4QDzi/F0VRGg8opWF4Uwjh+74Q4tGjTznncRxPp9PT01PXdaU5tdzvdsxWa6vXsyxrNHqX0h6lPULWu92NbndjONwmhFBKM+unMMY8z3Mcx3XdzLophBD8EkIIQkgql8v+Znu9tXIn2CKr+OHkw73dPYxxFEVJkjDGkiSxbZtzHgQB5zyKoslkcn5+zjl3XVcIkfWbXZBl+R+2RIj18PiYUmrbNmMsCAIhBEIoDMMgCBhjGOMwDBlj0n8i5ziO7/uu62KMHcdhKRjjbPYgCBzHsVMghAghXdezxDAMCKFhGAghCGGz2czOMwsAuCyvadpkMmGMxXGMMbZtO0mSw4MDxpjv+9lCsi15nheG4XQ6HY1GnHMhRJYnSSKE4JyfnZ1lP5wkCeccQvhihI7j3B+Pl4yld3Z24jh+OwiazeZodCMbRfr/tFaW6YazZCxaa6bTtk1zdXEBrZnmmmm2Later9Vr1UZdb9R1WKsBAK7IfyNJ0ivuNV5UL4GZXsfa26LMc6/7fdvE5vISbZMuaa2vrvib9u61HhvQD27s3Nr23xsOalcr/6r9vwCADLs5xB+UCQAAAABJRU5ErkJggg==" nextheight="1252" nextwidth="3308" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><hr><h2 id="h-reflection" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Reflection</h2><p>What struck me most was Claude's role as a <em>living encyclopedia of mathematical knowledge</em>. It's not always 100% correct, but it seems to have an answer for everything if you ask the right question. The human provides domain expertise, quality control, and strategic direction. I knew to look for algebraic approaches when Z3 failed, and I could verify the proof structure made sense. The AI provides breadth of knowledge, implementation speed, and pattern matching across domains, surfacing Sturm's theorem, generating a working Python script in minutes, connecting dots for me while I point out the direction.</p><p>But this only works because we're not blindly trusting AI output. The proof stands on Sturm's theorem, which has 200 years of mathematical scrutiny behind it. The implementation uses Python and SymPy, decades-old tools with extensive testing, and exact rational arithmetic that avoids floating-point uncertainty entirely. The sui-prover verified the supporting facts: overflow bounds (45 specs, all passing), point evaluations, and arithmetic primitive correctness. Claude found the path; the path itself is ancient and trusted.</p><p>The formal verification took an hour, and it was simultaneously a learning experience, an application experience, and a research experiment. These categories blur together now. The distinction between "learning" and "doing" feels increasingly blurry when AI can collapse the gap between knowing something exists and implementing it immediately.</p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/447116de8306ce67c217a99cd8b4d9193d66ee037d58a513a85475761c38805b.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[On-chain Atomic Gaussian Math ]]></title>
            <link>https://paragraph.com/@evandekim/on-chain-atomic-gaussian-math</link>
            <guid>31o6StqKYWR7B5fGsWYv</guid>
            <pubDate>Thu, 11 Dec 2025 02:52:40 GMT</pubDate>
            <description><![CDATA[GaussianMove uses the AAA algorithm to generate near-optimal rational approximations offline, then evaluates them on-chain via Horner's method—achieving CDF error of 3.35×10⁻⁹ and PPF error of 3.11×10⁻¹³ with predictable gas costs. Sui's native sui::random then makes Gaussian sampling operationally simple inside a single transaction. This article describes the constraints, the approximation methods, and several applications once these functions are available as ordinary library calls. ]]></description>
            <content:encoded><![CDATA[<h2 id="h-tldr" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">TL;DR</h2><p>GaussianMove uses the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arxiv.org/abs/1612.00337">AAA algorithm</a> to generate near-optimal rational approximations offline, then evaluates them on-chain via <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Horner%27s_method">Horner's method</a>—achieving CDF error of 3.35×10⁻⁹ and PPF error of 3.11×10⁻¹³ with predictable gas costs. Sui's native <code>sui::random</code> then makes Gaussian sampling operationally simple inside a single transaction. This article describes the constraints, the approximation methods, and several applications once these functions are available as ordinary library calls.</p><p>On-chain, Gaussian machinery is usually pushed off to oracles, hidden inside off-chain engines, or avoided entirely because gas and fixed-point arithmetic make it challenging. GaussianMove asks a simple question:</p><blockquote><p>What does it actually take to compute Φ and Φ⁻¹ on Sui when all you have are integers, fixed-point scaling, and strict gas limits?</p></blockquote><p>We’ll start from the mathematical constraints, choose an approximation strategy that is compatible with those constraints, and then plug the resulting primitives into familiar objects like Black–Scholes, VaR, and Gaussian-shaped AMMs.</p><hr><h2 id="h-deployed-packages-sui-testnet" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Deployed Packages (Sui Testnet)</h2><p>GaussianMove and its companion Black-Scholes package are deployed on Sui testnet:</p><table style="min-width: 100px"><colgroup><col><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Package</p></th><th colspan="1" rowspan="1"><p>Version</p></th><th colspan="1" rowspan="1"><p>Package ID</p></th><th colspan="1" rowspan="1"><p>Explorer</p></th></tr><tr><td colspan="1" rowspan="1"><p><strong>gaussian</strong></p></td><td colspan="1" rowspan="1"><p>v0.9.0</p></td><td colspan="1" rowspan="1"><p><code>0x66f9087a3d9ae3fe07a5f3c1475d503f1b0ea508d3b83b73b0b8637b57629f7f</code></p></td><td colspan="1" rowspan="1"><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://suiscan.xyz/testnet/object/0x66f9087a3d9ae3fe07a5f3c1475d503f1b0ea508d3b83b73b0b8637b57629f7f">View</a></p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>black_scholes</strong></p></td><td colspan="1" rowspan="1"><p>v0.2.0</p></td><td colspan="1" rowspan="1"><p><code>0x1637ddc0495a8833ebd580224dad7154dfb33477f73d2c7fb41e2b350efa55b3</code></p></td><td colspan="1" rowspan="1"><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://suiscan.xyz/testnet/object/0x1637ddc0495a8833ebd580224dad7154dfb33477f73d2c7fb41e2b350efa55b3">View</a></p></td></tr></tbody></table><p><strong>Repositories:</strong></p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/move-gaussian">move-gaussian</a> — Core Gaussian library (399 tests)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/move-black-scholes">move-black-scholes</a> — European options pricing (83 tests)</p></li></ul><hr><h2 id="h-part-i-the-mathematical-problem" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Part I: The Mathematical Problem</h2><p>Why is Gaussian math hard on-chain? The difficulty is not just gas; it’s that you only get integers, fixed-point scaling, and inverse functions that amplify small errors, which constrains the approximation method choices.</p><h3 id="h-gaussian-math-in-traditional-finance" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Gaussian Math in Traditional Finance</h3><p>Gaussian-based models are standard tools in traditional finance.</p><ul><li><p>The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model">Black–Scholes–Merton model</a> for European options pricing literally contains Φ(d₁) and Φ(d₂), where Φ is the standard normal CDF.</p></li><li><p>Risk measures such as <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Value_at_risk">Value at Risk</a> and Expected Shortfall often assume (log)normal returns and use the inverse CDF Φ⁻¹(α) to convert a confidence level α into a loss threshold.</p></li><li><p>Many factor and term-structure models linearize around Gaussian assumptions even when reality is heavier-tailed.</p></li></ul><p>In banks and brokerages, these functions are evaluated using high-quality numerical libraries (BLAS/LAPACK, Boost, SciPy, etc.), and their error properties are well understood. The question here is what it takes to bring that level of Gaussian math <em>on-chain</em>, where the only primitives are integer arithmetic and fixed-point representations.</p><h3 id="h-from-off-chain-models-to-on-chain-systems" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">From Off-Chain Models to On-Chain Systems</h3><p>DeFi derivatives protocols already lean heavily on Gaussian-style thinking, but most of the heavy numerical work happens off-chain:</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/lyra.eth/JRj-8JInwtW8jp5y6QzyUHq0suTcH_B1iGO7V5LYwVQ">Lyra</a> (~$58M TVL, one of the largest on-chain options protocols) prices options with Black–Scholes-style formulas but relies on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.lyra.finance/lyra-integrates-chainlink-price-feeds/">Chainlink price feeds</a> and off-chain Greeks; it does <em>not</em> evaluate Φ/Φ⁻¹ directly on-chain.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.hegic.co/">Hegic</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dopex.io/">Dopex</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.rysk.finance/">Rysk</a>, and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://valorem.xyz/">Valorem</a> use a mix of RFQ mechanisms, oracles, and simplified pricing rules rather than full on-chain Gaussian math.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ar5iv.labs.arxiv.org/html/2204.14232">Panoptic</a> is explicitly oracle-free but achieves this by deriving option payoffs from <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://uniswap.org/whitepaper-v3.pdf">Uniswap v3</a> LP mechanics instead of computing Gaussian functions.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://primitive.mirror.xyz/l5F1BLMhfmcm_B1R2w-by003_oiAz2-Ir_Jj_rCvGCs">Primitive RMM-01</a> together with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/primitivefinance/solstat">solstat</a> is the clearest EVM example of explicit on-chain Gaussian CDF usage, but the protocol has since closed down.</p></li></ul><p>In practice, most production DeFi options protocols avoid full on-chain Gaussian computation. They either use oracles to import off-chain pricing, derive prices from AMM mechanics, or accept simplified models. The few that attempted full on-chain Black–Scholes (like early Primitive) faced gas costs and complexity that limited adoption.</p><p><strong>So why build this?</strong></p><p>Three reasons:</p><ol><li><p><strong>The landscape is changing.</strong> Sui’s native randomness and lower compute costs make on-chain statistical computation more practical than on EVM.</p></li><li><p><strong>Research infrastructure matters.</strong> Even if production protocols use hybrid approaches, having audited, well-documented Gaussian primitives enables experimentation. The Paradigm <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.paradigm.xyz/2024/11/pm-amm">pm-AMM</a> paper (2024) and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.paradigm.xyz/2024/12/distribution-markets">Distribution Markets</a> work show continued research interest in Gaussian-based mechanisms.</p></li><li><p><strong>The mathematical problem is nontrivial.</strong> Computing Φ⁻¹(p) without floating-point is a substantive applied mathematics problem, independent of any particular application.</p></li></ol><hr><p>The fundamental question is deceptively simple: <strong>how do you compute Φ⁻¹(p) when your only arithmetic primitives are integer addition, subtraction, multiplication, and division?</strong> This is a timeless numerical analysis challenge from the 1960s—blockchain merely adds gas costs and determinism constraints.</p><h4 id="h-key-constraints-for-on-chain-gaussian-math" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Key Constraints for On-Chain Gaussian Math</h4><ul><li><p><strong>Fixed-point arithmetic constraints.</strong> On-chain environments (Solidity, Move, etc.) operate with integers and implicit scaling (typically WAD = 10¹⁸). This introduces overflow risk for <code>(a * b) / SCALE</code>, truncation error in division, and a hard floor on the smallest representable probabilities. Practical accuracy is limited by cumulative rounding, even though u256 offers ~77 decimal digits of intermediate precision.</p></li><li><p><strong>Randomness source.</strong> Gaussian sampling via inverse transform requires a high-quality uniform variate U ∈ (0,1). On EVM/Solana/Aptos this usually comes from external VRF providers such as <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.chain.link/vrf">Chainlink VRF</a> or <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.switchboard.xyz/product-documentation/randomness">Switchboard VRF</a>, adding multi-transaction flows, callbacks, and gas. On Sui, <code>sui::random</code> exposes a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sui.io/references/framework/sui_sui/random">Random</a> object that can be consumed inside a single transaction.</p></li><li><p><strong>Inverse functions amplify errors.</strong> Even if Φ(x) is approximated accurately, inverting it to obtain Φ⁻¹(p) amplifies small forward errors, especially in the tails where d/dp Φ⁻¹(p) grows large. Classical work such as <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.jstor.org/stable/2347330">Algorithm AS 241</a> (Wichura) and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://stackedboxes.org/2017/05/01/acklams-normal-quantile-function/">Acklam's normal quantile function</a> emphasizes careful piecewise design and tail handling; GaussianMove follows the same philosophy with AAA-based piecewise approximations.</p></li></ul><hr><h3 id="h-fixed-point-constraints-working-without-floats" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Fixed-Point Constraints: Working Without Floats</h3><p>Before we talk about algorithms, we need to make peace with a simple fact: on-chain, there are no floats—only integers with a fixed scale. That single choice drives almost every design decision in GaussianMove.</p><p>DeFi universally uses WAD scaling: 1.0 is represented as 10¹⁸. This gives 18 decimal places of precision, sufficient for most financial calculations (basis points are 10⁻⁴), but it also means all transcendental functions (exp, ln, sqrt) must be realized as integer-based polynomial or rational approximations.</p><pre data-type="codeBlock" text="const SCALE: u256 = 1_000_000_000_000_000_000;  // 10^18

/// Fixed-point multiplication: (a × b) / SCALE
public fun mul_wad(a: u256, b: u256): u256 {
    (a * b) / SCALE  // u256 intermediate prevents overflow
}

/// Fixed-point division: (a × SCALE) / b
public fun div_wad(a: u256, b: u256): u256 {
    (a * SCALE) / b
}
"><code><span class="hljs-keyword">const</span> SCALE: u256 = <span class="hljs-number">1_000_000_000_000_000_000</span>;  <span class="hljs-comment">// 10^18</span>

<span class="hljs-comment">/// Fixed-point multiplication: (a × b) / SCALE</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">mul_wad</span><span class="hljs-params">(a: <span class="hljs-type">u256</span>, b: <span class="hljs-type">u256</span>)</span></span>: u256 {
    (a * b) / SCALE  <span class="hljs-comment">// u256 intermediate prevents overflow</span>
}

<span class="hljs-comment">/// Fixed-point division: (a × SCALE) / b</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">div_wad</span><span class="hljs-params">(a: <span class="hljs-type">u256</span>, b: <span class="hljs-type">u256</span>)</span></span>: u256 {
    (a * SCALE) / b
}
</code></pre><h4 id="h-precision-hierarchy" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Precision Hierarchy</h4><p>Understanding where precision is lost is crucial for error analysis:</p><pre data-type="codeBlock" text="Layer                          Precision       Notes
─────────────────────────────────────────────────────
1. Off-chain AAA fitting       ~10⁻¹⁴         Dominated by algorithm tolerance
2. Coefficient quantization    ~10⁻¹⁸         Negligible (WAD has 18 digits)
3. On-chain Horner rounding    ~10⁻¹⁵         ~0.5 ULP per operation
4. WAD representation          10⁻¹⁸          Hard floor
─────────────────────────────────────────────────────
Current achieved:              3.35×10⁻⁹      CDF
Theoretical floor:             ~10⁻¹⁵         
"><code>Layer                          Precision       Notes
─────────────────────────────────────────────────────
<span class="hljs-number">1.</span> Off<span class="hljs-operator">-</span>chain AAA fitting       <span class="hljs-operator">~</span><span class="hljs-number">10</span>⁻¹⁴         Dominated by algorithm tolerance
<span class="hljs-number">2.</span> Coefficient quantization    <span class="hljs-operator">~</span><span class="hljs-number">10</span>⁻¹⁸         Negligible (WAD has <span class="hljs-number">18</span> digits)
<span class="hljs-number">3.</span> On<span class="hljs-operator">-</span>chain Horner rounding    <span class="hljs-operator">~</span><span class="hljs-number">10</span>⁻¹⁵         <span class="hljs-operator">~</span><span class="hljs-number">0</span><span class="hljs-number">.5</span> ULP per operation
<span class="hljs-number">4.</span> WAD representation          <span class="hljs-number">10</span>⁻¹⁸          Hard floor
─────────────────────────────────────────────────────
Current achieved:              <span class="hljs-number">3.35</span>×<span class="hljs-number">10</span>⁻⁹      CDF
Theoretical floor:             <span class="hljs-operator">~</span><span class="hljs-number">10</span>⁻¹⁵         
</code></pre><p>The precision floor (~10⁻¹⁵) comes from accumulated rounding in Horner evaluation: each of the ~11 iterations loses approximately 0.5 ULP (unit in last place), totaling 5-10 ULP. In practical terms, the approximation method is not the bottleneck here—the fixed-point evaluation is.</p><h3 id="h-approximation-theory-why-aaa" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Approximation Theory: Why AAA?</h3><p>Once you accept fixed-point arithmetic and u256 as your playing field, the next question is how to approximate Φ and Φ⁻¹ themselves.</p><h4 id="h-the-landscape-of-approximation-methods" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">The Landscape of Approximation Methods</h4><p>Several approaches exist for approximating Φ and Φ⁻¹:</p><table style="min-width: 100px"><colgroup><col><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Method</p></th><th colspan="1" rowspan="1"><p>Era</p></th><th colspan="1" rowspan="1"><p>Pros</p></th><th colspan="1" rowspan="1"><p>Cons</p></th></tr><tr><td colspan="1" rowspan="1"><p><strong>Taylor series</strong></p></td><td colspan="1" rowspan="1"><p>1700s</p></td><td colspan="1" rowspan="1"><p>Simple, well-understood</p></td><td colspan="1" rowspan="1"><p>Slow convergence, many terms</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Padé approximation</strong></p></td><td colspan="1" rowspan="1"><p>1890s</p></td><td colspan="1" rowspan="1"><p>Better than Taylor for same degree</p></td><td colspan="1" rowspan="1"><p>Non-trivial to compute</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Abramowitz-Stegun</strong></p></td><td colspan="1" rowspan="1"><p>1964</p></td><td colspan="1" rowspan="1"><p>Battle-tested, industry standard</p></td><td colspan="1" rowspan="1"><p>Requires exp(), fixed formulas</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Chebyshev polynomials</strong></p></td><td colspan="1" rowspan="1"><p>1960s</p></td><td colspan="1" rowspan="1"><p>Near-optimal for polynomials</p></td><td colspan="1" rowspan="1"><p>Limited to polynomial (not rational)</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>AAA (Adaptive Antoulas-Anderson)</strong></p></td><td colspan="1" rowspan="1"><p>2018</p></td><td colspan="1" rowspan="1"><p>Near-optimal rational, automatic</p></td><td colspan="1" rowspan="1"><p>Requires offline toolchain</p></td></tr></tbody></table><p>GaussianMove uses AAA because it produces <strong>near-optimal rational approximations automatically</strong>, avoiding the need to hand-tune coefficients for each function.</p><h4 id="h-the-aaa-algorithm" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">The AAA Algorithm</h4><p>AAA (Nakatsukasa et al., 2018) is a greedy algorithm that iteratively builds a barycentric rational approximation:</p><pre data-type="codeBlock" text="r(x) = Σⱼ (wⱼ × fⱼ) / (x - zⱼ)
       ─────────────────────────
       Σⱼ wⱼ / (x - zⱼ)
"><code><span class="hljs-built_in">r</span>(x) = Σⱼ (wⱼ × fⱼ) / (x - zⱼ)
       ─────────────────────────
       Σⱼ wⱼ / (x - zⱼ)
</code></pre><p>Where:</p><ul><li><p><strong>zⱼ</strong> (nodes): Sample points chosen adaptively</p></li><li><p><strong>wⱼ</strong> (weights): Barycentric weights computed by the algorithm</p></li><li><p><strong>fⱼ</strong> (values): Function values at nodes (from high-precision baseline)</p></li></ul><p>The algorithm's key property: it produces approximations close to the theoretical best rational function of a given degree, without requiring manual coefficient tuning. In practice, this lets us treat “pick a good rational approximation” as a design-time task handled by Python, not an on-chain concern.</p><h4 id="h-from-barycentric-to-polynomial-form" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">From Barycentric to Polynomial Form</h4><p>For on-chain evaluation, we convert the barycentric form to explicit polynomials:</p><pre data-type="codeBlock" text="r(x) = P(x) / Q(x) = (p₀ + p₁x + p₂x² + ... + pₙxⁿ) / (q₀ + q₁x + ... + qₘxᵐ)
"><code>r(x) <span class="hljs-operator">=</span> P(x) <span class="hljs-operator">/</span> Q(x) <span class="hljs-operator">=</span> (p₀ <span class="hljs-operator">+</span> p₁x <span class="hljs-operator">+</span> p₂x² <span class="hljs-operator">+</span> ... <span class="hljs-operator">+</span> pₙxⁿ) <span class="hljs-operator">/</span> (q₀ <span class="hljs-operator">+</span> q₁x <span class="hljs-operator">+</span> ... <span class="hljs-operator">+</span> qₘxᵐ)
</code></pre><p>This conversion happens offline. The Move code only evaluates P(x) and Q(x) using Horner's method, then computes their ratio.</p><h4 id="h-comparison-with-morpheus-aptos" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Comparison with Morpheus (Aptos)</h4><p>The Morpheus PM-AMM on Aptos uses a different approach: Abramowitz-Stegun (1964) for CDF and Acklam (2000) for inverse CDF, with Newton-Raphson refinement.</p><table style="min-width: 75px"><colgroup><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Aspect</p></th><th colspan="1" rowspan="1"><p>Morpheus (Aptos)</p></th><th colspan="1" rowspan="1"><p>GaussianMove (Sui)</p></th></tr><tr><td colspan="1" rowspan="1"><p><strong>CDF algorithm</strong></p></td><td colspan="1" rowspan="1"><p>Abramowitz-Stegun polynomial</p></td><td colspan="1" rowspan="1"><p>AAA rational approximation</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>CDF raw error</strong></p></td><td colspan="1" rowspan="1"><p>~7.5×10⁻⁸</p></td><td colspan="1" rowspan="1"><p>~3.35×10⁻⁹ (10× better)</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>PPF algorithm</strong></p></td><td colspan="1" rowspan="1"><p>Acklam + Newton refinement</p></td><td colspan="1" rowspan="1"><p>AAA piecewise + optional Newton</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Requires exp()?</strong></p></td><td colspan="1" rowspan="1"><p>Yes (for PDF, Newton)</p></td><td colspan="1" rowspan="1"><p>Only for Newton refinement</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Code complexity</strong></p></td><td colspan="1" rowspan="1"><p>High (piecewise, multi-algorithm)</p></td><td colspan="1" rowspan="1"><p>Medium (unified pipeline)</p></td></tr></tbody></table><p>Both approaches are valid. Morpheus prioritizes maximum precision with proven classical formulas; GaussianMove prioritizes a unified, auditable pipeline with modern approximation theory.</p><h4 id="h-piecewise-strategy-for-f" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Piecewise Strategy for Φ⁻¹</h4><p>The inverse CDF presents special challenges because its derivative approaches infinity as p → 0 or p → 1. A single rational approximation cannot maintain accuracy across the full domain.</p><p>GaussianMove uses three regions:</p><p><strong>Region 1: Central (0.02 ≥ p ≥ 0.98)</strong></p><ul><li><p>Direct AAA approximation of Φ⁻¹(p)</p></li><li><p>Expected error: ~3.11×10⁻¹³</p></li></ul><p><strong>Region 2: Lower tail (10⁻¹⁰ ≤ p &lt; 0.02)</strong></p><ul><li><p>Transform: t = √(-2 ln(p))</p></li><li><p>Approximate Φ⁻¹ as function of t</p></li><li><p>Expected error: ~2.03×10⁻¹³</p></li></ul><p><strong>Region 3: Upper tail (0.98 &lt; p ≤ 1 - 10⁻¹⁰)</strong></p><ul><li><p>Symmetry: Φ⁻¹(p) = -Φ⁻¹(1-p)</p></li><li><p>Reuses lower tail approximation</p></li></ul><p>This piecewise approach mirrors classical algorithms (Wichura AS 241, Moro/Jäckel) but fits coefficients via AAA rather than reusing floating-point polynomials.</p><p>At this point, we have a clear picture of the constraints (fixed-point, randomness, error amplification) and a modern approximation strategy (AAA + piecewise design). Next we need to quantify how much error is actually left and how it propagates into financial models.</p><hr><h3 id="h-error-analysis-and-bounds" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Error Analysis and Bounds</h3><p>Error bounds link approximation theory to practical finance. If the approximation error is many orders of magnitude smaller than model uncertainty and oracle noise, then from a DeFi perspective the numerical contribution of the approximation is relatively small.</p><h4 id="h-current-error-budget" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Current Error Budget</h4><p>From GaussianMove v0.9.0 coefficient generation:</p><table style="min-width: 75px"><colgroup><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Function</p></th><th colspan="1" rowspan="1"><p>Max Absolute Error</p></th><th colspan="1" rowspan="1"><p>Notes</p></th></tr><tr><td colspan="1" rowspan="1"><p><strong>Φ (CDF)</strong></p></td><td colspan="1" rowspan="1"><p>3.35×10⁻⁹</p></td><td colspan="1" rowspan="1"><p>WAD output ≤ 3.35×10⁹ raw units</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>φ (PDF)</strong></p></td><td colspan="1" rowspan="1"><p>7.61×10⁻¹⁵</p></td><td colspan="1" rowspan="1"><p>Negligible vs WAD quantization</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Φ⁻¹ central</strong></p></td><td colspan="1" rowspan="1"><p>3.11×10⁻¹³</p></td><td colspan="1" rowspan="1"><p>Applies on [0.02, 0.98]</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Φ⁻¹ tail</strong></p></td><td colspan="1" rowspan="1"><p>2.03×10⁻¹³</p></td><td colspan="1" rowspan="1"><p>Inputs clamped to (10⁻¹⁰, 1-10⁻¹⁰)</p></td></tr></tbody></table><p>These errors are validated against mpmath with 100-digit precision baselines.</p><h4 id="h-how-error-propagates-into-financial-models" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">How Error Propagates into Financial Models</h4><p><strong>For VaR calculations:</strong></p><pre data-type="codeBlock" text="VaR(α) = μ + σ × Φ⁻¹(α)
"><code><span class="hljs-selector-tag">VaR</span>(α) = μ + σ × Φ⁻¹(α)
</code></pre><p>With |Φ⁻¹ error| ≤ 3.11×10⁻¹³, the VaR error is:</p><pre data-type="codeBlock" text="|VaR error| ≤ σ × 3.11×10⁻¹³
"><code><span class="hljs-operator">|</span>VaR <span class="hljs-function"><span class="hljs-keyword">error</span>| ≤ σ × 3.11×10⁻¹³
</span></code></pre><p>For σ = $10,000 (typical portfolio volatility), this is $3.11×10⁻⁹—far below any practical threshold.</p><p><strong>For Black-Scholes:</strong> The Greeks (Delta, Gamma, Vega) involve Φ(d₁) and φ(d₁). With CDF error 3.35×10⁻⁹:</p><ul><li><p>Delta error ≤ 3.35×10⁻⁹ (direct)</p></li><li><p>Gamma involves φ, error ≤ 7.61×10⁻¹⁵</p></li><li><p>Vega scales by √T, error remains negligible</p></li></ul><p><strong>Comparison with model uncertainty:</strong></p><ul><li><p>Volatility estimation error: typically 5-20% (10⁻¹ to 10⁰)</p></li><li><p>Oracle price staleness: seconds to minutes of drift</p></li><li><p>Approximation error: 10⁻⁹ to 10⁻¹³</p></li></ul><p><strong>Conclusion</strong>: Approximation error is 6-10 orders of magnitude smaller than model/oracle uncertainty. For DeFi applications, even 10⁻⁸ precision (solgauss level) is overkill.</p><h4 id="h-domain-clamping" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Domain Clamping</h4><p>Probability inputs are clamped to (ε, 1-ε) with ε = 10⁻¹⁰ × WAD. This corresponds to approximately ±6.3σ—beyond which fixed-point arithmetic cannot meaningfully distinguish probabilities.</p><p>Applications requiring fatter tails (e.g., extreme risk modeling) should document this limitation and consider alternative approaches.</p><p>With constraints, approximation strategy, and error budgets in place, we can now switch from “math and theory” to “engineering”: how to turn all of this into a reproducible Python→Move pipeline.</p><hr><h2 id="h-part-ii-the-implementation" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Part II: The Implementation</h2><p>Part II describes how GaussianMove turns the previous section’s numerical choices into concrete code. The design-time Python pipeline finds and validates rational approximations; the runtime Move code evaluates them cheaply and deterministically on-chain.</p><h3 id="h-the-python-to-move-pipeline" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">The Python-to-Move Pipeline</h3><p>GaussianMove follows a "design-time vs runtime" separation:</p><pre data-type="codeBlock" text="┌─────────────────────────────────────────────────────────┐
│  PYTHON (Design-Time / Offline)                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │ 1. Sample function with mpmath (50+ digits)     │    │
│  │ 2. Run AAA algorithm (SciPy)                    │    │
│  │ 3. Convert barycentric → polynomial coeffs      │    │
│  │ 4. Quantize to WAD-scaled integers              │    │
│  │ 5. Validate accuracy, export to Move            │    │
│  └─────────────────────────────────────────────────┘    │
│                           │                             │
│                           ▼                             │
│                  [coefficient arrays]                   │
└───────────────────────────┼─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│  MOVE (Runtime / On-Chain)                              │
│  ┌─────────────────────────────────────────────────┐    │
│  │ 1. Load pre-computed coefficients (constants)   │    │
│  │ 2. Evaluate P(x), Q(x) using Horner's method    │    │
│  │ 3. Compute P(x) / Q(x)                          │    │
│  │ 4. Apply domain clamping and output bounds      │    │
│  └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘
"><code>┌─────────────────────────────────────────────────────────┐
│  PYTHON (Design-Time / Offline)                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │ <span class="hljs-number">1</span>. Sample function with mpmath (<span class="hljs-number">50</span>+ digits)     │    │
│  │ <span class="hljs-number">2</span>. Run AAA algorithm (SciPy)                    │    │
│  │ <span class="hljs-number">3</span>. Convert barycentric → polynomial coeffs      │    │
│  │ <span class="hljs-number">4</span>. Quantize to WAD-scaled integers              │    │
│  │ <span class="hljs-number">5</span>. Validate accuracy, export to Move            │    │
│  └─────────────────────────────────────────────────┘    │
│                           │                             │
│                           ▼                             │
│                  <span class="hljs-selector-attr">[coefficient arrays]</span>                   │
└───────────────────────────┼─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│  MOVE (Runtime / On-Chain)                              │
│  ┌─────────────────────────────────────────────────┐    │
│  │ <span class="hljs-number">1</span>. Load pre-computed coefficients (constants)   │    │
│  │ <span class="hljs-number">2</span>. Evaluate <span class="hljs-selector-tag">P</span>(x), <span class="hljs-selector-tag">Q</span>(x) using Horner's method    │    │
│  │ <span class="hljs-number">3</span>. Compute <span class="hljs-selector-tag">P</span>(x) / <span class="hljs-selector-tag">Q</span>(x)                          │    │
│  │ <span class="hljs-number">4</span>. Apply domain clamping and output bounds      │    │
│  └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘
</code></pre><p><strong>Why this matters for auditability</strong>: All numerically delicate work (node selection, coefficient fitting, convergence analysis) happens in a Python toolchain that can be inspected, re-run, and validated. The on-chain Move code is a small, predictable evaluation kernel.</p><h3 id="h-horners-method-on-polynomial-evaluation" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Horner's Method: O(n) Polynomial Evaluation</h3><p>Evaluating P(x) = c₀ + c₁x + c₂x² + ... + cₙxⁿ naively requires O(n²) multiplications (computing x², x³, etc.). Horner's method restructures as:</p><pre data-type="codeBlock" text="P(x) = c₀ + x(c₁ + x(c₂ + ... + x(cₙ)))
"><code>P(x) <span class="hljs-operator">=</span> c₀ <span class="hljs-operator">+</span> x(c₁ <span class="hljs-operator">+</span> x(c₂ <span class="hljs-operator">+</span> ... <span class="hljs-operator">+</span> x(cₙ)))
</code></pre><p>This is O(n)—one multiplication and one addition per term.</p><pre data-type="codeBlock" text="/// Horner's method for polynomial evaluation
public fun horner_eval(x: u256, coeffs: &amp;vector&lt;u256&gt;): u256 {
    let n = vector::length(coeffs);
    if (n == 0) { return 0 };
    
    // Start with highest-degree coefficient
    let mut result = *vector::borrow(coeffs, n - 1);
    
    // Work backwards: result = result × x + coeffs[i]
    let mut i = n - 1;
    while (i &gt; 0) {
        i = i - 1;
        result = mul_wad(result, x);
        result = result + *vector::borrow(coeffs, i);
    };
    
    result
}
"><code><span class="hljs-comment">/// Horner's method for polynomial evaluation</span>
<span class="hljs-keyword">public</span> fun horner_eval(x: u256, coeffs: <span class="hljs-operator">&amp;</span>vector<span class="hljs-operator">&lt;</span>u256<span class="hljs-operator">&gt;</span>): u256 {
    let n <span class="hljs-operator">=</span> vector::length(coeffs);
    <span class="hljs-keyword">if</span> (n <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) { <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> };
    
    <span class="hljs-comment">// Start with highest-degree coefficient</span>
    let mut result <span class="hljs-operator">=</span> <span class="hljs-operator">*</span>vector::borrow(coeffs, n <span class="hljs-operator">-</span> <span class="hljs-number">1</span>);
    
    <span class="hljs-comment">// Work backwards: result = result × x + coeffs[i]</span>
    let mut i <span class="hljs-operator">=</span> n <span class="hljs-operator">-</span> <span class="hljs-number">1</span>;
    <span class="hljs-keyword">while</span> (i <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) {
        i <span class="hljs-operator">=</span> i <span class="hljs-operator">-</span> <span class="hljs-number">1</span>;
        result <span class="hljs-operator">=</span> mul_wad(result, x);
        result <span class="hljs-operator">=</span> result <span class="hljs-operator">+</span> <span class="hljs-operator">*</span>vector::borrow(coeffs, i);
    };
    
    result
}
</code></pre><p>For degree-11 polynomials (typical for GaussianMove), this is 11 iterations—predictable gas cost, numerical stability, and minimal intermediate overflow risk.</p><h3 id="h-signed-wad-for-negative-quantiles" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Signed WAD for Negative Quantiles</h3><p>The standard normal distribution is symmetric around zero, so Φ⁻¹(p) can be negative. Move lacks native signed integers, so GaussianMove uses a <code>SignedWad</code> type:</p><pre data-type="codeBlock" text="public struct SignedWad has copy, drop, store {
    magnitude: u256,
    negative: bool,
}
"><code><span class="hljs-keyword">public</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">SignedWad</span> has copy, drop, store {
    magnitude: u256,
    negative: <span class="hljs-type">bool</span>,
}
</code></pre><p>Operations track sign explicitly:</p><pre data-type="codeBlock" text="public fun add(a: SignedWad, b: SignedWad): SignedWad {
    if (a.negative == b.negative) {
        // Same sign: add magnitudes
        SignedWad { magnitude: a.magnitude + b.magnitude, negative: a.negative }
    } else {
        // Different signs: subtract smaller from larger
        if (a.magnitude &gt;= b.magnitude) {
            SignedWad { magnitude: a.magnitude - b.magnitude, negative: a.negative }
        } else {
            SignedWad { magnitude: b.magnitude - a.magnitude, negative: b.negative }
        }
    }
}
"><code><span class="hljs-keyword">public</span> fun add(a: SignedWad, b: SignedWad): SignedWad {
    <span class="hljs-keyword">if</span> (a.negative <span class="hljs-operator">=</span><span class="hljs-operator">=</span> b.negative) {
        <span class="hljs-comment">// Same sign: add magnitudes</span>
        SignedWad { magnitude: a.magnitude <span class="hljs-operator">+</span> b.magnitude, negative: a.negative }
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Different signs: subtract smaller from larger</span>
        <span class="hljs-keyword">if</span> (a.magnitude <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> b.magnitude) {
            SignedWad { magnitude: a.magnitude <span class="hljs-operator">-</span> b.magnitude, negative: a.negative }
        } <span class="hljs-keyword">else</span> {
            SignedWad { magnitude: b.magnitude <span class="hljs-operator">-</span> a.magnitude, negative: b.negative }
        }
    }
}
</code></pre><h3 id="h-v090-api-design-decisions" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">v0.9.0 API Design Decisions</h3><p><strong>Strict PPF Domain Validation</strong></p><p>The <code>ppf(p)</code> function enforces strict domain validation:</p><pre data-type="codeBlock" text="// Aborts with EProbOutOfDomain (302) if p is outside valid domain
let z = core::ppf(0);  // Aborts! p=0 is outside (EPS, 1-EPS)

// For sampling, use ppf_from_u64 which maps any u64 to valid domain
let z = core::ppf_from_u64(random_seed);  // Always succeeds
"><code><span class="hljs-comment">// Aborts with EProbOutOfDomain (302) if p is outside valid domain</span>
<span class="hljs-keyword">let</span> <span class="hljs-variable">z</span> = core::<span class="hljs-title function_ invoke__">ppf</span>(<span class="hljs-number">0</span>);  <span class="hljs-comment">// Aborts! p=0 is outside (EPS, 1-EPS)</span>

<span class="hljs-comment">// For sampling, use ppf_from_u64 which maps any u64 to valid domain</span>
<span class="hljs-keyword">let</span> <span class="hljs-variable">z</span> = core::<span class="hljs-title function_ invoke__">ppf_from_u64</span>(random_seed);  <span class="hljs-comment">// Always succeeds</span>
</code></pre><p><strong>Rationale:</strong> Explicit failure surfaces invalid inputs immediately rather than silently clamping. For sampling use cases, <code>ppf_from_u64(seed)</code> provides a safe alternative that maps any u64 into the valid domain.</p><p><strong>SignedWad Fields</strong></p><p>The <code>SignedWad</code> struct uses short field names for ergonomics:</p><pre data-type="codeBlock" text="public struct SignedWad has copy, drop, store {
    mag: u256,   // magnitude (absolute value)
    neg: bool,   // true if negative
}
"><code><span class="hljs-keyword">public</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">SignedWad</span> has copy, drop, store {
    mag: u256,   <span class="hljs-comment">// magnitude (absolute value)</span>
    neg: <span class="hljs-type">bool</span>,   <span class="hljs-comment">// true if negative</span>
}
</code></pre><p>Accessor methods (<code>abs()</code>, <code>is_negative()</code>) provide a stable API.</p><h3 id="h-suis-randomness-advantage" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Sui's Randomness Advantage</h3><p>With the mathematical machinery in place, Sui's <code>sui::random</code> completes the picture for sampling:</p><pre data-type="codeBlock" text="use sui::random::Random;
use gaussian::core;
use gaussian::signed_wad::SignedWad;

public entry fun sample_standard_normal(r: &amp;Random, ctx: &amp;mut TxContext): SignedWad {
    let mut gen = random::new_generator(r, ctx);
    
    // Generate uniform in (0, 1), avoiding exact 0 or 1
    let u = random::generate_u256_in_range(&amp;mut gen, 1, WAD - 1);
    
    // Transform via inverse CDF
    core::ppf(u)
}
"><code><span class="hljs-keyword">use</span> sui::random::Random;
<span class="hljs-keyword">use</span> gaussian::core;
<span class="hljs-keyword">use</span> gaussian::signed_wad::SignedWad;

public entry fun <span class="hljs-title function_ invoke__">sample_standard_normal</span>(r: &amp;Random, ctx: &amp;<span class="hljs-keyword">mut</span> TxContext): SignedWad {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">gen</span> = random::<span class="hljs-title function_ invoke__">new_generator</span>(r, ctx);
    
    <span class="hljs-comment">// Generate uniform in (0, 1), avoiding exact 0 or 1</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">u</span> = random::<span class="hljs-title function_ invoke__">generate_u256_in_range</span>(&amp;<span class="hljs-keyword">mut</span> gen, <span class="hljs-number">1</span>, WAD - <span class="hljs-number">1</span>);
    
    <span class="hljs-comment">// Transform via inverse CDF</span>
    core::<span class="hljs-title function_ invoke__">ppf</span>(u)
}
</code></pre><p><strong>Single transaction</strong>: No VRF callback or second transaction.</p><p><strong>Validator consensus security</strong>: Randomness derived from distributed validator set, not manipulable by any single party.</p><p><strong>Native integration</strong>: <code>&amp;Random</code> is a first-class Sui object, passed directly to functions that need it.</p><p>This is the one place where Sui provides a genuine platform advantage—but it only matters because the mathematical foundation (accurate Φ⁻¹ approximation) is already in place.</p><p>Taken together, the Python pipeline, Horner evaluation, SignedWad representation, and <code>sui::random</code> give us a complete implementation story. The next natural question is: what do you actually <em>do</em> with these primitives?</p><hr><h2 id="h-part-iii-applications" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Part III: Applications</h2><p>Part III sketches how Φ and Φ⁻¹ plug into familiar financial formulas. These are not full protocol designs; they are case studies meant to show that once GaussianMove exists, Black–Scholes, VaR, and Gaussian-shaped AMMs can be expressed directly in terms of its API.</p><h3 id="h-black-scholes-as-a-mathematical-case-study" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Black-Scholes as a Mathematical Case Study</h3><p>The Black-Scholes formula for European call options is:</p><pre data-type="codeBlock" text="C = S₀Φ(d₁) - Ke^(-rT)Φ(d₂)
"><code>C <span class="hljs-operator">=</span> S₀Φ(d₁) <span class="hljs-operator">-</span> Ke<span class="hljs-operator">^</span>(<span class="hljs-operator">-</span>rT)Φ(d₂)
</code></pre><p>where:</p><pre data-type="codeBlock" text="d₁ = [ln(S₀/K) + (r + σ²/2)T] / (σ√T)
d₂ = d₁ - σ√T
"><code>d₁ <span class="hljs-operator">=</span> [ln(S₀<span class="hljs-operator">/</span>K) <span class="hljs-operator">+</span> (r <span class="hljs-operator">+</span> σ²<span class="hljs-operator">/</span><span class="hljs-number">2</span>)T] <span class="hljs-operator">/</span> (σ√T)
d₂ <span class="hljs-operator">=</span> d₁ <span class="hljs-operator">-</span> σ√T
</code></pre><h4 id="h-the-mathematical-components" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">The Mathematical Components</h4><p><strong>Computing d₁ requires:</strong></p><ol><li><p><code>ln(S₀/K)</code> — natural logarithm (transcendental)</p></li><li><p><code>σ√T</code> — square root (algebraic but irrational)</p></li><li><p>Division and addition — straightforward in fixed-point</p></li></ol><p><strong>Computing the option price requires:</strong> 4. <code>Φ(d₁)</code>, <code>Φ(d₂)</code> — normal CDF (the hard part) 5. <code>e^(-rT)</code> — exponential (transcendental)</p><p>GaussianMove provides the CDF. The companion <code>move-black-scholes</code> package implements the transcendental helpers:</p><pre data-type="codeBlock" text="// From black_scholes::d_values
public fun compute_d1(
    spot: u256,      // S₀ in WAD
    strike: u256,    // K in WAD
    time: u256,      // T in WAD (years)
    rate: u256,      // r in WAD
    vol: u256        // σ in WAD
): SignedWad {
    // ln(S/K)
    let log_moneyness = transcendental::ln_wad(div_wad(spot, strike));
    
    // (r + σ²/2)T
    let drift = mul_wad(rate + mul_wad(vol, vol) / 2, time);
    
    // σ√T
    let vol_sqrt_t = mul_wad(vol, transcendental::sqrt_wad(time));
    
    // d₁ = (ln(S/K) + drift) / (σ√T)
    signed_wad::div(
        signed_wad::add(log_moneyness, drift),
        vol_sqrt_t
    )
}
"><code><span class="hljs-comment">// From black_scholes::d_values</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">compute_d1</span><span class="hljs-params">(
    spot: <span class="hljs-type">u256</span>,      <span class="hljs-comment">// S₀ in WAD</span>
    strike: <span class="hljs-type">u256</span>,    <span class="hljs-comment">// K in WAD</span>
    time: <span class="hljs-type">u256</span>,      <span class="hljs-comment">// T in WAD (years)</span>
    rate: <span class="hljs-type">u256</span>,      <span class="hljs-comment">// r in WAD</span>
    vol: <span class="hljs-type">u256</span>        <span class="hljs-comment">// σ in WAD</span>
)</span></span>: SignedWad {
    <span class="hljs-comment">// ln(S/K)</span>
    let log_moneyness = transcendental::ln_wad(div_wad(spot, strike));
    
    <span class="hljs-comment">// (r + σ²/2)T</span>
    let drift = mul_wad(rate + mul_wad(vol, vol) / <span class="hljs-number">2</span>, time);
    
    <span class="hljs-comment">// σ√T</span>
    let vol_sqrt_t = mul_wad(vol, transcendental::sqrt_wad(time));
    
    <span class="hljs-comment">// d₁ = (ln(S/K) + drift) / (σ√T)</span>
    signed_wad::div(
        signed_wad::add(log_moneyness, drift),
        vol_sqrt_t
    )
}
</code></pre><p>The transcendental functions (<code>ln_wad</code>, <code>sqrt_wad</code>, <code>exp_wad</code>) are also implemented via polynomial approximation, following the same AAA pipeline philosophy. In practice, this means Black–Scholes-style pricing on Sui becomes a matter of wiring together a few math primitives.</p><hr><h3 id="h-empirical-validation-live-testnet-transactions" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Empirical Validation: Live Testnet Transactions</h3><p>The following transactions demonstrate GaussianMove in production, pricing ATM European options (S=$100, K=$100, T=1yr, r=5%, σ=20%):</p><p><strong>Transaction 1: Option Pricing</strong></p><ul><li><p>TX: <code>CdAxPyw1T7tF4xMPpfVqVhJMDL4Xy6zeyC24YeQxpjJt</code></p></li><li><p>Results: Call=$10.45, Put=$5.57, Put-Call Parity=✓</p></li></ul><p><strong>Transaction 2: Greeks Calculation</strong></p><ul><li><p>TX: <code>48TFYV87TXRJMUuCzoMZ4T5CLVsFgQoT1fptR2w7NXPv</code></p></li><li><p>Results: Δ=0.637, Γ=0.019, ν=37.52, θ=-6.41, ρ=53.23</p></li></ul><p><strong>Comparison with scipy reference:</strong></p><table style="min-width: 100px"><colgroup><col><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Metric</p></th><th colspan="1" rowspan="1"><p>On-Chain Result</p></th><th colspan="1" rowspan="1"><p>scipy Reference</p></th><th colspan="1" rowspan="1"><p>Error</p></th></tr><tr><td colspan="1" rowspan="1"><p>Call Price</p></td><td colspan="1" rowspan="1"><p>$10.4506</p></td><td colspan="1" rowspan="1"><p>$10.4506</p></td><td colspan="1" rowspan="1"><p>&lt;0.01%</p></td></tr><tr><td colspan="1" rowspan="1"><p>Put Price</p></td><td colspan="1" rowspan="1"><p>$5.5735</p></td><td colspan="1" rowspan="1"><p>$5.5735</p></td><td colspan="1" rowspan="1"><p>&lt;0.01%</p></td></tr><tr><td colspan="1" rowspan="1"><p>Delta</p></td><td colspan="1" rowspan="1"><p>0.6368</p></td><td colspan="1" rowspan="1"><p>0.6368</p></td><td colspan="1" rowspan="1"><p>&lt;0.01%</p></td></tr><tr><td colspan="1" rowspan="1"><p>Gamma</p></td><td colspan="1" rowspan="1"><p>0.0188</p></td><td colspan="1" rowspan="1"><p>0.0188</p></td><td colspan="1" rowspan="1"><p>&lt;0.01%</p></td></tr></tbody></table><p>These results validate that GaussianMove's ~10⁻⁹ CDF error propagates to &lt;0.01% pricing error in Black-Scholes applications.</p><hr><h2 id="h-part-iv-ecosystem-context" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Part IV: Ecosystem Context</h2><p>Part IV places GaussianMove alongside existing Gaussian and options libraries in EVM and other ecosystems. The goal is not to declare a winner, but to show where a Sui-native, AAA-based library fits on the accuracy/gas/complexity frontier.</p><h3 id="h-evm-gaussian-libraries-a-brief-comparison" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">EVM Gaussian Libraries: A Brief Comparison</h3><p>The EVM ecosystem has seen two waves of Gaussian implementations:</p><p><strong>First wave (2014-2022)</strong>: Production-driven</p><ul><li><p><code>errcw/gaussian</code> (2014): JavaScript reference</p></li><li><p><code>primitivefinance/solstat</code> (2022): First DeFi library, ~10⁻⁷ error, ~5,000 gas</p></li></ul><p><strong>Second wave (2024)</strong>: Research-driven</p><ul><li><p><code>GA006/gaussian-cdf</code>: Zelen-Severo polynomial, ~10⁻⁸ error</p></li><li><p><code>0xknxwledge/DegeGauss</code>: ABDK 128-bit float, ~10⁻¹⁶ error, ~53,000 gas</p></li><li><p><code>cairoeth/solgauss</code>: Rational Chebyshev, ~10⁻⁸ error, ~600 gas, includes PPF</p></li></ul><p><strong>Key insight</strong>: solgauss achieves the best gas/accuracy Pareto frontier via rational approximation—the same approach GaussianMove uses, but with AAA instead of hand-tuned Chebyshev coefficients.</p><h3 id="h-algorithm-comparison" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Algorithm Comparison</h3><table style="min-width: 125px"><colgroup><col><col><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Library</p></th><th colspan="1" rowspan="1"><p>Algorithm</p></th><th colspan="1" rowspan="1"><p>CDF Error</p></th><th colspan="1" rowspan="1"><p>PPF?</p></th><th colspan="1" rowspan="1"><p>Notes</p></th></tr><tr><td colspan="1" rowspan="1"><p>solstat</p></td><td colspan="1" rowspan="1"><p>Abramowitz-Stegun</p></td><td colspan="1" rowspan="1"><p>10⁻⁷</p></td><td colspan="1" rowspan="1"><p>No</p></td><td colspan="1" rowspan="1"><p>Uses exp(), high gas</p></td></tr><tr><td colspan="1" rowspan="1"><p>solgauss</p></td><td colspan="1" rowspan="1"><p>Rational Chebyshev</p></td><td colspan="1" rowspan="1"><p>10⁻⁸</p></td><td colspan="1" rowspan="1"><p>Yes</p></td><td colspan="1" rowspan="1"><p>No exp() for CDF</p></td></tr><tr><td colspan="1" rowspan="1"><p>DegeGauss</p></td><td colspan="1" rowspan="1"><p>ABDK 128-bit float</p></td><td colspan="1" rowspan="1"><p>10⁻¹⁶</p></td><td colspan="1" rowspan="1"><p>No</p></td><td colspan="1" rowspan="1"><p>Extreme precision, extreme gas</p></td></tr><tr><td colspan="1" rowspan="1"><p>Morpheus</p></td><td colspan="1" rowspan="1"><p>A-S + Acklam + Newton</p></td><td colspan="1" rowspan="1"><p>10⁻¹⁵</p></td><td colspan="1" rowspan="1"><p>Yes</p></td><td colspan="1" rowspan="1"><p>Most complete, most complex</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>GaussianMove</strong></p></td><td colspan="1" rowspan="1"><p>AAA rational</p></td><td colspan="1" rowspan="1"><p>10⁻⁹</p></td><td colspan="1" rowspan="1"><p>Yes</p></td><td colspan="1" rowspan="1"><p>v0.9.0, 399 tests, sui::random</p></td></tr></tbody></table><p>GaussianMove occupies a distinct position: better accuracy than solgauss, lower complexity than Morpheus, and native randomness integration that no EVM library can match.</p><hr><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>We started with a simple question: how do you compute Φ and Φ⁻¹ on a chain that only speaks integers? GaussianMove's answer is to treat this as an approximation-theory problem first, and an engineering problem second.</p><p>Concretely, GaussianMove:</p><ol><li><p>Uses modern <strong>AAA rational approximation</strong> to generate near-optimal fits for Φ and Φ⁻¹ offline.</p></li><li><p>Enforces explicit <strong>error budgets</strong> (CDF 3.35×10⁻⁹, PPF 3.11×10⁻¹³) validated against high-precision baselines.</p></li><li><p>Wraps everything in an <strong>auditable Python→Move pipeline</strong> and a small, deterministic on-chain evaluation kernel.</p></li><li><p>Leverages Sui's <strong>native randomness</strong> so Gaussian sampling fits cleanly into single-transaction flows.</p></li></ol><p>Whether or not DeFi fully embraces on-chain Gaussian math, it is useful to have a transparent implementation available.</p><hr><h2 id="h-references" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">References</h2><h3 id="h-approximation-theory" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Approximation Theory</h3><ul><li><p>Nakatsukasa, Y., Sète, O., &amp; Trefethen, L. N. (2018). The AAA algorithm for rational approximation. <em>SIAM J. Sci. Comput.</em>, 40(3), A1494-A1522. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arxiv.org/abs/1612.00337">arXiv:1612.00337</a></p></li><li><p>Nakatsukasa, Y., &amp; Trefethen, L. N. (2023). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arxiv.org/abs/2312.03565">The first five years of the AAA algorithm</a>. arXiv:2312.03565.</p></li><li><p>Nakatsukasa, Y., &amp; Trefethen, L. N. (2025). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arxiv.org/abs/2510.16237">Applications of AAA rational approximation</a>. <em>Acta Numerica</em>. arXiv:2510.16237.</p></li><li><p>Driscoll, T. A., Nakatsukasa, Y., &amp; Trefethen, L. N. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://tobydriscoll.net/_docs/driscoll-rational-approximation-continuum-2024.pdf">AAA Rational Approximation on a Continuum</a>. <em>SIAM J. Sci. Comput.</em>, 46(2), A929-A952.</p></li></ul><h3 id="h-classical-algorithms-for-normal-distribution" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Classical Algorithms for Normal Distribution</h3><ul><li><p>Abramowitz, M., &amp; Stegun, I. A. (1964). <em>Handbook of Mathematical Functions</em>. Ch. 26.2.17.</p></li><li><p>Wichura, M. J. (1988). Algorithm AS 241: The Percentage Points of the Normal Distribution. <em>Applied Statistics</em>, 37(3), 477-484.</p></li><li><p>Cody, W. J. (1969). Rational Chebyshev Approximations for the Error Function. <em>Mathematics of Computation</em>, 23(107).</p></li><li><p>Acklam, P. J. (2000). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://stackedboxes.org/2017/05/01/acklams-normal-quantile-function/">An algorithm for computing the inverse normal cumulative distribution function</a>. (See also <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.johndcook.com/normal_cdf_inverse.html">John D. Cook's literate program</a>)</p></li><li><p>Koopman, R. (2025). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://doi.org/10.33993/jnaat541-1434">Some Simple Full-Range Inverse-Normal Approximations</a>. <em>Journal of Numerical Analysis and Approximation Theory</em>, 54(1).</p></li></ul><h3 id="h-horners-method-and-polynomial-evaluation" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Horner's Method and Polynomial Evaluation</h3><ul><li><p>Graillat, S., et al. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://link.springer.com/article/10.1007/s10543-024-01017-w">Accurate Horner methods in real and complex floating-point arithmetic</a>. <em>BIT Numerical Mathematics</em>, 64, article 17.</p></li><li><p>Graillat, S., Langlois, P., &amp; Louvet, N. (2009). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://link.springer.com/article/10.1007/BF03186531">Algorithms for accurate, validated and fast polynomial evaluation</a>. <em>Japan Journal of Industrial and Applied Mathematics</em>, 26(2), 191-214.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Horner%27s_method">Horner's method - Wikipedia</a></p></li></ul><h3 id="h-fixed-point-arithmetic-in-defi" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Fixed-Point Arithmetic in DeFi</h3><ul><li><p>RareSkills. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://rareskills.io/post/solidity-fixed-point">Fixed Point Arithmetic in Solidity</a>. (Comprehensive tutorial on WAD/RAY standards)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/PaulRBerg/prb-math">PRBMath</a>: Solidity library for advanced fixed-point math.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/dapphub/ds-math">ds-math</a>: Original DappHub WAD/RAY implementation.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/zfedoran/brine-fp">brine-fp</a>: Fixed-point math library with logarithmic and exponential functions for blockchain.</p></li></ul><h3 id="h-solidity-gaussian-implementations" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Solidity Gaussian Implementations</h3><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/primitivefinance/solstat">primitivefinance/solstat</a>: First production DeFi Gaussian library (Primitive RMM-01).</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/cairoeth/solgauss">cairoeth/solgauss</a>: Rational Chebyshev approximation, most complete API.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/0xknxwledge/DegeGauss">0xknxwledge/DegeGauss</a>: ABDK 128-bit floating-point approach.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/simontianx/OnChainRNG/tree/main/GaussianRNG">simontianx/OnChainRNG/GaussianRNG</a>: CLT-based Gaussian approximation.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/araghava/cairo-black-scholes">araghava/cairo-black-scholes</a>: Black-Scholes on StarkNet.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/opynfinance/BlackScholes">opynfinance/BlackScholes</a>: Opyn's Black-Scholes implementation.</p></li></ul><h3 id="h-move-implementations" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Move Implementations</h3><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/move-gaussian">GaussianMove (move-gaussian)</a> — v0.9.0, Package: <code>0x66f9087a3d9ae3fe07a5f3c1475d503f1b0ea508d3b83b73b0b8637b57629f7f</code></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/move-black-scholes">BlackScholes (move-black-scholes)</a> — v0.2.0, Package: <code>0x1637ddc0495a8833ebd580224dad7154dfb33477f73d2c7fb41e2b350efa55b3</code></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Apostlex0/PredictionMarket_AMM">Morpheus PM-AMM (Aptos)</a></p></li></ul><h3 id="h-amms-and-options-protocols" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AMMs and Options Protocols</h3><ul><li><p>Evans, A., Angeris, G., &amp; Chitra, T. (2021). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://primitive.mirror.xyz/l5F1BLMhfmcm_B1R2w-by003_oiAz2-Ir_Jj_rCvGCs">Introducing Primitive RMM-01</a>. Primitive Finance.</p></li><li><p>Sterrett, E., &amp; Jepsen, W. (2022). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arxiv.org/abs/2205.09890">Replicating Portfolios: Constructing Permissionless Derivatives</a>. arXiv:2205.09890.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://primitive.mirror.xyz/Audtl29HY_rnhN4E2LwnP7-zjDcDGAyXZ4h3QpDeajg">RMM Primer</a>: Friendly guide to Primitive.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://library.primitive.xyz/">Primitive Library Documentation</a></p></li><li><p>Moallemi, C., &amp; Robinson, D. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.paradigm.xyz/2024/11/pm-amm">pm-AMM: A Uniform AMM for Prediction Markets</a>. Paradigm.</p></li><li><p>White, D. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.paradigm.xyz/2024/12/distribution-markets">Distribution Markets</a>. Paradigm.</p></li></ul><h3 id="h-defi-risk-management-and-var" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">DeFi Risk Management and VaR</h3><ul><li><p>Chainrisk. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.chainrisk.xyz/blog-posts/var-methodology">VaR Methodology for DeFi</a>.</p></li><li><p>Gauntlet. (2023). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/gauntlet-networks/improved-var-methodology-9f4f0c4cdb6f">Improved VaR Methodology</a>.</p></li><li><p>GARP. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.garp.org/risk-intelligence/market/digital-asset-risk-241018">Digital-Asset Risk Management: VaR Meets Cryptocurrencies</a>.</p></li><li><p>Aufiero, S., et al. (2025). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arxiv.org/abs/2508.12007">Mapping Microscopic and Systemic Risks in TradFi and DeFi</a>. arXiv:2508.12007.</p></li><li><p>KernelDAO. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blogs.kerneldao.com/blog/traditional-vs-defi-risk-management-a-quantitative-comparison">Traditional vs DeFi Risk Management: A Quantitative Comparison</a>.</p></li></ul><h3 id="h-black-scholes-in-defi" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Black-Scholes in DeFi</h3><ul><li><p>Polygon. (2022). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://polygontech.medium.com/black-scholes-merton-model-to-price-defi-options-part-1-a-tale-of-the-king-with-torn-clothes-dff043eadea6">Black Scholes Merton Model to Price DeFi Options</a>.</p></li><li><p>Chainlink. (2020). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.chain.link/defi-call-option-exchange-in-solidity/">Build a DeFi Call Option Exchange With Chainlink Price Feeds</a>.</p></li><li><p>Auctus. (2020). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.auctus.org/aco-black-scholes-a-pooled-liquidity-model-for-options-powered-by-chainlink-is-now-live-9638dccf1825">ACO Black-Scholes: A Pooled Liquidity Model for Options Powered by Chainlink</a>.</p></li><li><p>Panoptic. (2024). <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://panoptic.xyz/research/perpetual-option-pricing-model-comparison">How to Price Perpetual Options: Five Models Compared</a>.</p></li></ul><h3 id="h-sui-documentation" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Sui Documentation</h3><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sui.io/guides/developer/advanced/randomness-onchain">Sui Randomness Guide</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sui.io/references/framework/sui_sui/random">sui::random Framework Reference</a></p></li></ul><h3 id="h-tools" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Tools</h3><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.AAA.html">SciPy AAA</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mpmath.org/">mpmath (Arbitrary Precision)</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/c-f-h/baryrat">baryrat (AAA Implementation)</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.scipy.org/doc/scipy/tutorial/stats/sampling_pinv.html">SciPy PINV (Polynomial interpolation based INVersion of CDF)</a></p></li></ul><br>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>sui</category>
            <category>move</category>
            <category>gaussian</category>
            <category>math</category>
            <category>randomness</category>
            <category>python</category>
            <category>smart</category>
            <category>contracts</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/604e80a155ba74c84429c9b66fc4ca8c7f6d3147c2c87b1720f42c4d344bd09e.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Getting the Sui Latest Object States]]></title>
            <link>https://paragraph.com/@evandekim/getting-the-sui-latest-object-states</link>
            <guid>5cZju5x25Wq5OQ08bYKr</guid>
            <pubDate>Thu, 13 Nov 2025 17:44:18 GMT</pubDate>
            <description><![CDATA[How “Latest State” Works on SuiThe latest-state pattern is one of the most important techniques for anyone working with Sui object data. This technique generalizes to every Sui object type and forms the backbone for any analysis rooted in the chain’s actual state. Every asset—whether a coin, NFT, or protocol object—exists as a Move object that evolves through a sequence of versions. Each mutation creates a new version, deleted objects remain visible with object_status = 'Deleted', and there i...]]></description>
            <content:encoded><![CDATA[<h1 id="h-how-latest-state-works-on-sui" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How “Latest State” Works on Sui</h1><p>The latest-state pattern is one of the most important techniques for anyone working with Sui object data. This technique generalizes to every Sui object type and forms the backbone for any analysis rooted in the chain’s actual state. Every asset—whether a coin, NFT, or protocol object—exists as a Move object that evolves through a sequence of versions.</p><p>Each mutation creates a new version, deleted objects remain visible with <code>object_status = 'Deleted'</code>, and there is no separate balance table or account registry keeping track of state elsewhere. As a result, the <em>canonical truth</em> for any asset is simply the <strong>row with the highest version number</strong>. If you want to know “what does this object look like right now?”, the answer is always: <em>find its latest version</em>.</p><p>This structural choice gives Sui enormous flexibility but also shapes how analysts must work with the data. Tasks like fetching the current owner or metadata of an object become trivial once you understand the pattern. But tasks like reconstructing transfers, ownership histories, or application-level flows require walking through the full sequence of versions—something covered in the previous post.</p><p>The Pawtato Heroes example highlights why the latest-state pattern is so essential: the latest version reveals the object’s current metadata and owner, but because objects can be owned by other objects, the last modifying transaction’s <code>sender</code> is often the real controller.</p><pre data-type="codeBlock" text="object_id: 0xHERO...123

                      object versions
          +-----------------------------------------------+
          |                                               |
 version 1   version 2          version 3 (latest)
 +--------+  +--------+         +------------------------+
 |owner:A |  |owner:X |   →     |owner:Y (wrapper obj)   |
 |meta:v1 |  |meta:v1 |         |meta:v2                 |
 +--------+  +--------+         +------------------------+
     ^           ^                        ^
     |           |                        |
 Created      Mutated                Mutated
                                     (ownership changed,
                                     but to an object)

          transaction producing version 3
          -----------------------------------------
          tx_digest: 0xTX...789
          sender:    0xREAL_OWNER   ← true controller
          -----------------------------------------
                             ↑
                             |
                    previous_transaction  
                    on version 3 object row

Latest-state logic:
- MAX(version) = 3 → latest object row (owner:Y, meta:v2)
- But owner:Y is another object (wrapper/vault)
- Join version 3's previous_transaction → transaction.sender
- Effective owner = 0xREAL_OWNER

"><code>object_id: 0xHERO...123

                      object versions
          <span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
          <span class="hljs-operator">|</span>                                               <span class="hljs-operator">|</span>
 version <span class="hljs-number">1</span>   version <span class="hljs-number">2</span>          version <span class="hljs-number">3</span> (latest)
 <span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>  <span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>         <span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
 <span class="hljs-operator">|</span>owner:A <span class="hljs-operator">|</span>  <span class="hljs-operator">|</span>owner:X <span class="hljs-operator">|</span>   →     <span class="hljs-operator">|</span>owner:Y (wrapper obj)   <span class="hljs-operator">|</span>
 <span class="hljs-operator">|</span>meta:v1 <span class="hljs-operator">|</span>  <span class="hljs-operator">|</span>meta:v1 <span class="hljs-operator">|</span>         <span class="hljs-operator">|</span>meta:v2                 <span class="hljs-operator">|</span>
 <span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>  <span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>         <span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
     <span class="hljs-operator">^</span>           <span class="hljs-operator">^</span>                        <span class="hljs-operator">^</span>
     <span class="hljs-operator">|</span>           <span class="hljs-operator">|</span>                        <span class="hljs-operator">|</span>
 Created      Mutated                Mutated
                                     (ownership changed,
                                     but to an object)

          transaction producing version <span class="hljs-number">3</span>
          <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
          tx_digest: 0xTX...789
          sender:    0xREAL_OWNER   ← <span class="hljs-literal">true</span> controller
          <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
                             ↑
                             <span class="hljs-operator">|</span>
                    previous_transaction  
                    on version <span class="hljs-number">3</span> object row

Latest<span class="hljs-operator">-</span>state logic:
<span class="hljs-operator">-</span> MAX(version) <span class="hljs-operator">=</span> <span class="hljs-number">3</span> → latest object row (owner:Y, meta:v2)
<span class="hljs-operator">-</span> But owner:Y <span class="hljs-keyword">is</span> another object (wrapper<span class="hljs-operator">/</span>vault)
<span class="hljs-operator">-</span> Join version <span class="hljs-number">3</span><span class="hljs-string">'s previous_transaction → transaction.sender
- Effective owner = 0xREAL_OWNER

</span></code></pre><hr><h1 id="h-querying-latest-state-objects-on-dune" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Querying Latest State Objects on Dune</h1><p>Below is a practical pattern for retrieving the <strong>latest version</strong> and owner for a Sui NFT collection Pawtato Heroes. The query can be found on Dune <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/queries/6204236/9899927/">here</a>. This example demonstrates how to apply latest-state logic to an NFT collection by tracking ownership changes that occur through <code>Mutated</code> versions, and resolving the <em>true</em> owner by joining the object’s most recent version to the transaction that produced it.</p><pre data-type="codeBlock" text="WITH params AS (
  SELECT
    DATE '2025-10-30' AS start_date,
    CURRENT_DATE      AS end_date
),

-- All relevant HERO object versions in the date window
base AS (
  SELECT
    o.object_id,
    o.version,
    o.object_status,
    o.owner_address,   -- may be an intermediary object
    o.timestamp_ms,
    o.type_,
    o.object_json,
    o.previous_transaction AS tx_digest,
    from_unixtime(CAST(o.timestamp_ms AS DOUBLE) / 1000.0) AS updated_at_utc
  FROM sui.objects o
  CROSS JOIN params p
  WHERE o.type_ = '0xe0fa7b75a3dc8137b38bceb0c0c21c10e0f57c408fe9068694f58fd21e071925::pawtato_heroes::HERO'
    AND o.object_status IN ('Created', 'Mutated')   -- ownership may change on Mutated
    AND o.date &gt;= p.start_date                     -- efficient partition pruning
    AND o.date &lt;  p.end_date
),

-- Latest version per HERO
latest AS (
  SELECT
    object_id,
    MAX(version) AS max_version
  FROM base
  GROUP BY object_id
),

-- Date-windowed transactions (prevents full-table scan)
tx_window AS (
  SELECT
    t.transaction_digest,
    t.sender,
    t.timestamp_ms AS tx_timestamp_ms,
    from_unixtime(CAST(t.timestamp_ms AS DOUBLE) / 1000.0) AS tx_timestamp_utc
  FROM sui.transactions t
  CROSS JOIN params p
  WHERE t.date &gt;= p.start_date
    AND t.date &lt;  p.end_date
)

-- Final latest state + effective owner
SELECT
  b.object_id,
  b.version,
  b.object_status,
  b.owner_address,     -- raw object owner (could be another object)
  b.updated_at_utc,
  b.type_,
  b.object_json,
  b.tx_digest,
  tx.sender            AS effective_owner,     -- true controller of the NFT
  tx.tx_timestamp_utc  AS last_tx_at_utc
FROM base b
JOIN latest l
  ON b.object_id = l.object_id
 AND b.version   = l.max_version
LEFT JOIN tx_window tx
  ON b.tx_digest = tx.transaction_digest;

"><code>WITH params AS (
  SELECT
    DATE <span class="hljs-string">'2025-10-30'</span> AS start_date,
    CURRENT_DATE      AS end_date
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> All relevant HERO object versions in the date window
base AS (
  SELECT
    o.object_id,
    o.version,
    o.object_status,
    o.owner_address,   <span class="hljs-operator">-</span><span class="hljs-operator">-</span> may be an intermediary object
    o.timestamp_ms,
    o.type_,
    o.object_json,
    o.previous_transaction AS tx_digest,
    from_unixtime(CAST(o.timestamp_ms AS DOUBLE) <span class="hljs-operator">/</span> <span class="hljs-number">1000.0</span>) AS updated_at_utc
  FROM sui.objects o
  CROSS JOIN params p
  WHERE o.type_ <span class="hljs-operator">=</span> <span class="hljs-string">'0xe0fa7b75a3dc8137b38bceb0c0c21c10e0f57c408fe9068694f58fd21e071925::pawtato_heroes::HERO'</span>
    AND o.object_status IN (<span class="hljs-string">'Created'</span>, <span class="hljs-string">'Mutated'</span>)   <span class="hljs-operator">-</span><span class="hljs-operator">-</span> ownership may change on Mutated
    AND o.date <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> p.start_date                     <span class="hljs-operator">-</span><span class="hljs-operator">-</span> efficient partition pruning
    AND o.date <span class="hljs-operator">&lt;</span>  p.end_date
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> Latest version per HERO
latest AS (
  SELECT
    object_id,
    MAX(version) AS max_version
  FROM base
  GROUP BY object_id
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> Date<span class="hljs-operator">-</span>windowed transactions (prevents full<span class="hljs-operator">-</span>table scan)
tx_window AS (
  SELECT
    t.transaction_digest,
    t.sender,
    t.timestamp_ms AS tx_timestamp_ms,
    from_unixtime(CAST(t.timestamp_ms AS DOUBLE) <span class="hljs-operator">/</span> <span class="hljs-number">1000.0</span>) AS tx_timestamp_utc
  FROM sui.transactions t
  CROSS JOIN params p
  WHERE t.date <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> p.start_date
    AND t.date <span class="hljs-operator">&lt;</span>  p.end_date
)

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> Final latest state <span class="hljs-operator">+</span> effective owner
SELECT
  b.object_id,
  b.version,
  b.object_status,
  b.owner_address,     <span class="hljs-operator">-</span><span class="hljs-operator">-</span> raw object owner (could be another object)
  b.updated_at_utc,
  b.type_,
  b.object_json,
  b.tx_digest,
  <span class="hljs-built_in">tx</span>.sender            AS effective_owner,     <span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-literal">true</span> controller of the NFT
  <span class="hljs-built_in">tx</span>.tx_timestamp_utc  AS last_tx_at_utc
FROM base b
JOIN latest l
  ON b.object_id <span class="hljs-operator">=</span> l.object_id
 AND b.version   <span class="hljs-operator">=</span> l.max_version
LEFT JOIN tx_window <span class="hljs-built_in">tx</span>
  ON b.tx_digest <span class="hljs-operator">=</span> <span class="hljs-built_in">tx</span>.transaction_digest;

</code></pre><p>This query works by first collecting all relevant Pawtato HERO object versions—both <code>Created</code> and <code>Mutated</code>. The date window is recent because the collection launched October 2025 which makes the dataset small and efficient to scan. It then identifies the latest version of each object by selecting the maximum <code>version</code> per <code>object_id</code>, which represents the current on-chain state of that NFT.</p><p>To resolve the true owner, the query pulls in a date-filtered slice of <code>sui.transactions</code> and joins each object’s most recent <code>previous_transaction</code> to the corresponding transaction record. This allows the query to enrich the final result with full <code>object_json</code> metadata, the transaction that last updated the object, and most importantly the <code>sender</code> of that transaction—who is often the actual controlling address, even when the object’s recorded owner is an intermediate wrapper object.</p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>web3</category>
            <category>data</category>
            <category>sui</category>
            <category>dune</category>
            <category>sql</category>
            <category>nft</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/bc555bf62e527af2b6531fb8450fa133de8d40d93a0880f56514ff2be062a399.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Calculating Coin Transfers on Sui with Dune]]></title>
            <link>https://paragraph.com/@evandekim/calculating-coin-transfers-on-sui-with-dune</link>
            <guid>kffprc5kQRbNEWZIY0Uv</guid>
            <pubDate>Wed, 12 Nov 2025 22:38:53 GMT</pubDate>
            <description><![CDATA[OutlineMental Model of Sui ObjectsQuerying USDC Coin Transfers on DuneTransfer HeuristicsQuery OptimizationsThis post shows how to calculate USDC transfers on the Sui blockchain using Dune’s Trino SQL engine. Sui is object-centric - meaning that every object has their own lifecycle, ownership history, versioning, and special object type data. This means that coins and addresses on Sui are also Move objects. To calculate balance transfers, we must compare the state of multiple coin objects, no...]]></description>
            <content:encoded><![CDATA[<br><p>Outline</p><ol><li><p>Mental Model of Sui Objects</p></li><li><p>Querying USDC Coin Transfers on Dune</p></li><li><p>Transfer Heuristics</p></li><li><p>Query Optimizations</p></li></ol><p>This post shows how to calculate USDC transfers on the Sui blockchain using Dune’s Trino SQL engine. <strong>Sui is object-centric</strong> - meaning that every object has their own lifecycle, ownership history, versioning, and special object type data. This means that coins and addresses on Sui are also Move objects. To calculate balance transfers, we must compare the state of multiple coin objects, not from a single account balance like on EVM chains.</p><hr><h2 id="h-1-mental-model-of-sui-objects" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">1) Mental Model of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sui.io/concepts/object-model">Sui Objects</a></h2><ul><li><p><strong>Objects, not accounts.</strong> Every coin is an object with an <code>object_id</code>, a <code>version</code> that increases with each mutation, an owner, and a coin <strong>type</strong> (e.g., <code>0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC</code>).</p></li><li><p><strong>Ownership forms.</strong> Address-owned, object-owned, shared, or immutable. A wallet can hold <em>many</em> coin objects for the same token.</p></li><li><p><strong>Causality via transactions.</strong> Each new object version is produced by a specific transaction digest (previous transaction).</p></li></ul><p>Practical implication: to compute “transfers,” we trace how coin objects’ ownership and balances change over time, then aggregate the positive inflows to receivers.</p><hr><h2 id="h-2-querying-usdc-coin-transfers-on-dune" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">2) Querying USDC Coin Transfers on Dune</h2><p>Below is a query example to calculate USDC transfers for the first month of the coin’s creation. The dune link can be <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/queries/6192221/9883431">found here</a>.</p><p>The query reconstructs USDC transfers directly from object-state changes rather than relying on emitted events. Each time a new coin object is created, its full coin_balance represents an inflow to the receiver. When an existing object is mutated, the query computes the positive delta between its current and previous balances (max(new − old, 0)), which captures additional inflows such as received transfers or merged coins. To make these deltas accurate without scanning the entire historical table, the query introduces an anchor—the most recent pre-window version of every object—so that LAG(prev_balance) comparisons are local to the analysis range. Finally, by filtering both sui.objects and sui.transactions on their date partitions, Dune’s engine can prune away irrelevant partitions and limit I/O to only the time window being studied, significantly reducing scan cost.</p><pre data-type="codeBlock" text="WITH params AS (
  SELECT
    CAST('0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC' AS VARCHAR) AS usdc,
    DATE '2024-09-01' AS start_date,
    DATE '2024-10-10' AS end_date
),
day_rows AS (
  SELECT
    o.object_id,
    o.version,
    o.previous_transaction AS tx_digest,
    o.timestamp_ms,
    o.owner_address AS receiver,
    o.coin_type,
    o.object_status,
    TRY_CAST(o.coin_balance AS BIGINT) AS coin_balance
  FROM sui.objects o
  CROSS JOIN params p
  WHERE o.object_status IN ('Created', 'Mutated')
    AND o.coin_type = p.usdc
    -- ✅ partition pruning on objects
    AND o.date &gt;= p.start_date
    AND o.date &lt;  p.end_date
),
anchors AS (
  SELECT
    p.object_id,
    MAX(p.version) AS version,
    CAST(NULL AS VARCHAR) AS tx_digest,
    max_by(p.timestamp_ms, p.version) AS timestamp_ms,
    max_by(p.owner_address, p.version) AS receiver,
    p.coin_type,
    CAST('ANCHOR' AS VARCHAR) AS object_status,
    max_by(TRY_CAST(p.coin_balance AS BIGINT), p.version) AS coin_balance
  FROM sui.objects p
  CROSS JOIN params pr
  WHERE p.coin_type = pr.usdc
    -- ✅ prune to pre-window partitions only
    AND p.date &lt; pr.start_date
    -- only anchor for objects that appear in the main window
    AND p.object_id IN (SELECT DISTINCT object_id FROM day_rows)
  GROUP BY p.object_id, p.coin_type
),
unioned AS (
  SELECT object_id, version, tx_digest, timestamp_ms, receiver, coin_type, object_status, coin_balance FROM anchors
  UNION ALL
  SELECT object_id, version, tx_digest, timestamp_ms, receiver, coin_type, object_status, coin_balance FROM day_rows
),
calc AS (
  SELECT
    u.object_id,
    u.version,
    u.tx_digest,
    u.timestamp_ms,
    u.receiver,
    u.coin_type,
    u.object_status,
    u.coin_balance,
    LAG(u.receiver)     OVER (PARTITION BY u.object_id ORDER BY u.version) AS prev_owner,
    LAG(u.coin_balance) OVER (PARTITION BY u.object_id ORDER BY u.version) AS prev_balance
  FROM unioned u
),
-- Prefilter transactions by partition, then join (avoids scanning all tx partitions)
tx_window AS (
  SELECT tx.*
  FROM sui.transactions tx
  CROSS JOIN params p
  WHERE tx.date &gt;= p.start_date
    AND tx.date &lt;  p.end_date
),
calc_with_tx AS (
  SELECT c.*, tx.sender AS tx_sender
  FROM calc c
  LEFT JOIN tx_window tx
    ON c.tx_digest = tx.transaction_digest
),
outs AS (
  SELECT
    tx_digest,
    receiver,
    coin_type,
    SUM(
      CASE
        WHEN object_status = 'Created' THEN coin_balance
        WHEN object_status = 'Mutated' THEN greatest(coin_balance - COALESCE(prev_balance, 0), 0)
        ELSE 0
      END
    ) AS amount_raw
  FROM calc
  WHERE (object_status = 'Created' AND coin_balance &gt; 0)
     OR (object_status = 'Mutated' AND (coin_balance - COALESCE(prev_balance, 0)) &gt; 0)
  GROUP BY tx_digest, receiver, coin_type
),
tx_stats AS (
  SELECT tx_digest, COUNT() AS receiver_cnt
  FROM outs
  GROUP BY tx_digest
)
SELECT
  c.object_id,
  c.version,
  c.tx_digest,
  c.timestamp_ms,
  c.receiver,
  CASE WHEN c.object_status = 'Created' THEN c.tx_sender ELSE c.prev_owner END AS sender,
  c.coin_type,
  c.object_status,
  c.coin_balance,
  c.prev_balance,
  CASE
    WHEN c.object_status = 'Created' THEN c.coin_balance
    ELSE greatest(c.coin_balance - COALESCE(c.prev_balance, 0), 0)
  END AS effective_amount,
  CASE WHEN ts.receiver_cnt = 1 THEN 'transfer' ELSE 'other' END AS classification,
  CASE WHEN ts.receiver_cnt = 1 THEN TRUE ELSE FALSE END AS is_transfer
FROM calc_with_tx c
LEFT JOIN tx_stats ts
  ON c.tx_digest = ts.tx_digest
WHERE (c.object_status = 'Created' AND c.coin_balance &gt; 0)
   OR (c.object_status = 'Mutated' AND (c.coin_balance - COALESCE(c.prev_balance, 0)) &gt; 0);

"><code>WITH params AS (
  SELECT
    CAST(<span class="hljs-string">'0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC'</span> AS VARCHAR) AS usdc,
    DATE <span class="hljs-string">'2024-09-01'</span> AS start_date,
    DATE <span class="hljs-string">'2024-10-10'</span> AS end_date
),
day_rows AS (
  SELECT
    o.object_id,
    o.version,
    o.previous_transaction AS tx_digest,
    o.timestamp_ms,
    o.owner_address AS receiver,
    o.coin_type,
    o.object_status,
    TRY_CAST(o.coin_balance AS BIGINT) AS coin_balance
  FROM sui.objects o
  CROSS JOIN params p
  WHERE o.object_status IN (<span class="hljs-string">'Created'</span>, <span class="hljs-string">'Mutated'</span>)
    AND o.coin_type <span class="hljs-operator">=</span> p.usdc
    <span class="hljs-operator">-</span><span class="hljs-operator">-</span> ✅ partition pruning on objects
    AND o.date <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> p.start_date
    AND o.date <span class="hljs-operator">&lt;</span>  p.end_date
),
anchors AS (
  SELECT
    p.object_id,
    MAX(p.version) AS version,
    CAST(NULL AS VARCHAR) AS tx_digest,
    max_by(p.timestamp_ms, p.version) AS timestamp_ms,
    max_by(p.owner_address, p.version) AS receiver,
    p.coin_type,
    CAST(<span class="hljs-string">'ANCHOR'</span> AS VARCHAR) AS object_status,
    max_by(TRY_CAST(p.coin_balance AS BIGINT), p.version) AS coin_balance
  FROM sui.objects p
  CROSS JOIN params pr
  WHERE p.coin_type <span class="hljs-operator">=</span> pr.usdc
    <span class="hljs-operator">-</span><span class="hljs-operator">-</span> ✅ prune to pre<span class="hljs-operator">-</span>window partitions only
    AND p.date <span class="hljs-operator">&lt;</span> pr.start_date
    <span class="hljs-operator">-</span><span class="hljs-operator">-</span> only anchor <span class="hljs-keyword">for</span> objects that appear in the main window
    AND p.object_id IN (SELECT DISTINCT object_id FROM day_rows)
  GROUP BY p.object_id, p.coin_type
),
unioned AS (
  SELECT object_id, version, tx_digest, timestamp_ms, receiver, coin_type, object_status, coin_balance FROM anchors
  UNION ALL
  SELECT object_id, version, tx_digest, timestamp_ms, receiver, coin_type, object_status, coin_balance FROM day_rows
),
calc AS (
  SELECT
    u.object_id,
    u.version,
    u.tx_digest,
    u.timestamp_ms,
    u.receiver,
    u.coin_type,
    u.object_status,
    u.coin_balance,
    LAG(u.receiver)     OVER (PARTITION BY u.object_id ORDER BY u.version) AS prev_owner,
    LAG(u.coin_balance) OVER (PARTITION BY u.object_id ORDER BY u.version) AS prev_balance
  FROM unioned u
),
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> Prefilter transactions by partition, then join (avoids scanning all <span class="hljs-built_in">tx</span> partitions)
tx_window AS (
  SELECT <span class="hljs-built_in">tx</span>.*
  FROM sui.transactions <span class="hljs-built_in">tx</span>
  CROSS JOIN params p
  WHERE <span class="hljs-built_in">tx</span>.date <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> p.start_date
    AND <span class="hljs-built_in">tx</span>.date <span class="hljs-operator">&lt;</span>  p.end_date
),
calc_with_tx AS (
  SELECT c.*, <span class="hljs-built_in">tx</span>.sender AS tx_sender
  FROM calc c
  LEFT JOIN tx_window <span class="hljs-built_in">tx</span>
    ON c.tx_digest <span class="hljs-operator">=</span> <span class="hljs-built_in">tx</span>.transaction_digest
),
outs AS (
  SELECT
    tx_digest,
    receiver,
    coin_type,
    SUM(
      CASE
        WHEN object_status <span class="hljs-operator">=</span> <span class="hljs-string">'Created'</span> THEN coin_balance
        WHEN object_status <span class="hljs-operator">=</span> <span class="hljs-string">'Mutated'</span> THEN greatest(coin_balance <span class="hljs-operator">-</span> COALESCE(prev_balance, <span class="hljs-number">0</span>), <span class="hljs-number">0</span>)
        ELSE <span class="hljs-number">0</span>
      END
    ) AS amount_raw
  FROM calc
  WHERE (object_status <span class="hljs-operator">=</span> <span class="hljs-string">'Created'</span> AND coin_balance <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>)
     OR (object_status <span class="hljs-operator">=</span> <span class="hljs-string">'Mutated'</span> AND (coin_balance <span class="hljs-operator">-</span> COALESCE(prev_balance, <span class="hljs-number">0</span>)) <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>)
  GROUP BY tx_digest, receiver, coin_type
),
tx_stats AS (
  SELECT tx_digest, COUNT() AS receiver_cnt
  FROM outs
  GROUP BY tx_digest
)
SELECT
  c.object_id,
  c.version,
  c.tx_digest,
  c.timestamp_ms,
  c.receiver,
  CASE WHEN c.object_status <span class="hljs-operator">=</span> <span class="hljs-string">'Created'</span> THEN c.tx_sender ELSE c.prev_owner END AS sender,
  c.coin_type,
  c.object_status,
  c.coin_balance,
  c.prev_balance,
  CASE
    WHEN c.object_status <span class="hljs-operator">=</span> <span class="hljs-string">'Created'</span> THEN c.coin_balance
    ELSE greatest(c.coin_balance <span class="hljs-operator">-</span> COALESCE(c.prev_balance, <span class="hljs-number">0</span>), <span class="hljs-number">0</span>)
  END AS effective_amount,
  CASE WHEN ts.receiver_cnt <span class="hljs-operator">=</span> <span class="hljs-number">1</span> THEN <span class="hljs-string">'transfer'</span> ELSE <span class="hljs-string">'other'</span> END AS classification,
  CASE WHEN ts.receiver_cnt <span class="hljs-operator">=</span> <span class="hljs-number">1</span> THEN TRUE ELSE FALSE END AS is_transfer
FROM calc_with_tx c
LEFT JOIN tx_stats ts
  ON c.tx_digest <span class="hljs-operator">=</span> ts.tx_digest
WHERE (c.object_status <span class="hljs-operator">=</span> <span class="hljs-string">'Created'</span> AND c.coin_balance <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>)
   OR (c.object_status <span class="hljs-operator">=</span> <span class="hljs-string">'Mutated'</span> AND (c.coin_balance <span class="hljs-operator">-</span> COALESCE(c.prev_balance, <span class="hljs-number">0</span>)) <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>);

</code></pre><h2 id="h-3-transfer-heuristics" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">3) Transfer Heuristics</h2><p>Sui’s object model doesn’t emit standardized “transfer” events like ERC-20 tokens on Ethereum. Instead, value movement must be inferred from how coin objects change owners or balances over time. To reconstruct these movements, the query aggregates all positive inflows on the <strong>receiver side</strong> of each transaction. This approach is designed to capture most genuine transfers with high recall—if someone truly receives tokens, that inflow will appear as a <code>Created</code> or <code>Mutated</code> coin object with a positive delta. As a result, <strong>false negatives are rare</strong>: real peer-to-peer (P2P) transfers almost always surface correctly.</p><p>However, there is a risk for false positives. Any DeFi applications—AMMs, lending protocols, routers, and bridges—mutate coin objects internally as part of swaps, liquidity operations, or accounting adjustments. These actions also create positive deltas that can look indistinguishable from user-to-user transfers when viewed at the object level.</p><p>To control for this, the query introduces a simple but effective classifier: a transaction is labeled a <em>transfer</em> only when it has a single receiver (<code>receiver_cnt = 1</code>). Transactions with multiple receivers are more likely to represent complex DeFi interactions and are therefore classified as “other.”</p><p>In practice, this “simple transfer” heuristic serves as a <strong>pragmatic first layer of classification</strong>: it cleanly isolates high-confidence P2P flows while leaving room for later enrichment into categories like “likely P2P,” “app-mediated,” or “internal protocol movement.” This balance between simplicity and interpretability makes it a good starting point for mapping economic flows in an object-centric system like Sui.</p><hr><h2 id="h-4-query-optimization" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">4) Query Optimization</h2><p>This query is deceptively heavy. Complexity comes from <strong>logical SQL</strong> and <strong>physical layout</strong>.</p><h3 id="h-41-logical-sql-complexity" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">4.1 Logical SQL Complexity</h3><ul><li><p><strong>Window functions (</strong><code>LAG</code><strong>)</strong> over <code>(PARTITION BY object_id ORDER BY version)</code> require Trino to sort and buffer complete per-object histories that land in the window.</p></li><li><p><strong>Union of anchors + in-window rows</strong> increases input size and sustained memory pressure.</p></li><li><p><strong>Post-window aggregates</strong> mean large intermediate results must be shuffled/aggregated after the sort phases.</p></li><li><p><strong>Joins to transactions</strong> add cross-stage shuffles if filters aren’t aligned.</p></li></ul><p>Result: multi-stage distributed execution with large <strong>network shuffles</strong> and potential <strong>spills</strong> when memory is tight.</p><h3 id="h-42-physical-data-layout-and-partitioning" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">4.2 Physical Data Layout and Partitioning</h3><p>On Dune, tables are <strong>clustered/partitioned by a </strong><code>date</code><strong> column</strong>, not by <code>coin_type</code> or <code>object_id</code>.</p><p>Implications:</p><ul><li><p><strong>Partition pruning works only along </strong><code>date</code>. The <code>WHERE</code> clause should include a filter on the <code>date</code> partition to avoid scanning every partition in the chosen period.</p></li><li><p>Filtering by <code>coin_type</code> or grouping by <code>object_id</code> <strong>does not reduce partitions scanned</strong>; it only filters <em>within</em> each touched partition.</p></li><li><p>Wrapping a partition column in functions may invalidate partition pruning efficiencies</p></li></ul><p>Takeaway: runtime scales roughly with both the <strong>number of active coin objects</strong> and <strong>the number of date partitions</strong> touched.</p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>defi</category>
            <category>sui</category>
            <category>usdc</category>
            <category>dune</category>
            <category>query</category>
            <category>sql</category>
            <category>optimization</category>
            <category>web3</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/7967716e7e18eed65a585d3063bed0e9ae33154e284e3e43dfcf4e73bfc97353.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Building an Agentic Native MCP for Data Science ]]></title>
            <link>https://paragraph.com/@evandekim/building-an-agentic-native-mcp-for-data-science</link>
            <guid>UHyXlU1oHBo6Y2rETLYa</guid>
            <pubDate>Thu, 23 Oct 2025 22:03:06 GMT</pubDate>
            <description><![CDATA[IntroductionThis is a build log of my first steps building an MCP server for Snowflake. It captures what I tried, what exists today, what worked, what did not, and where I am going next. If you are exploring or building your own MCP, I hope these notes and code references save you time. For install and a quickstart, see the igloo-mcp README.Table of contentsWhy I started building an MCPHow igloo-mcp differs from Snowflake Labs MCPHow igloo-mcp worksLearningsWhat's nextWhy I started building a...]]></description>
            <content:encoded><![CDATA[<br><h2 id="h-introduction" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Introduction</h2><p>This is a build log of my first steps building an MCP server for Snowflake. It captures what I tried, what exists today, what worked, what did not, and where I am going next. If you are exploring or building your own MCP, I hope these notes and code references save you time. For install and a quickstart, see the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/igloo-mcp">igloo-mcp README</a>.</p><h3 id="h-table-of-contents" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Table of contents</h3><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="#why-i-started-building-an-mcp">Why I started building an MCP</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="#how-igloo-mcp-differs-from-snowflake-labs-mcp">How igloo-mcp differs from Snowflake Labs MCP</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="#how-igloo-mcp-works">How igloo-mcp works</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="#learnings">Learnings</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="#whats-next">What's next</a></p></li></ul><h2 id="h-why-i-started-building-an-mcp" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Why I started building an MCP</h2><p>As a data scientist, there are always more questions than answers. I built <code>igloo-mcp</code> to optimize how agents run sequences of queries on my Snowflake database to answer the never ending stream of questions. The major benefit of agentic querying is that agents can quickly build the context needed to derive nontrivial insights with minimal instructions.</p><p>It's magical when everything works smoothly. I can rapidly debug and improve data pipelines, develop metrics faster, and also ask low priority but potentially interesting questions about datasets that I never had time to answer before. I also wanted a way to codify behaviors I found effective. MCPs are a natural choice to standardize tools that make agents' outputs more reliable and trustworthy.</p><p>Finally, I wanted to build the MCP from scratch to experiment with managing agents to design a repo from the ground up. When I use agents on the Snowflake database, I almost always use <code>igloo-mcp</code> to run tens of queries at a time. In contrast, building a repository lets me test multi agent workflows on code.</p><h2 id="h-how-igloo-mcp-differs-from-snowflake-labs-mcp" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How <code>igloo-mcp</code> differs from Snowflake Labs MCP</h2><ul><li><p>Stacking on top instead of rebuilding: <code>igloo-mcp</code> boots the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Snowflake-Labs/mcp">Snowflake Labs MCP</a> runtime and layers catalog/dependency workflows on top, which keeps the upstream Cortex/object/query tools available while adding the metadata utilities I rely on.</p></li><li><p>Zero config profiles vs YAML service maps: <code>igloo-mcp</code> reuses the SnowCLI profile that already exists on my machine, while Snowflake Labs MCP expects a <code>configuration.yaml</code> that lists Cortex services, tool groups, and SQL permissions before the server can start.</p></li><li><p>Safety first execution path: <code>igloo-mcp</code> runs queries through the CLI to inherit profile RBAC, validate statements against allow/deny lists, check profile health, and optionally return verbose error diagnostics. Snowflake Labs MCP executes through the connector and surfaces failures through a generic <code>SnowflakeException</code>, leaving destructive statements enabled unless you turn them off in YAML.</p></li></ul><h2 id="h-how-igloo-mcp-works" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How <code>igloo-mcp</code> works</h2><p>I evaluated Snowflake Labs MCP to understand its strengths and how to make an MCP that better fits my agentic workflows. <code>igloo-mcp</code> reflects those choices.</p><p>In practice, the server prioritizes safe, observable query execution for agent loops:</p><ul><li><p>Guardrails default to read‑only and provide clear, actionable messages when a statement is blocked (including when Snowflake classifies it as a generic <code>Command</code>).</p></li><li><p>Each request can attach a short <code>reason</code> that is propagated to the Snowflake <code>QUERY_TAG</code> and to the optional local history, making it easy to correlate Snowflake history with local runs.</p></li><li><p>Schema‑first tool design: each tool declares a tight JSON schema (with <code>additionalProperties=false</code>) and concrete examples so clients discover and call tools predictably.</p></li><li><p>Predictable ergonomics: compact errors by default keep LLM loops clean; opt‑in verbose mode adds structured “quick fixes” when you need depth.</p></li></ul><p>Currently, functionality includes:</p><h3 id="h-tools" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Tools</h3><ul><li><p><code>execute_query</code> — Executes SQL via SnowCLI; supports warehouse/database/schema/role overrides, allow/deny validation, timeouts, and optional verbose errors</p></li><li><p><code>preview_table</code> — Returns a limited preview of a table</p></li><li><p><code>build_catalog</code> — Builds metadata from INFORMATION_SCHEMA (account or database scope) with JSON/JSONL output; includes DDL</p></li><li><p><code>get_catalog_summary</code> — Reads and returns catalog summary from a directory</p></li><li><p><code>build_dependency_graph</code> — Produces object dependency graph (JSON or DOT)</p></li><li><p><code>test_connection</code> — Verifies Snowflake connectivity using the configured profile</p></li><li><p><code>health_check</code> — Reports server and profile health</p></li></ul><h3 id="h-defaults" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Defaults</h3><ul><li><p>SQL guardrails default to read-only. <code>INSERT</code>, <code>UPDATE</code>, <code>CREATE</code>, <code>ALTER</code>, <code>DELETE</code>, <code>DROP</code>, and <code>TRUNCATE</code> are blocked by default; can be explicitly opted into for controlled workflows. Statements Snowflake classifies as generic "Command" are also rejected by default.</p></li></ul><h3 id="h-execution" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Execution</h3><ul><li><p>Error handling:</p><ul><li><p>Compact by default keeps agent loops readable and reduces noise</p></li><li><p>Verbose on demand includes a SQL snippet, timeout, and context for faster debugging</p></li><li><p>Profile health checks fail fast when a profile is missing or misconfigured</p></li><li><p>Allow/deny validation blocks risky or unsupported statements before anything runs</p></li><li><p>Clearer safety messages when Snowflake's parser falls back to a generic <code>Command</code> type, so agents know why a statement was blocked and what safe alternatives exist</p></li><li><p>Messages are formatted so MCP clients surface actionable feedback instead of long stack traces</p></li></ul></li><li><p>Session overrides: per request overrides for warehouse, database, schema, and role</p></li><li><p>Timeouts &amp; cancellation: per‑request timeouts are enforced; on local timeout a best‑effort server‑side cancel is issued and the tool returns immediately. Query ID is captured when available; verbose timeouts include concrete next steps.</p></li></ul><h3 id="h-observability" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Observability</h3><ul><li><p>Lightweight local query history (opt‑in): set <code>IGLOO_MCP_QUERY_HISTORY=/path/to/history.jsonl</code> to write one JSON object per executed query. Each event includes a status (<code>success</code>/<code>timeout</code>/<code>error</code>), a truncated statement preview, optional <code>query_id</code> (when available), <code>rowcount</code>/<code>duration_ms</code> for successful reads, the effective timeout, and any session overrides. The optional <code>reason</code> you pass is stored in both Snowflake <code>QUERY_TAG</code> and the local history, which makes it easy to join Snowflake account history with local events for auditing and debugging.</p></li></ul><h4 id="h-why-add-local-history" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Why add local history?</h4><ul><li><p>Tie agent prompts to database activity without standing up infra; JSONL is append‑only and portable.</p></li><li><p>Correlate with Snowflake’s <code>ACCOUNT_USAGE.QUERY_HISTORY</code> by <code>QUERY_TAG</code> to get warehouse cost, bytes scanned, or execution profile.</p></li><li><p>Debug timeouts and cancellations: even when <code>query_id</code> is missing, the preview + <code>reason</code> + status help reconstruct sequences.</p></li><li><p>Lightweight telemetry: count queries per task, calculate duration distributions, and quickly spot recurring errors.</p></li></ul><h4 id="h-enable-and-sample-entries" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Enable and sample entries</h4><pre data-type="codeBlock" text="export IGLOO_MCP_QUERY_HISTORY=./igloo_query_history.jsonl
"><code>export IGLOO_MCP_QUERY_HISTORY<span class="hljs-operator">=</span>./igloo_query_history.jsonl
</code></pre><p>Example lines (one JSON object per line):</p><pre data-type="codeBlock" text="{&quot;ts&quot;: 1737412345, &quot;status&quot;: &quot;success&quot;, &quot;profile&quot;: &quot;quickstart&quot;, &quot;statement_preview&quot;: &quot;SELECT * FROM customers LIMIT 10&quot;, &quot;rowcount&quot;: 10, &quot;timeout_seconds&quot;: 30, &quot;overrides&quot;: {&quot;warehouse&quot;: &quot;COMPUTE_WH&quot;}, &quot;query_id&quot;: &quot;01a1b2c3d4&quot;, &quot;duration_ms&quot;: 142, &quot;reason&quot;: &quot;Explore sample customers&quot;}
{&quot;ts&quot;: 1737412399, &quot;status&quot;: &quot;timeout&quot;, &quot;profile&quot;: &quot;quickstart&quot;, &quot;statement_preview&quot;: &quot;SELECT * FROM huge_table WHERE date &gt;= '2024-01-01'&quot;, &quot;timeout_seconds&quot;: 30, &quot;overrides&quot;: {&quot;warehouse&quot;: &quot;COMPUTE_WH&quot;}, &quot;error&quot;: &quot;Query execution exceeded timeout and was cancelled&quot;}
{&quot;ts&quot;: 1737412468, &quot;status&quot;: &quot;error&quot;, &quot;profile&quot;: &quot;quickstart&quot;, &quot;statement_preview&quot;: &quot;SELECT * FROM missing_table&quot;, &quot;timeout_seconds&quot;: 30, &quot;overrides&quot;: {}, &quot;error&quot;: &quot;Object 'MISSING_TABLE' does not exist.&quot;}
"><code><span class="hljs-punctuation">{</span><span class="hljs-attr">"ts"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1737412345</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"status"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"success"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"profile"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"quickstart"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"statement_preview"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"SELECT * FROM customers LIMIT 10"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"rowcount"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"timeout_seconds"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">30</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"overrides"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-attr">"warehouse"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"COMPUTE_WH"</span><span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"query_id"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"01a1b2c3d4"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"duration_ms"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">142</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"reason"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Explore sample customers"</span><span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">{</span><span class="hljs-attr">"ts"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1737412399</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"status"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"timeout"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"profile"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"quickstart"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"statement_preview"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"SELECT * FROM huge_table WHERE date &gt;= '2024-01-01'"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"timeout_seconds"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">30</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"overrides"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-attr">"warehouse"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"COMPUTE_WH"</span><span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"error"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Query execution exceeded timeout and was cancelled"</span><span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">{</span><span class="hljs-attr">"ts"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1737412468</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"status"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"error"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"profile"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"quickstart"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"statement_preview"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"SELECT * FROM missing_table"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"timeout_seconds"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">30</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"overrides"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"error"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Object 'MISSING_TABLE' does not exist."</span><span class="hljs-punctuation">}</span>
</code></pre><h2 id="h-learnings" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Learnings</h2><p>I pushed the boundaries to see where agent capabilities currently end. The biggest revelation was that agents cannot be trivially parallelized on large codebases. It requires thoughtful planning, and there are not yet great solutions to monitor multiple agents working on different things. However, there are promising products like Claude Subagents and Zed CLI CRDTs (Conflict free Replicated Data Types).</p><p>Another limitation is the human ability to keep up with multiple agents' work. That said, it feels like more parallel autonomous agents are getting closer, and I am looking forward to seeing more of this soon.</p><p>A large refactor cut the codebase from more than 20k LOC to less than 5k LOC; repo wide renaming required multiple assistive passes.</p><h2 id="h-whats-next" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What's next</h2><p>I will keep iterating on <code>igloo-mcp</code> to continue learning how to build strong MCP tools and what does and does not work in practice for different workflows. For instance, I am doing heavy quantitative research and I am exploring a cache for the <code>execute_query</code> tool and richer query history features (per‑project grouping, rotation, and search) on top of the current JSONL history.</p><p>The other angle is that I find it useful to hand pick issues by defining the problem and desired result, then have the LLM write a more in depth issue and eventually fix it and open a PR. As a result, I spend more time as a product manager and power user of my own work while delegating more boilerplate aspects of development to different agents in the background.</p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>data</category>
            <category>analytics</category>
            <category>llm</category>
            <category>agentic</category>
            <category>mcp</category>
            <category>snowflake</category>
            <category>db</category>
            <category>igloo-mcp</category>
            <category>python</category>
            <category>web3data</category>
            <category>blockchain</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/11b807c885ff5236396b95a8f2c9aa5db68c263a4780070dd429ed7a7c1afe04.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[LLM-Enabled Data Pipeline for Analyzing Smart Contracts (Featuring Move Packages)]]></title>
            <link>https://paragraph.com/@evandekim/llm-enabled-data-pipeline-for-analyzing-smart-contracts-featuring-move-packages</link>
            <guid>U9YnPICX5TwW0kFTiUAp</guid>
            <pubDate>Fri, 22 Aug 2025 02:55:28 GMT</pubDate>
            <description><![CDATA[This article presents an AI-powered, LLM-enabled data pipeline for smart contract analytics on the Sui blockchain. Using Snowflake, it curates structured event and object catalogues from 95k+ Move packages, enabling LLMs to perform on-chain data analysis at scale. The pipeline automates SQL generation for DeFi metrics like trading volume, funding rates, DAUs, and open interest, showcasing a scalable approach to blockchain data pipelines.]]></description>
            <content:encoded><![CDATA[<h3 id="h-table-of-contents" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Table of Contents</strong></h3><ol><li><p><strong>The Universal Challenge: The "New Protocol" Learning Curve</strong></p></li><li><p><strong>Building an LLM-First Pipeline for Move Package Analysis</strong></p></li><li><p><strong>Case Study: Bluefin Pro Perps Analysis</strong></p><ul><li><p>LLM Pipeline Workflow</p></li><li><p>Prompt Iteration Highlights</p></li><li><p>Takeaways &amp; Results</p></li></ul></li><li><p><strong>Final Outputs</strong></p><ul><li><p>SQL views and daily metrics</p></li><li><p>Charts and dashboards</p></li></ul></li><li><p><strong>Appendix: Technical References</strong></p><ul><li><p>Condensed Prompt Chat (all with superwhisper)</p></li><li><p>Final LLM SQL Output at end of convo:</p></li><li><p>Move Package Catalogue Tasks</p><ul><li><p>Events</p></li><li><p>Objects</p></li><li><p>Snowflake Table Data Catalogue</p></li><li><p>Bluefin Daily Market View Output:</p></li></ul></li></ul></li></ol><h1 id="h-the-universal-challenge-the-new-protocol-learning-curve" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>The Universal Challenge: The "New Protocol" Learning Curve</strong></h1><p>A persistent challenge in crypto data analytics is the sheer diversity of smart contracts. Every protocol ships its own set of contracts that must be fully understood before you can build data pipelines and extract actionable insights.</p><p>On Sui alone, there are 95k**+ Move packages** emitting <strong>350,000+ events</strong> and nearly <strong>900k objects</strong>—each with distinct schema and structure. Even protocols in the same category, such as the CLMM-based DEXs <em>Cetus</em> and <em>Bluefin</em>, can have significant implementation differences. Even when they don’t, the contracts must still be analyzed to confirm parity. This constant variability makes it challenging for analysts to keep pace with the rapid innovation happening on any chain.</p><h1 id="h-building-an-llm-first-pipeline-for-move-package-analysis" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Building an LLM-First Pipeline for Move Package Analysis</h1><p>I prototyped an LLM-enabled data pipeline to accelerate package analysis by strictly managing a local, context-rich environment. The system centers on two curated data catalogues that provide the model with the critical context it needs. I have attached the snowflake tasks and view in the appendix for additional reference:</p><ol><li><p><strong>Snowflake Data Catalogue</strong> – A SQL view extracts metadata for all relevant Snowflake tables. The results are exported as CSV, and a Python script converts each row into a <code>.sql</code> file, with a strict <strong>1:1 mapping between table and file</strong>. This setup gives the LLM complete context for <strong>400+ Snowflake tables</strong>, enabling it to selectively load only the subset needed for a given task.</p></li><li><p><strong>Move Package Catalogue</strong> – Snowflake tasks index and store all unique event and object data for a specific package. The output is stored in an LLM-friendly format, giving the model a <strong>complete on-chain view</strong> for that package. With this holistic view of objects and events, the LLM can detect patterns and relationships more effectively.</p></li></ol><p>This initial design keeps LLM access fully isolated—no direct database reads or writes. It also works well with cost-efficient models; for example, I primarily use <em>Gemini Flash 2.5 (non-max mode)</em> for most tasks.</p><p>These foundational tables and views provide exceptional context for the LLM. By comparison, Cursor rules have shown little additional performance benefit. There are some very basic rules that I currently use:</p><ul><li><p>Show sample datasets for core Snowflake tables (events, objects, transactions) and some non-core DeFi tables (coin info, prices). This is somewhat redundant with the Snowflake Data Catalogue now and was a legacy cursor rule.</p></li><li><p>Include simple operational guidelines such as: <em>write queries efficiently</em>, <em>check Snowflake SQL syntax</em>, and <em>be concise</em>.</p></li></ul><p>With this foundation in place, I tested the pipeline on a real-world case—analyzing Bluefin’s Pro Perps package.</p><h1 id="h-case-study-bluefin-pro-perps" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Case Study - Bluefin Pro Perps</h1><h3 id="h-llm-pipeline-workflow" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>LLM Pipeline Workflow</strong></h3><ol><li><p><strong>Find relevant package data</strong> – Manually locate Bluefin Pro package objects and events from the Move Package Catalogue and export them for LLM use.</p></li><li><p><strong>Prompt the LLM with context</strong> – Load the package data into the LLM and ask it to identify relevant events and generate a query for daily trading volume by perpetual market.</p></li><li><p><strong>Iterate on results</strong> – Review outputs, make targeted corrections, and request additional metrics (funding rate, daily price, daily active users, open interest).</p></li><li><p><strong>Plot the results and check accuracy</strong></p></li></ol><h3 id="h-prompt-iteration-highlights" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Prompt Iteration Highlights</strong></h3><ul><li><p><strong>First pass</strong> – Query worked, but BTC, ETH, and SOL market decimals were off by 1. Prompted the LLM to adjust.</p></li><li><p><strong>Subsequent passes</strong> – Added average daily funding rate, daily price, DAUs, and open interest.</p></li><li><p><strong>Challenge</strong> – Open interest calculations were consistently wrong; the LLM lacked the right DeFi context. Several prompts failed to fix it until switching from “daily average” to “end-of-day snapshot” calculations.</p></li><li><p><strong>Resolution</strong> – After explicitly flagging the snapshot logic as incorrect, the LLM revised it to calculate open interest correctly.</p></li></ul><h3 id="h-takeaways-and-results" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Takeaways &amp; Results</h3><ul><li><p><strong>LLM finds relevant events autonomously</strong> – Without any manual intervention, it correctly identified key events from the package-level context.</p></li><li><p><strong>Complex metrics need iteration</strong> – Multiple passes are normal for nuanced DeFi calculations like open interest.</p></li><li><p><strong>Flagging &gt; explaining</strong> – Pointing out “this snapshot is wrong” was more effective than prescribing exact fixes.</p></li><li><p><strong>Documentation is built-in</strong> – Successful conversations leave the LLM with the full context to auto-generate comments for tables, views, and columns.</p></li></ul><p>The end result is an underlying table that makes it effortless to plot something like this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/59f31ffa8c5115ddc7c3a39362e0ddc3.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAARCAIAAAAzPjmrAAAACXBIWXMAABYlAAAWJQFJUiTwAAAEX0lEQVR4nKVUe0xbZRQ/mf8YY2bUZWjE4ZRNl6gMY0CyLTp8DOeLDYgrygibmxBAWQsMlvGcKzAHjKVbeGxjFjL2co04UMJzWIoUWnqBlrXD1SBDEve4vYX76Nevn7m9QAvuP0/6x3n87vmdc76eA5OTk1qtVqfTdXV16fV6g8Gg0+ns9tvkf8gf9j8v6E0MywqCANPT0xRFjY6OGo1Gs9lstVoHhgzjtyYQQqwXQdNOwSssyyFuFnGsyxvieH5O4KSQIAgMw0h4lmX/nplpG7M5GIbnePBndmMPIaR20Jyh6bBZzCaTaWDAGBd/4EavboQyjdy0jTWdMDZUjNycMJuo/nFjflvt8IhphKLMZrNCodBoNOPj4xRF2e12m8VCURRN04AXBCEkCIIbuVlemOUFQojkd87OYYznKxB4JPCSiTHmXD6dZVmE0KLpQqJgjH0d0DRttdoYhiGEeLx8EisRrXkTY7FHSXEj5BJcLoQ4nvPC5v2L4iHiPHwEGGNBEKQUYsTn9/hZItmiPPSFl4V8BCzLWq1WqQMJ5EJigQs6WlKdN0VydkZdk1pZX2WwUIQQaShSNgntIxBTCOLc/lsXXvCIg3IjxAmIdw30aA19/SDfuvtMLsjfrutvJoS43MhDPKxzbtbhRBgjfwKGcVqtNrvdTtM0wpjjeULIiSsNlzp/IYTcdziutLXN3LtHCCnRqLeXKULk8a9lJlySBXUoE/OjVxlaz3u8MyCE5Fwo365MFqfp/wYM45yaurM4PuQWH+2d7PfST6VjFx6yGAI+Cvix86dWw2ijKq34q/WVpbEnj+8qDg1sSo2reAHMF1WEkGu9zTGqvKv5O87vWZd7ta6y+Qd/AsZqtU1N3bFM2MZu32pqUVdfV9dEBjfs3bbraFKtKqcyGM4eS4v6emNmbPQHqwNTwkLTI94EeC4k7DMAUOQotdN/taqyqqJX1Ce8Xx4RXJgYosr8eMkbSA02lByM3fDEIVmkfNsGgJfXrtsiD1mZK9sJANnxCSmBj4aEfQKwBp7fAmu3QmBE6Kf7AJ6M2bv/w6LN2UmyqEceD3wrBlaHb1y1PuqNd5f8ixiGcdD3d6ccBngpKCIOAjfDF9mw7zDAK69Hp8Jjr26SyQHWBH2rhB3fwIEyUByHjGPPVqhhp/zLrLyUFRApS4WnwuFINeSehGfCAzZ9voQAud2EkOjGFkgsgOIa8XtFORTWwJ5CKFdDQt6LqouQcAjK6iH5KGSWw8GTYkh5FpKKVspLAZ6GnErYfwSKaiCtFNKUUHB6CQEhhJ+d1QxSudc6zvUZq7oHvm/vO9WpU7b3Nfab5JdbLw+NFVzvPffbYPHP3TXa4ereoXxN55kbg0W/apv0VJamXd0//F2btrZHX9LSU9n9e8Pg2HKC+U317r2kSwsl9ieI10lyOukHTodDcroXwIjjJLB0RR6yyb5tWvZzu/11QsgDmv7n7l3PwrpKAIw9EmD+dGIs3eZ/Ad67Awo/ZjD5AAAAAElFTkSuQmCC" nextheight="722" nextwidth="1374" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><br><h1 id="h-appendix" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Appendix:</h1><h2 id="h-condensed-prompt-chat-all-with-superwhisper" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Condensed Prompt Chat (all with superwhisper)</h2><p><code>Take a look at all of these Bluefin Pro package events. They have different event names, they have module names, as well as event JSON information. Now the file is a little messy, but are you able, can you infer which events I should focus on and create a SQL query so that I can get the trading volume broken down by perp market every day? @bbluefin_pro_package_events.csv</code> The prompt worked on the first try. After initial inspection I noticed that the BTC, ETH, and SOL market decimals were off by 1. I told it to "fix it". <code>Okay, this looks almost correct. It looks like the BTC perp market and the ETH perp market are one decimal off. So like BTC says, for example, 93 million, but I think it's closer to 9.3 million. Same with ETH. Can you please fix that just for BTC and ETH? The rest of the decimals look good, though.</code> Fix was implemented. Then I realized that Solana market decimals were off too. Also I casually mentioned if it could find the funding rate from the events and calculate the daily average funding rate. I didn't specify the event or the event type structure because LLM has the package context already. <code>Ah, my bad. It looks like Solana, the Soul Perp market also has an extra decimal, so please move it. It says 32 million, but I think it should be closer to 3.2 million. Otherwise it looks good. Additionally, are you able to add a column to get the average funding rate, the average daily funding rate for each market?@bluefin_pro_package_events.csv</code> I also asked for a daily average price column, a daily active user count, and a daily average open interest as well. These were all added in after a first request. Upon inspecting the query outputs, I notiecd that the open interest was denominated in the native token amounts. So I asked "add usd open interest column too".</p><p>The open interest calculation was incorrect. The LLM doesn't have the right DeFi context to properly calculate open interest so I guided it a little. The re-prompt did not fix it properly: <code>Okay, well, so I like the columns, but I'm not sure that the output is correct. How exactly are you calculating the average? Because I think this is a little complex because you have to, you can't just take all of the orders and then take the average. That's going to over count the numbers because you're not a count, you need to properly account for like how many orders are open at every funding interval versus how many get closed and then take the average of just the open orders at every funding interval.</code></p><p>I changed query prompt approach again. Still incorrect. <code>Um, actually instead of doing a daily average, could we just take a daily snapshot instead? Uh, do this, take the snapshot for the end of every day for all the positions that are open.</code></p><p>Third time is the charm! The problem was isolated to the snapshot calculation and pointed out and the LLM was able to revise the snapshot calculations to correctly calculate the open interest. <code>No, this isn't correct still. The numbers for the open interest in USD amounts are still way too high. I don't know what you're doing here. I don't know how to troubleshoot this either, but the numbers are still way too high. So I don't know if the snapshot is being calculated properly. I would guess you're still accounting for positions that are...you haven't properly accounted for positions that are closed, maybe?</code></p><h2 id="h-final-llm-sql-output-at-end-of-convo" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Final LLM SQL Output at end of convo:</h2><pre data-type="codeBlock" text="CREATE OR REPLACE VIEW PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.BLUEFIN_PRO_DAILY_MARKET_METRICS_V
COMMENT = 'Provides daily aggregated metrics for Bluefin Pro perpetual markets. It includes daily trading volume (USD), volume-weighted average price (USD), daily active users (DAU), end-of-day (EOD) open interest in both native token and USD, and the average daily funding rate. Data is sourced from Bluefin Pro package events (0xe744...). NOTE: A manual decimal correction (+1) is applied to BTC-PERP, ETH-PERP, and SOL-PERP markets to correct an observed 10x scaling discrepancy in the raw on-chain data for accurate calculations.'
AS
WITH
-- ============================================================================
-- STEP 1: Get the latest on-chain metadata for each market (e.g., decimals)
-- ============================================================================
market_metadata AS (
    SELECT
        event_json:perpetual:symbol::string as market,
        -- Derive decimal count from on-chain value (e.g., 8000000000 -&gt; 8)
        event_json:perpetual:base_asset_decimals::number / 1e9 as base_asset_decimals
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET
    WHERE event_type IN (
        '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::PerpetualUpdate',
        '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::PerpetualSynced'
    )
    AND market IS NOT NULL
    AND base_asset_decimals IS NOT NULL
    QUALIFY ROW_NUMBER() OVER (PARTITION BY market ORDER BY timestamp_ms DESC) = 1
),

-- ============================================================================
-- STEP 2: Calculate daily trading volume, average price, and active users
-- ============================================================================
trade_details AS (
    SELECT
        t.TIMESTAMP_MS,
        t.EVENT_JSON:market::string as market,
        t.EVENT_JSON:fill_price::number as fill_price,
        t.EVENT_JSON:fill_quantity::number as fill_quantity,
        t.EVENT_JSON:maker:address::string as maker_address,
        t.EVENT_JSON:taker:address::string as taker_address,
        -- MANUAL CORRECTION: Add 1 to decimals for specific markets due to
        -- an observed 10x discrepancy in their `fill_quantity` scaling.
        CASE
            WHEN m.market IN ('BTC-PERP', 'ETH-PERP', 'SOL-PERP') THEN m.base_asset_decimals + 1
            ELSE m.base_asset_decimals
        END as base_asset_decimals
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET t
    JOIN market_metadata m ON t.EVENT_JSON:market::string = m.market
    WHERE t.EVENT_TYPE = '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::TradeExecuted'
),

trade_participants AS (
    SELECT TIMESTAMP_MS, market, maker_address as user_address FROM trade_details
    UNION
    SELECT TIMESTAMP_MS, market, taker_address as user_address FROM trade_details
),

daily_active_users AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms/1000)) as trade_date,
        market,
        COUNT(DISTINCT user_address) as dau
    FROM trade_participants
    GROUP BY 1,2
),

daily_trade_metrics AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms / 1000)) as trade_date,
        market,
        SUM((fill_price / 1e9) * (fill_quantity / POW(10, base_asset_decimals))) as daily_volume_usd,
        SUM(fill_price * fill_quantity) / SUM(fill_quantity) / 1e9 as daily_average_price
    FROM trade_details
    GROUP BY 1, 2
),

-- ============================================================================
-- STEP 3: Calculate average daily funding rate from 'FundingRateUpdated' events
-- ============================================================================
daily_funding_rates AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms / 1000)) as rate_date,
        event_json:market::string as market,
        AVG((CASE WHEN event_json:rate:sign::boolean THEN 1 ELSE -1 END * event_json:rate:value::number) / 1e9) as avg_daily_funding_rate
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET
    WHERE event_type = '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::FundingRateUpdated'
    GROUP BY 1, 2
),

-- ============================================================================
-- STEP 4: Get End-of-Day Open Interest Snapshot
-- ============================================================================
open_interest_snapshots AS (
    SELECT
        t.timestamp_ms,
        t.event_json:position:perpetual::string as market,
        -- MANUAL CORRECTION: Apply the same decimal adjustment here for `position:size`
        SUM(t.event_json:position:size::number / POW(10,
            CASE
                WHEN m.market IN ('BTC-PERP', 'ETH-PERP', 'SOL-PERP') THEN m.base_asset_decimals + 1
                ELSE m.base_asset_decimals
            END
        )) as oi_snapshot_native
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET t
    JOIN market_metadata m ON t.event_json:position:perpetual::string = m.market
    WHERE t.event_type = '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::FundingRateApplied'
      AND t.event_json:position:is_long::boolean = true
    GROUP BY 1, 2
),

eod_open_interest AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms/1000)) as oi_date,
        market,
        oi_snapshot_native as eod_open_interest_native
    FROM open_interest_snapshots
    QUALIFY ROW_NUMBER() OVER (PARTITION BY oi_date, market ORDER BY timestamp_ms DESC) = 1
)

-- ============================================================================
-- FINAL STEP: Join all the daily metrics together
-- ============================================================================
SELECT
    m.trade_date,
    m.market,
    m.daily_volume_usd,
    m.daily_average_price,
    d.dau AS daily_active_users,
    oi.eod_open_interest_native,
    oi.eod_open_interest_native * m.daily_average_price as eod_open_interest_usd,
    f.avg_daily_funding_rate
FROM daily_trade_metrics m
LEFT JOIN daily_active_users d ON m.trade_date = d.trade_date AND m.market = d.market
LEFT JOIN eod_open_interest oi ON m.trade_date = oi.oi_date AND m.market = oi.market
LEFT JOIN daily_funding_rates f ON m.trade_date = f.rate_date AND m.market = f.market;

-- Create a temporary table from the view for immediate dashboarding use
CREATE OR REPLACE TEMPORARY TABLE TMP_BLUEFIN_PRO_METRICS AS
SELECT * FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.BLUEFIN_PRO_DAILY_MARKET_METRICS_V;

"><code>CREATE OR REPLACE VIEW PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.BLUEFIN_PRO_DAILY_MARKET_METRICS_V
COMMENT <span class="hljs-operator">=</span> <span class="hljs-string">'Provides daily aggregated metrics for Bluefin Pro perpetual markets. It includes daily trading volume (USD), volume-weighted average price (USD), daily active users (DAU), end-of-day (EOD) open interest in both native token and USD, and the average daily funding rate. Data is sourced from Bluefin Pro package events (0xe744...). NOTE: A manual decimal correction (+1) is applied to BTC-PERP, ETH-PERP, and SOL-PERP markets to correct an observed 10x scaling discrepancy in the raw on-chain data for accurate calculations.'</span>
AS
WITH
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> STEP <span class="hljs-number">1</span>: Get the latest on<span class="hljs-operator">-</span>chain metadata <span class="hljs-keyword">for</span> each market (e.g., decimals)
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
market_metadata AS (
    SELECT
        event_json:perpetual:symbol::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> market,
        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Derive decimal count <span class="hljs-keyword">from</span> on<span class="hljs-operator">-</span>chain value (e.g., <span class="hljs-number">8000000000</span> <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-number">8</span>)
        event_json:perpetual:base_asset_decimals::number <span class="hljs-operator">/</span> <span class="hljs-number">1e9</span> <span class="hljs-keyword">as</span> base_asset_decimals
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET
    WHERE event_type IN (
        <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::PerpetualUpdate'</span>,
        <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::PerpetualSynced'</span>
    )
    AND market IS NOT NULL
    AND base_asset_decimals IS NOT NULL
    QUALIFY ROW_NUMBER() OVER (PARTITION BY market ORDER BY timestamp_ms DESC) <span class="hljs-operator">=</span> <span class="hljs-number">1</span>
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> STEP <span class="hljs-number">2</span>: Calculate daily trading volume, average price, and active users
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
trade_details AS (
    SELECT
        t.TIMESTAMP_MS,
        t.EVENT_JSON:market::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> market,
        t.EVENT_JSON:fill_price::number <span class="hljs-keyword">as</span> fill_price,
        t.EVENT_JSON:fill_quantity::number <span class="hljs-keyword">as</span> fill_quantity,
        t.EVENT_JSON:maker:<span class="hljs-keyword">address</span>::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> maker_address,
        t.EVENT_JSON:taker:<span class="hljs-keyword">address</span>::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> taker_address,
        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> MANUAL CORRECTION: Add <span class="hljs-number">1</span> to decimals <span class="hljs-keyword">for</span> specific markets due to
        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> an observed 10x discrepancy in their `fill_quantity` scaling.
        CASE
            WHEN m.market IN (<span class="hljs-string">'BTC-PERP'</span>, <span class="hljs-string">'ETH-PERP'</span>, <span class="hljs-string">'SOL-PERP'</span>) THEN m.base_asset_decimals <span class="hljs-operator">+</span> <span class="hljs-number">1</span>
            ELSE m.base_asset_decimals
        END <span class="hljs-keyword">as</span> base_asset_decimals
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET t
    JOIN market_metadata m ON t.EVENT_JSON:market::<span class="hljs-keyword">string</span> <span class="hljs-operator">=</span> m.market
    WHERE t.EVENT_TYPE <span class="hljs-operator">=</span> <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::TradeExecuted'</span>
),

trade_participants AS (
    SELECT TIMESTAMP_MS, market, maker_address <span class="hljs-keyword">as</span> user_address FROM trade_details
    UNION
    SELECT TIMESTAMP_MS, market, taker_address <span class="hljs-keyword">as</span> user_address FROM trade_details
),

daily_active_users AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms<span class="hljs-operator">/</span><span class="hljs-number">1000</span>)) <span class="hljs-keyword">as</span> trade_date,
        market,
        COUNT(DISTINCT user_address) <span class="hljs-keyword">as</span> dau
    FROM trade_participants
    GROUP BY <span class="hljs-number">1</span>,<span class="hljs-number">2</span>
),

daily_trade_metrics AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>)) <span class="hljs-keyword">as</span> trade_date,
        market,
        SUM((fill_price <span class="hljs-operator">/</span> <span class="hljs-number">1e9</span>) <span class="hljs-operator">*</span> (fill_quantity <span class="hljs-operator">/</span> POW(<span class="hljs-number">10</span>, base_asset_decimals))) <span class="hljs-keyword">as</span> daily_volume_usd,
        SUM(fill_price <span class="hljs-operator">*</span> fill_quantity) <span class="hljs-operator">/</span> SUM(fill_quantity) <span class="hljs-operator">/</span> <span class="hljs-number">1e9</span> <span class="hljs-keyword">as</span> daily_average_price
    FROM trade_details
    GROUP BY <span class="hljs-number">1</span>, <span class="hljs-number">2</span>
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> STEP <span class="hljs-number">3</span>: Calculate average daily funding rate <span class="hljs-keyword">from</span> <span class="hljs-string">'FundingRateUpdated'</span> events
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
daily_funding_rates AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>)) <span class="hljs-keyword">as</span> rate_date,
        event_json:market::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> market,
        AVG((CASE WHEN event_json:rate:sign::boolean THEN <span class="hljs-number">1</span> ELSE <span class="hljs-number">-1</span> END <span class="hljs-operator">*</span> event_json:rate:<span class="hljs-built_in">value</span>::number) <span class="hljs-operator">/</span> <span class="hljs-number">1e9</span>) <span class="hljs-keyword">as</span> avg_daily_funding_rate
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET
    WHERE event_type <span class="hljs-operator">=</span> <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::FundingRateUpdated'</span>
    GROUP BY <span class="hljs-number">1</span>, <span class="hljs-number">2</span>
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> STEP <span class="hljs-number">4</span>: Get End<span class="hljs-operator">-</span>of<span class="hljs-operator">-</span>Day Open Interest Snapshot
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
open_interest_snapshots AS (
    SELECT
        t.timestamp_ms,
        t.event_json:position:perpetual::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> market,
        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> MANUAL CORRECTION: Apply the same decimal adjustment here <span class="hljs-keyword">for</span> `position:size`
        SUM(t.event_json:position:size::number <span class="hljs-operator">/</span> POW(<span class="hljs-number">10</span>,
            CASE
                WHEN m.market IN (<span class="hljs-string">'BTC-PERP'</span>, <span class="hljs-string">'ETH-PERP'</span>, <span class="hljs-string">'SOL-PERP'</span>) THEN m.base_asset_decimals <span class="hljs-operator">+</span> <span class="hljs-number">1</span>
                ELSE m.base_asset_decimals
            END
        )) <span class="hljs-keyword">as</span> oi_snapshot_native
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET t
    JOIN market_metadata m ON t.event_json:position:perpetual::<span class="hljs-keyword">string</span> <span class="hljs-operator">=</span> m.market
    WHERE t.event_type <span class="hljs-operator">=</span> <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::FundingRateApplied'</span>
      AND t.event_json:position:is_long::boolean <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>
    GROUP BY <span class="hljs-number">1</span>, <span class="hljs-number">2</span>
),

eod_open_interest AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms<span class="hljs-operator">/</span><span class="hljs-number">1000</span>)) <span class="hljs-keyword">as</span> oi_date,
        market,
        oi_snapshot_native <span class="hljs-keyword">as</span> eod_open_interest_native
    FROM open_interest_snapshots
    QUALIFY ROW_NUMBER() OVER (PARTITION BY oi_date, market ORDER BY timestamp_ms DESC) <span class="hljs-operator">=</span> <span class="hljs-number">1</span>
)

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> FINAL STEP: Join all the daily metrics together
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
SELECT
    m.trade_date,
    m.market,
    m.daily_volume_usd,
    m.daily_average_price,
    d.dau AS daily_active_users,
    oi.eod_open_interest_native,
    oi.eod_open_interest_native <span class="hljs-operator">*</span> m.daily_average_price <span class="hljs-keyword">as</span> eod_open_interest_usd,
    f.avg_daily_funding_rate
FROM daily_trade_metrics m
LEFT JOIN daily_active_users d ON m.trade_date <span class="hljs-operator">=</span> d.trade_date AND m.market <span class="hljs-operator">=</span> d.market
LEFT JOIN eod_open_interest oi ON m.trade_date <span class="hljs-operator">=</span> oi.oi_date AND m.market <span class="hljs-operator">=</span> oi.market
LEFT JOIN daily_funding_rates f ON m.trade_date <span class="hljs-operator">=</span> f.rate_date AND m.market <span class="hljs-operator">=</span> f.market;

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> Create a temporary table <span class="hljs-keyword">from</span> the <span class="hljs-keyword">view</span> <span class="hljs-keyword">for</span> immediate dashboarding use
CREATE OR REPLACE TEMPORARY TABLE TMP_BLUEFIN_PRO_METRICS AS
SELECT <span class="hljs-operator">*</span> FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.BLUEFIN_PRO_DAILY_MARKET_METRICS_V;

</code></pre><br><h2 id="h-move-package-catalogue-tasks" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Move Package Catalogue Tasks</h2><h3 id="h-for-events" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">For Events</h3><pre data-type="codeBlock" text="create or replace task PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.POPULATE_EVENT_TYPE_SAMPLES_TASK
	warehouse=DEFI_WH
	schedule='USING CRON 0 * * * * America/New_York'
	COMMENT='Populates and updates the EVENT_TYPE_SAMPLES_TB with new unique event types and their sample data, avoiding full scans by only processing recent events and checking for existing event types.'
	as BEGIN
  CREATE TABLE IF NOT EXISTS PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_TYPE_SAMPLES_TB (
      EVENT_TYPE VARCHAR(16777216) PRIMARY KEY,
      PACKAGE VARCHAR(16777216) COMMENT 'The package ID part of the event type.',
      MODULE VARCHAR(16777216) COMMENT 'The module name part of the event type.',
      FIRST_APPEARANCE_TRANSACTION_DIGEST VARCHAR(16777216),
      SAMPLE_EVENT_JSON VARIANT,
      FIRST_APPEARANCE_TIMESTAMP_MS NUMBER(38,0),
      FIRST_APPEARANCE_UTC TIMESTAMP_LTZ(3),
      LAST_UPDATED_AT TIMESTAMP_NTZ(9) DEFAULT CURRENT_TIMESTAMP()
  )
  COMMENT='Auxiliary table storing a single sample transaction and event JSON for each unique event type. Populated by POPULATE_EVENT_TYPE_SAMPLES_TASK.';  INSERT INTO PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_TYPE_SAMPLES_TB (
      EVENT_TYPE,
      PACKAGE,
      MODULE,
      FIRST_APPEARANCE_TRANSACTION_DIGEST,
      SAMPLE_EVENT_JSON,
      FIRST_APPEARANCE_TIMESTAMP_MS,
      FIRST_APPEARANCE_UTC
  )
  SELECT
      e.event_type,
      SPLIT_PART(e.event_type, '::', 1) AS package,
      SPLIT_PART(e.event_type, '::', 2) AS module,
      ANY_VALUE(e.transaction_digest) AS first_appearance_transaction_digest,
      ANY_VALUE(e.event_json) AS sample_event_json,
      MIN(e.timestamp_ms) AS first_appearance_timestamp_ms,
      TO_TIMESTAMP_LTZ(MIN(e.timestamp_ms), 3) AS first_appearance_utc
  FROM
      event_parquet e
  WHERE
      e.timestamp_ms &gt; COALESCE(
          (SELECT MAX(first_appearance_timestamp_ms) FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_TYPE_SAMPLES_TB),
          0
      )
      AND NOT EXISTS (
          SELECT 1
          FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_TYPE_SAMPLES_TB ets
          WHERE ets.event_type = e.event_type
      )
  GROUP BY
      e.event_type;
END;
"><code>create or replace task PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.POPULATE_EVENT_TYPE_SAMPLES_TASK
	warehouse<span class="hljs-operator">=</span>DEFI_WH
	schedule<span class="hljs-operator">=</span><span class="hljs-string">'USING CRON 0 * * * * America/New_York'</span>
	COMMENT<span class="hljs-operator">=</span><span class="hljs-string">'Populates and updates the EVENT_TYPE_SAMPLES_TB with new unique event types and their sample data, avoiding full scans by only processing recent events and checking for existing event types.'</span>
	<span class="hljs-keyword">as</span> BEGIN
  CREATE TABLE IF NOT EXISTS PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_TYPE_SAMPLES_TB (
      EVENT_TYPE VARCHAR(<span class="hljs-number">16777216</span>) PRIMARY KEY,
      PACKAGE VARCHAR(<span class="hljs-number">16777216</span>) COMMENT <span class="hljs-string">'The package ID part of the event type.'</span>,
      MODULE VARCHAR(<span class="hljs-number">16777216</span>) COMMENT <span class="hljs-string">'The module name part of the event type.'</span>,
      FIRST_APPEARANCE_TRANSACTION_DIGEST VARCHAR(<span class="hljs-number">16777216</span>),
      SAMPLE_EVENT_JSON VARIANT,
      FIRST_APPEARANCE_TIMESTAMP_MS NUMBER(<span class="hljs-number">38</span>,<span class="hljs-number">0</span>),
      FIRST_APPEARANCE_UTC TIMESTAMP_LTZ(<span class="hljs-number">3</span>),
      LAST_UPDATED_AT TIMESTAMP_NTZ(<span class="hljs-number">9</span>) DEFAULT CURRENT_TIMESTAMP()
  )
  COMMENT<span class="hljs-operator">=</span><span class="hljs-string">'Auxiliary table storing a single sample transaction and event JSON for each unique event type. Populated by POPULATE_EVENT_TYPE_SAMPLES_TASK.'</span>;  INSERT INTO PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_TYPE_SAMPLES_TB (
      EVENT_TYPE,
      PACKAGE,
      MODULE,
      FIRST_APPEARANCE_TRANSACTION_DIGEST,
      SAMPLE_EVENT_JSON,
      FIRST_APPEARANCE_TIMESTAMP_MS,
      FIRST_APPEARANCE_UTC
  )
  SELECT
      e.event_type,
      SPLIT_PART(e.event_type, <span class="hljs-string">'::'</span>, <span class="hljs-number">1</span>) AS package,
      SPLIT_PART(e.event_type, <span class="hljs-string">'::'</span>, <span class="hljs-number">2</span>) AS module,
      ANY_VALUE(e.transaction_digest) AS first_appearance_transaction_digest,
      ANY_VALUE(e.event_json) AS sample_event_json,
      MIN(e.timestamp_ms) AS first_appearance_timestamp_ms,
      TO_TIMESTAMP_LTZ(MIN(e.timestamp_ms), <span class="hljs-number">3</span>) AS first_appearance_utc
  FROM
      event_parquet e
  WHERE
      e.timestamp_ms <span class="hljs-operator">&gt;</span> COALESCE(
          (SELECT MAX(first_appearance_timestamp_ms) FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_TYPE_SAMPLES_TB),
          <span class="hljs-number">0</span>
      )
      AND NOT EXISTS (
          SELECT <span class="hljs-number">1</span>
          FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_TYPE_SAMPLES_TB ets
          WHERE ets.event_type <span class="hljs-operator">=</span> e.event_type
      )
  GROUP BY
      e.event_type;
END;
</code></pre><h3 id="h-for-objects" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">For Objects</h3><pre data-type="codeBlock" text="create or replace task PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.POPULATE_OBJECT_TYPE_SAMPLES_TASK
	warehouse=DEFI_WH
	schedule='USING CRON 0 * * * * America/New_York'
	COMMENT='Populates and updates the OBJECT_TYPE_SAMPLES_TB with new unique object types and their sample data, avoiding full scans by only processing recently created objects and checking for existing object types.'
	as BEGIN
  -- Create the OBJECT_TYPE_SAMPLES_TB table if it does not already exist.
  -- This table stores a single sample object JSON and metadata for each unique object type encountered on-chain.
  CREATE TABLE IF NOT EXISTS PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.OBJECT_TYPE_SAMPLES_TB (
      OBJECT_TYPE VARCHAR(16777216) PRIMARY KEY COMMENT 'The full type string of the object.',
      PACKAGE VARCHAR(16777216) COMMENT 'The package ID part of the object type (e.g., &quot;0x2&quot;).',
      MODULE VARCHAR(16777216) COMMENT 'The module name part of the object type (e.g., &quot;coin&quot;).',
      TYPE VARCHAR(16777216) COMMENT 'The specific type name within the module (e.g., &quot;CoinMetadata&quot;).',
      FIRST_APPEARANCE_OBJECT_ID VARCHAR(16777216) COMMENT 'The object ID of the first created instance of this type.',
      SAMPLE_OBJECT_JSON VARIANT COMMENT 'A sample JSON representation of the object data at its first appearance.',
      FIRST_APPEARANCE_PREVIOUS_TRANSACTION VARCHAR(16777216) COMMENT 'The transaction digest of the transaction that created the first instance of this object type.',
      FIRST_APPEARANCE_TIMESTAMP_MS NUMBER(38,0) COMMENT 'The timestamp in milliseconds of the first creation event for this object type.',
      FIRST_APPEARANCE_UTC TIMESTAMP_LTZ(9) COMMENT 'The UTC timestamp of the first creation event for this object type, converted for easier readability.',
      LAST_UPDATED_AT TIMESTAMP_NTZ(9) DEFAULT CURRENT_TIMESTAMP() COMMENT 'Timestamp when this record was last updated or inserted.'
  )
  COMMENT='Auxiliary table storing a single sample object JSON and metadata for each unique object type. Populated by POPULATE_OBJECT_TYPE_SAMPLES_TASK.';

  -- Insert new unique object types into the samples table.
  -- This query is designed to be incremental, only processing objects created since the last successful run
  -- and adding only those object types that are not yet present in the `OBJECT_TYPE_SAMPLES_TB`.
  INSERT INTO PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.OBJECT_TYPE_SAMPLES_TB (
      object_type,
      package,
      module,
      type,
      first_appearance_object_id,
      sample_object_json,
      first_appearance_previous_transaction,
      first_appearance_timestamp_ms,
      first_appearance_utc
  )
  SELECT
      o.type AS object_type,
      -- Extract the package part from the object_type (e.g., '0x2::coin::CoinMetadata' -&gt; '0x2')
      SPLIT_PART(o.type, '::', 1) AS package,
      -- Extract the module part from the object_type (e.g., '0x2::coin::CoinMetadata' -&gt; 'coin')
      SPLIT_PART(o.type, '::', 2) AS module,
      -- Extract the specific type name part from the object_type (e.g., '0x2::coin::CoinMetadata' -&gt; 'CoinMetadata')
      REGEXP_SUBSTR(o.type, '^[^:]*::[^:]*::(.*)', 1, 1, 'e', 1) AS type,
      -- Select any object_id for the first appearance of this type (since we group by type)
      ANY_VALUE(o.object_id) AS first_appearance_object_id,
      -- Select a sample JSON for the first appearance of this type
      ANY_VALUE(o.object_json) AS sample_object_json,
      -- Select the transaction digest of the first transaction that created this object type
      ANY_VALUE(o.previous_transaction) AS first_appearance_previous_transaction,
      -- Find the minimum timestamp to identify the very first creation event for each object type
      MIN(o.timestamp_ms) AS first_appearance_timestamp_ms,
      -- Convert the minimum timestamp to a readable UTC format
      TO_TIMESTAMP_LTZ(MIN(o.timestamp_ms), 3) AS first_appearance_utc
  FROM
      PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.object_parquet2 o
  WHERE
      -- Filter for objects that were 'Created', as we are tracking their first appearance
      o.object_status = 'Created'
      -- Optimizes the scan by only looking at data newer than the latest entry in the samples table.
      -- COALESCE with 0 ensures it processes all data from the beginning on the very first run.
      AND o.timestamp_ms &gt; COALESCE(
          (SELECT MAX(first_appearance_timestamp_ms) FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.OBJECT_TYPE_SAMPLES_TB),
          0
      )
      -- Ensures that only truly new object types (not already in the samples table) are inserted.
      AND NOT EXISTS (
          SELECT 1
          FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.OBJECT_TYPE_SAMPLES_TB existing_ots
          WHERE existing_ots.object_type = o.type
      )
  GROUP BY
      o.type; -- Group by object type to ensure one entry per unique type, capturing its first observed creation.
END;

"><code>create or replace task PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.POPULATE_OBJECT_TYPE_SAMPLES_TASK
	warehouse<span class="hljs-operator">=</span>DEFI_WH
	schedule<span class="hljs-operator">=</span><span class="hljs-string">'USING CRON 0 * * * * America/New_York'</span>
	COMMENT<span class="hljs-operator">=</span><span class="hljs-string">'Populates and updates the OBJECT_TYPE_SAMPLES_TB with new unique object types and their sample data, avoiding full scans by only processing recently created objects and checking for existing object types.'</span>
	<span class="hljs-keyword">as</span> BEGIN
  <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Create the OBJECT_TYPE_SAMPLES_TB table <span class="hljs-keyword">if</span> it does not already exist.
  -<span class="hljs-operator">-</span> This table stores a single sample object JSON and metadata <span class="hljs-keyword">for</span> each unique object <span class="hljs-keyword">type</span> encountered on<span class="hljs-operator">-</span>chain.
  CREATE TABLE IF NOT EXISTS PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.OBJECT_TYPE_SAMPLES_TB (
      OBJECT_TYPE VARCHAR(<span class="hljs-number">16777216</span>) PRIMARY KEY COMMENT <span class="hljs-string">'The full type string of the object.'</span>,
      PACKAGE VARCHAR(<span class="hljs-number">16777216</span>) COMMENT <span class="hljs-string">'The package ID part of the object type (e.g., "0x2").'</span>,
      MODULE VARCHAR(<span class="hljs-number">16777216</span>) COMMENT <span class="hljs-string">'The module name part of the object type (e.g., "coin").'</span>,
      TYPE VARCHAR(<span class="hljs-number">16777216</span>) COMMENT <span class="hljs-string">'The specific type name within the module (e.g., "CoinMetadata").'</span>,
      FIRST_APPEARANCE_OBJECT_ID VARCHAR(<span class="hljs-number">16777216</span>) COMMENT <span class="hljs-string">'The object ID of the first created instance of this type.'</span>,
      SAMPLE_OBJECT_JSON VARIANT COMMENT <span class="hljs-string">'A sample JSON representation of the object data at its first appearance.'</span>,
      FIRST_APPEARANCE_PREVIOUS_TRANSACTION VARCHAR(<span class="hljs-number">16777216</span>) COMMENT <span class="hljs-string">'The transaction digest of the transaction that created the first instance of this object type.'</span>,
      FIRST_APPEARANCE_TIMESTAMP_MS NUMBER(<span class="hljs-number">38</span>,<span class="hljs-number">0</span>) COMMENT <span class="hljs-string">'The timestamp in milliseconds of the first creation event for this object type.'</span>,
      FIRST_APPEARANCE_UTC TIMESTAMP_LTZ(<span class="hljs-number">9</span>) COMMENT <span class="hljs-string">'The UTC timestamp of the first creation event for this object type, converted for easier readability.'</span>,
      LAST_UPDATED_AT TIMESTAMP_NTZ(<span class="hljs-number">9</span>) DEFAULT CURRENT_TIMESTAMP() COMMENT <span class="hljs-string">'Timestamp when this record was last updated or inserted.'</span>
  )
  COMMENT<span class="hljs-operator">=</span><span class="hljs-string">'Auxiliary table storing a single sample object JSON and metadata for each unique object type. Populated by POPULATE_OBJECT_TYPE_SAMPLES_TASK.'</span>;

  <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Insert <span class="hljs-keyword">new</span> unique object types into the samples table.
  -<span class="hljs-operator">-</span> This query <span class="hljs-keyword">is</span> designed to be incremental, only processing objects created since the last successful run
  <span class="hljs-operator">-</span><span class="hljs-operator">-</span> and adding only those object types that are not yet present in the `OBJECT_TYPE_SAMPLES_TB`.
  INSERT INTO PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.OBJECT_TYPE_SAMPLES_TB (
      object_type,
      package,
      module,
      <span class="hljs-keyword">type</span>,
      first_appearance_object_id,
      sample_object_json,
      first_appearance_previous_transaction,
      first_appearance_timestamp_ms,
      first_appearance_utc
  )
  SELECT
      o.type AS object_type,
      <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Extract the package part <span class="hljs-keyword">from</span> the object_type (e.g., <span class="hljs-string">'0x2::coin::CoinMetadata'</span> <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-string">'0x2'</span>)
      SPLIT_PART(o.type, <span class="hljs-string">'::'</span>, <span class="hljs-number">1</span>) AS package,
      <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Extract the module part <span class="hljs-keyword">from</span> the object_type (e.g., <span class="hljs-string">'0x2::coin::CoinMetadata'</span> <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-string">'coin'</span>)
      SPLIT_PART(o.type, <span class="hljs-string">'::'</span>, <span class="hljs-number">2</span>) AS module,
      <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Extract the specific <span class="hljs-keyword">type</span> name part <span class="hljs-keyword">from</span> the object_type (e.g., <span class="hljs-string">'0x2::coin::CoinMetadata'</span> <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-string">'CoinMetadata'</span>)
      REGEXP_SUBSTR(o.type, <span class="hljs-string">'^[^:]*::[^:]*::(.*)'</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-string">'e'</span>, <span class="hljs-number">1</span>) AS <span class="hljs-keyword">type</span>,
      <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Select any object_id <span class="hljs-keyword">for</span> the first appearance of <span class="hljs-built_in">this</span> <span class="hljs-keyword">type</span> (since we group by <span class="hljs-keyword">type</span>)
      ANY_VALUE(o.object_id) AS first_appearance_object_id,
      <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Select a sample JSON <span class="hljs-keyword">for</span> the first appearance of <span class="hljs-built_in">this</span> <span class="hljs-keyword">type</span>
      ANY_VALUE(o.object_json) AS sample_object_json,
      <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Select the transaction digest of the first transaction that created <span class="hljs-built_in">this</span> object <span class="hljs-keyword">type</span>
      ANY_VALUE(o.previous_transaction) AS first_appearance_previous_transaction,
      <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Find the minimum timestamp to identify the very first creation <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">each</span> <span class="hljs-title">object</span> <span class="hljs-title"><span class="hljs-keyword">type</span></span>
      <span class="hljs-title">MIN</span>(<span class="hljs-params">o.timestamp_ms</span>) <span class="hljs-title">AS</span> <span class="hljs-title">first_appearance_timestamp_ms</span>,
      -- <span class="hljs-title">Convert</span> <span class="hljs-title">the</span> <span class="hljs-title">minimum</span> <span class="hljs-title">timestamp</span> <span class="hljs-title">to</span> <span class="hljs-title">a</span> <span class="hljs-title">readable</span> <span class="hljs-title">UTC</span> <span class="hljs-title">format</span>
      <span class="hljs-title">TO_TIMESTAMP_LTZ</span>(<span class="hljs-params">MIN(<span class="hljs-params">o.timestamp_ms</span>), <span class="hljs-number">3</span></span>) <span class="hljs-title">AS</span> <span class="hljs-title">first_appearance_utc</span>
  <span class="hljs-title">FROM</span>
      <span class="hljs-title">PIPELINE_V2_GROOT_DB</span>.<span class="hljs-title">PIPELINE_V2_GROOT_SCHEMA</span>.<span class="hljs-title">object_parquet2</span> <span class="hljs-title">o</span>
  <span class="hljs-title">WHERE</span>
      -- <span class="hljs-title">Filter</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">objects</span> <span class="hljs-title">that</span> <span class="hljs-title">were</span> '<span class="hljs-title">Created</span>', <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">we</span> <span class="hljs-title">are</span> <span class="hljs-title">tracking</span> <span class="hljs-title">their</span> <span class="hljs-title">first</span> <span class="hljs-title">appearance</span>
      <span class="hljs-title">o</span>.<span class="hljs-title">object_status</span> = '<span class="hljs-title">Created</span>'
      -- <span class="hljs-title">Optimizes</span> <span class="hljs-title">the</span> <span class="hljs-title">scan</span> <span class="hljs-title">by</span> <span class="hljs-title">only</span> <span class="hljs-title">looking</span> <span class="hljs-title">at</span> <span class="hljs-title">data</span> <span class="hljs-title">newer</span> <span class="hljs-title">than</span> <span class="hljs-title">the</span> <span class="hljs-title">latest</span> <span class="hljs-title">entry</span> <span class="hljs-title">in</span> <span class="hljs-title">the</span> <span class="hljs-title">samples</span> <span class="hljs-title">table</span>.
      -- <span class="hljs-title">COALESCE</span> <span class="hljs-title">with</span> 0 <span class="hljs-title">ensures</span> <span class="hljs-title">it</span> <span class="hljs-title">processes</span> <span class="hljs-title">all</span> <span class="hljs-title">data</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-title">the</span> <span class="hljs-title">beginning</span> <span class="hljs-title">on</span> <span class="hljs-title">the</span> <span class="hljs-title">very</span> <span class="hljs-title">first</span> <span class="hljs-title">run</span>.
      <span class="hljs-title">AND</span> <span class="hljs-title">o</span>.<span class="hljs-title">timestamp_ms</span> &gt; <span class="hljs-title">COALESCE</span>(<span class="hljs-params">
          (<span class="hljs-params">SELECT MAX(<span class="hljs-params">first_appearance_timestamp_ms</span>) FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.OBJECT_TYPE_SAMPLES_TB</span>),
          <span class="hljs-number">0</span>
      </span>)
      -- <span class="hljs-title">Ensures</span> <span class="hljs-title">that</span> <span class="hljs-title">only</span> <span class="hljs-title">truly</span> <span class="hljs-title"><span class="hljs-keyword">new</span></span> <span class="hljs-title">object</span> <span class="hljs-title">types</span> (<span class="hljs-params">not already in the samples table</span>) <span class="hljs-title">are</span> <span class="hljs-title">inserted</span>.
      <span class="hljs-title">AND</span> <span class="hljs-title">NOT</span> <span class="hljs-title">EXISTS</span> (<span class="hljs-params">
          SELECT <span class="hljs-number">1</span>
          FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.OBJECT_TYPE_SAMPLES_TB existing_ots
          WHERE existing_ots.object_type = o.<span class="hljs-keyword">type</span>
      </span>)
  <span class="hljs-title">GROUP</span> <span class="hljs-title">BY</span>
      <span class="hljs-title">o</span>.<span class="hljs-title"><span class="hljs-keyword">type</span></span></span>; <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Group by object <span class="hljs-keyword">type</span> to ensure one entry per unique <span class="hljs-keyword">type</span>, capturing its first observed creation.
END;

</code></pre><h3 id="h-snowflake-table-data-catalogue" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Snowflake Table Data Catalogue</h3><pre data-type="codeBlock" text="create or replace view PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.DATA_CATALOGUE_VIEW(
	TABLE_SCHEMA,
	TABLE_NAME,
	TABLE_TYPE,
	DESCRIPTION,
	COLUMN_METADATA_JSON,
	COLUMN_COMMENTS,
	LAST_ALTERED
) COMMENT='Provides a comprehensive catalogue of tables and views within the PIPELINE_V2_GROOT_SCHEMA, including table-level descriptions and detailed metadata for each column (name, type, and comment).'
 as
SELECT
    t.TABLE_SCHEMA AS TABLE_SCHEMA,       -- Schema name
    t.TABLE_NAME AS TABLE_NAME,           -- Table name
    t.TABLE_TYPE AS TABLE_TYPE,           -- Type: BASE TABLE, VIEW, etc.
    t.COMMENT AS DESCRIPTION,             -- Table-level description
    OBJECT_AGG(c.COLUMN_NAME, c.DATA_TYPE::VARIANT) AS COLUMN_METADATA_JSON,       -- Column names and types as a JSON object (cast DATA_TYPE to VARIANT)
    ARRAY_AGG(c.COMMENT) WITHIN GROUP (ORDER BY c.ORDINAL_POSITION) AS COLUMN_COMMENTS,        -- Column-level comments as an array
    t.LAST_ALTERED AS LAST_ALTERED        -- Last altered time
FROM INFORMATION_SCHEMA.TABLES t
LEFT JOIN INFORMATION_SCHEMA.COLUMNS c
    ON t.TABLE_CATALOG = c.TABLE_CATALOG
    AND t.TABLE_SCHEMA = c.TABLE_SCHEMA
    AND t.TABLE_NAME = c.TABLE_NAME
WHERE t.TABLE_SCHEMA = 'PIPELINE_V2_GROOT_SCHEMA'
GROUP BY t.TABLE_SCHEMA, t.TABLE_NAME, t.TABLE_TYPE, t.COMMENT, t.LAST_ALTERED;
"><code>create or replace <span class="hljs-keyword">view</span> PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.DATA_CATALOGUE_VIEW(
	TABLE_SCHEMA,
	TABLE_NAME,
	TABLE_TYPE,
	DESCRIPTION,
	COLUMN_METADATA_JSON,
	COLUMN_COMMENTS,
	LAST_ALTERED
) COMMENT<span class="hljs-operator">=</span><span class="hljs-string">'Provides a comprehensive catalogue of tables and views within the PIPELINE_V2_GROOT_SCHEMA, including table-level descriptions and detailed metadata for each column (name, type, and comment).'</span>
 <span class="hljs-keyword">as</span>
SELECT
    t.TABLE_SCHEMA AS TABLE_SCHEMA,       <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Schema name
    t.TABLE_NAME AS TABLE_NAME,           <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Table name
    t.TABLE_TYPE AS TABLE_TYPE,           <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Type: BASE TABLE, VIEW, etc.
    t.COMMENT AS DESCRIPTION,             <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Table<span class="hljs-operator">-</span>level description
    OBJECT_AGG(c.COLUMN_NAME, c.DATA_TYPE::VARIANT) AS COLUMN_METADATA_JSON,       <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Column names and types <span class="hljs-keyword">as</span> a JSON object (cast DATA_TYPE to VARIANT)
    ARRAY_AGG(c.COMMENT) WITHIN GROUP (ORDER BY c.ORDINAL_POSITION) AS COLUMN_COMMENTS,        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Column<span class="hljs-operator">-</span>level comments <span class="hljs-keyword">as</span> an array
    t.LAST_ALTERED AS LAST_ALTERED        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Last altered time
FROM INFORMATION_SCHEMA.TABLES t
LEFT JOIN INFORMATION_SCHEMA.COLUMNS c
    ON t.TABLE_CATALOG <span class="hljs-operator">=</span> c.TABLE_CATALOG
    AND t.TABLE_SCHEMA <span class="hljs-operator">=</span> c.TABLE_SCHEMA
    AND t.TABLE_NAME <span class="hljs-operator">=</span> c.TABLE_NAME
WHERE t.TABLE_SCHEMA <span class="hljs-operator">=</span> <span class="hljs-string">'PIPELINE_V2_GROOT_SCHEMA'</span>
GROUP BY t.TABLE_SCHEMA, t.TABLE_NAME, t.TABLE_TYPE, t.COMMENT, t.LAST_ALTERED;
</code></pre><h3 id="h-bluefin-daily-market-view-output" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Bluefin Daily Market View Output:</h3><pre data-type="codeBlock" text="create or replace view PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.BLUEFIN_PRO_DAILY_MARKET_METRICS_V(
	TRADE_DATE,
	MARKET,
	DAILY_VOLUME_USD,
	DAILY_AVERAGE_PRICE,
	DAILY_ACTIVE_USERS,
	EOD_OPEN_INTEREST_NATIVE,
	EOD_OPEN_INTEREST_USD,
	AVG_DAILY_FUNDING_RATE
) COMMENT='Provides daily aggregated metrics for Bluefin Pro perpetual markets. It includes daily trading volume (USD), volume-weighted average price (USD), daily active users (DAU), end-of-day (EOD) open interest in both native token and USD, and the average daily funding rate. Data is sourced from Bluefin Pro package events (0xe744...). NOTE: A manual decimal correction (+1) is applied to BTC-PERP, ETH-PERP, and SOL-PERP markets to correct an observed 10x scaling discrepancy in the raw on-chain data for accurate calculations.'
 as
WITH
-- ============================================================================
-- STEP 1: Get the latest on-chain metadata for each market (e.g., decimals)
-- ============================================================================
market_metadata AS (
    SELECT
        event_json:perpetual:symbol::string as market,
        -- Derive decimal count from on-chain value (e.g., 8000000000 -&gt; 8)
        event_json:perpetual:base_asset_decimals::number / 1e9 as base_asset_decimals
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET
    WHERE event_type IN (
        '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::PerpetualUpdate',
        '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::PerpetualSynced'
    )
    AND market IS NOT NULL
    AND base_asset_decimals IS NOT NULL
    QUALIFY ROW_NUMBER() OVER (PARTITION BY market ORDER BY timestamp_ms DESC) = 1
),

-- ============================================================================
-- STEP 2: Calculate daily trading volume, average price, and active users
-- ============================================================================
trade_details AS (
    SELECT
        t.TIMESTAMP_MS,
        t.EVENT_JSON:market::string as market,
        t.EVENT_JSON:fill_price::number as fill_price,
        t.EVENT_JSON:fill_quantity::number as fill_quantity,
        t.EVENT_JSON:maker:address::string as maker_address,
        t.EVENT_JSON:taker:address::string as taker_address,
        -- MANUAL CORRECTION: Add 1 to decimals for specific markets due to
        -- an observed 10x discrepancy in their `fill_quantity` scaling.
        CASE
            WHEN m.market IN ('BTC-PERP', 'ETH-PERP', 'SOL-PERP') THEN m.base_asset_decimals + 1
            ELSE m.base_asset_decimals
        END as base_asset_decimals
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET t
    JOIN market_metadata m ON t.EVENT_JSON:market::string = m.market
    WHERE t.EVENT_TYPE = '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::TradeExecuted'
),

trade_participants AS (
    SELECT TIMESTAMP_MS, market, maker_address as user_address FROM trade_details
    UNION
    SELECT TIMESTAMP_MS, market, taker_address as user_address FROM trade_details
),

daily_active_users AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms/1000)) as trade_date,
        market,
        COUNT(DISTINCT user_address) as dau
    FROM trade_participants
    GROUP BY 1,2
),

daily_trade_metrics AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms / 1000)) as trade_date,
        market,
        SUM((fill_price / 1e9) * (fill_quantity / POW(10, base_asset_decimals))) as daily_volume_usd,
        SUM(fill_price * fill_quantity) / SUM(fill_quantity) / 1e9 as daily_average_price
    FROM trade_details
    GROUP BY 1, 2
),

-- ============================================================================
-- STEP 3: Calculate average daily funding rate from 'FundingRateUpdated' events
-- ============================================================================
daily_funding_rates AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms / 1000)) as rate_date,
        event_json:market::string as market,
        AVG((CASE WHEN event_json:rate:sign::boolean THEN 1 ELSE -1 END * event_json:rate:value::number) / 1e9) as avg_daily_funding_rate
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET
    WHERE event_type = '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::FundingRateUpdated'
    GROUP BY 1, 2
),

-- ============================================================================
-- STEP 4: Get End-of-Day Open Interest Snapshot
-- ============================================================================
open_interest_snapshots AS (
    SELECT
        t.timestamp_ms,
        t.event_json:position:perpetual::string as market,
        -- MANUAL CORRECTION: Apply the same decimal adjustment here for `position:size`
        SUM(t.event_json:position:size::number / POW(10,
            CASE
                WHEN m.market IN ('BTC-PERP', 'ETH-PERP', 'SOL-PERP') THEN m.base_asset_decimals + 1
                ELSE m.base_asset_decimals
            END
        )) as oi_snapshot_native
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET t
    JOIN market_metadata m ON t.event_json:position:perpetual::string = m.market
    WHERE t.event_type = '0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::FundingRateApplied'
      AND t.event_json:position:is_long::boolean = true
    GROUP BY 1, 2
),

eod_open_interest AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms/1000)) as oi_date,
        market,
        oi_snapshot_native as eod_open_interest_native
    FROM open_interest_snapshots
    QUALIFY ROW_NUMBER() OVER (PARTITION BY oi_date, market ORDER BY timestamp_ms DESC) = 1
)

-- ============================================================================
-- FINAL STEP: Join all the daily metrics together
-- ============================================================================
SELECT
    m.trade_date,
    m.market,
    m.daily_volume_usd,
    m.daily_average_price,
    d.dau AS daily_active_users,
    oi.eod_open_interest_native,
    oi.eod_open_interest_native * m.daily_average_price as eod_open_interest_usd,
    f.avg_daily_funding_rate
FROM daily_trade_metrics m
LEFT JOIN daily_active_users d ON m.trade_date = d.trade_date AND m.market = d.market
LEFT JOIN eod_open_interest oi ON m.trade_date = oi.oi_date AND m.market = oi.market
LEFT JOIN daily_funding_rates f ON m.trade_date = f.rate_date AND m.market = f.market;"><code>create or replace <span class="hljs-keyword">view</span> PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.BLUEFIN_PRO_DAILY_MARKET_METRICS_V(
	TRADE_DATE,
	MARKET,
	DAILY_VOLUME_USD,
	DAILY_AVERAGE_PRICE,
	DAILY_ACTIVE_USERS,
	EOD_OPEN_INTEREST_NATIVE,
	EOD_OPEN_INTEREST_USD,
	AVG_DAILY_FUNDING_RATE
) COMMENT<span class="hljs-operator">=</span><span class="hljs-string">'Provides daily aggregated metrics for Bluefin Pro perpetual markets. It includes daily trading volume (USD), volume-weighted average price (USD), daily active users (DAU), end-of-day (EOD) open interest in both native token and USD, and the average daily funding rate. Data is sourced from Bluefin Pro package events (0xe744...). NOTE: A manual decimal correction (+1) is applied to BTC-PERP, ETH-PERP, and SOL-PERP markets to correct an observed 10x scaling discrepancy in the raw on-chain data for accurate calculations.'</span>
 <span class="hljs-keyword">as</span>
WITH
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> STEP <span class="hljs-number">1</span>: Get the latest on<span class="hljs-operator">-</span>chain metadata <span class="hljs-keyword">for</span> each market (e.g., decimals)
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
market_metadata AS (
    SELECT
        event_json:perpetual:symbol::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> market,
        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Derive decimal count <span class="hljs-keyword">from</span> on<span class="hljs-operator">-</span>chain value (e.g., <span class="hljs-number">8000000000</span> <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-number">8</span>)
        event_json:perpetual:base_asset_decimals::number <span class="hljs-operator">/</span> <span class="hljs-number">1e9</span> <span class="hljs-keyword">as</span> base_asset_decimals
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET
    WHERE event_type IN (
        <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::PerpetualUpdate'</span>,
        <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::PerpetualSynced'</span>
    )
    AND market IS NOT NULL
    AND base_asset_decimals IS NOT NULL
    QUALIFY ROW_NUMBER() OVER (PARTITION BY market ORDER BY timestamp_ms DESC) <span class="hljs-operator">=</span> <span class="hljs-number">1</span>
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> STEP <span class="hljs-number">2</span>: Calculate daily trading volume, average price, and active users
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
trade_details AS (
    SELECT
        t.TIMESTAMP_MS,
        t.EVENT_JSON:market::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> market,
        t.EVENT_JSON:fill_price::number <span class="hljs-keyword">as</span> fill_price,
        t.EVENT_JSON:fill_quantity::number <span class="hljs-keyword">as</span> fill_quantity,
        t.EVENT_JSON:maker:<span class="hljs-keyword">address</span>::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> maker_address,
        t.EVENT_JSON:taker:<span class="hljs-keyword">address</span>::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> taker_address,
        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> MANUAL CORRECTION: Add <span class="hljs-number">1</span> to decimals <span class="hljs-keyword">for</span> specific markets due to
        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> an observed 10x discrepancy in their `fill_quantity` scaling.
        CASE
            WHEN m.market IN (<span class="hljs-string">'BTC-PERP'</span>, <span class="hljs-string">'ETH-PERP'</span>, <span class="hljs-string">'SOL-PERP'</span>) THEN m.base_asset_decimals <span class="hljs-operator">+</span> <span class="hljs-number">1</span>
            ELSE m.base_asset_decimals
        END <span class="hljs-keyword">as</span> base_asset_decimals
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET t
    JOIN market_metadata m ON t.EVENT_JSON:market::<span class="hljs-keyword">string</span> <span class="hljs-operator">=</span> m.market
    WHERE t.EVENT_TYPE <span class="hljs-operator">=</span> <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::TradeExecuted'</span>
),

trade_participants AS (
    SELECT TIMESTAMP_MS, market, maker_address <span class="hljs-keyword">as</span> user_address FROM trade_details
    UNION
    SELECT TIMESTAMP_MS, market, taker_address <span class="hljs-keyword">as</span> user_address FROM trade_details
),

daily_active_users AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms<span class="hljs-operator">/</span><span class="hljs-number">1000</span>)) <span class="hljs-keyword">as</span> trade_date,
        market,
        COUNT(DISTINCT user_address) <span class="hljs-keyword">as</span> dau
    FROM trade_participants
    GROUP BY <span class="hljs-number">1</span>,<span class="hljs-number">2</span>
),

daily_trade_metrics AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>)) <span class="hljs-keyword">as</span> trade_date,
        market,
        SUM((fill_price <span class="hljs-operator">/</span> <span class="hljs-number">1e9</span>) <span class="hljs-operator">*</span> (fill_quantity <span class="hljs-operator">/</span> POW(<span class="hljs-number">10</span>, base_asset_decimals))) <span class="hljs-keyword">as</span> daily_volume_usd,
        SUM(fill_price <span class="hljs-operator">*</span> fill_quantity) <span class="hljs-operator">/</span> SUM(fill_quantity) <span class="hljs-operator">/</span> <span class="hljs-number">1e9</span> <span class="hljs-keyword">as</span> daily_average_price
    FROM trade_details
    GROUP BY <span class="hljs-number">1</span>, <span class="hljs-number">2</span>
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> STEP <span class="hljs-number">3</span>: Calculate average daily funding rate <span class="hljs-keyword">from</span> <span class="hljs-string">'FundingRateUpdated'</span> events
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
daily_funding_rates AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>)) <span class="hljs-keyword">as</span> rate_date,
        event_json:market::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> market,
        AVG((CASE WHEN event_json:rate:sign::boolean THEN <span class="hljs-number">1</span> ELSE <span class="hljs-number">-1</span> END <span class="hljs-operator">*</span> event_json:rate:<span class="hljs-built_in">value</span>::number) <span class="hljs-operator">/</span> <span class="hljs-number">1e9</span>) <span class="hljs-keyword">as</span> avg_daily_funding_rate
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET
    WHERE event_type <span class="hljs-operator">=</span> <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::FundingRateUpdated'</span>
    GROUP BY <span class="hljs-number">1</span>, <span class="hljs-number">2</span>
),

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> STEP <span class="hljs-number">4</span>: Get End<span class="hljs-operator">-</span>of<span class="hljs-operator">-</span>Day Open Interest Snapshot
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
open_interest_snapshots AS (
    SELECT
        t.timestamp_ms,
        t.event_json:position:perpetual::<span class="hljs-keyword">string</span> <span class="hljs-keyword">as</span> market,
        <span class="hljs-operator">-</span><span class="hljs-operator">-</span> MANUAL CORRECTION: Apply the same decimal adjustment here <span class="hljs-keyword">for</span> `position:size`
        SUM(t.event_json:position:size::number <span class="hljs-operator">/</span> POW(<span class="hljs-number">10</span>,
            CASE
                WHEN m.market IN (<span class="hljs-string">'BTC-PERP'</span>, <span class="hljs-string">'ETH-PERP'</span>, <span class="hljs-string">'SOL-PERP'</span>) THEN m.base_asset_decimals <span class="hljs-operator">+</span> <span class="hljs-number">1</span>
                ELSE m.base_asset_decimals
            END
        )) <span class="hljs-keyword">as</span> oi_snapshot_native
    FROM PIPELINE_V2_GROOT_DB.PIPELINE_V2_GROOT_SCHEMA.EVENT_PARQUET t
    JOIN market_metadata m ON t.event_json:position:perpetual::<span class="hljs-keyword">string</span> <span class="hljs-operator">=</span> m.market
    WHERE t.event_type <span class="hljs-operator">=</span> <span class="hljs-string">'0xe74481697f432ddee8dd6f9bd13b9d0297a5b63d55f3db25c4d3b5d34dad85b7::events::FundingRateApplied'</span>
      AND t.event_json:position:is_long::boolean <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>
    GROUP BY <span class="hljs-number">1</span>, <span class="hljs-number">2</span>
),

eod_open_interest AS (
    SELECT
        DATE(TO_TIMESTAMP(timestamp_ms<span class="hljs-operator">/</span><span class="hljs-number">1000</span>)) <span class="hljs-keyword">as</span> oi_date,
        market,
        oi_snapshot_native <span class="hljs-keyword">as</span> eod_open_interest_native
    FROM open_interest_snapshots
    QUALIFY ROW_NUMBER() OVER (PARTITION BY oi_date, market ORDER BY timestamp_ms DESC) <span class="hljs-operator">=</span> <span class="hljs-number">1</span>
)

<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> FINAL STEP: Join all the daily metrics together
<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
SELECT
    m.trade_date,
    m.market,
    m.daily_volume_usd,
    m.daily_average_price,
    d.dau AS daily_active_users,
    oi.eod_open_interest_native,
    oi.eod_open_interest_native <span class="hljs-operator">*</span> m.daily_average_price <span class="hljs-keyword">as</span> eod_open_interest_usd,
    f.avg_daily_funding_rate
FROM daily_trade_metrics m
LEFT JOIN daily_active_users d ON m.trade_date <span class="hljs-operator">=</span> d.trade_date AND m.market <span class="hljs-operator">=</span> d.market
LEFT JOIN eod_open_interest oi ON m.trade_date <span class="hljs-operator">=</span> oi.oi_date AND m.market <span class="hljs-operator">=</span> oi.market
LEFT JOIN daily_funding_rates f ON m.trade_date <span class="hljs-operator">=</span> f.rate_date AND m.market <span class="hljs-operator">=</span> f.market;</code></pre><br>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>llm</category>
            <category>ai</category>
            <category>data</category>
            <category>crypto</category>
            <category>web3</category>
            <category>defi</category>
            <category>sui</category>
            <category>move</category>
            <category>perp</category>
            <category>trading</category>
            <category>analytics</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/c1a2246f85f4f44bd42f1b293a64d828.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Sui NFT Collection Analytics]]></title>
            <link>https://paragraph.com/@evandekim/sui-nft-collection-analytics</link>
            <guid>v3LVdJJ0cuzm3lVrWmKN</guid>
            <pubDate>Sun, 16 Feb 2025 15:52:47 GMT</pubDate>
            <description><![CDATA[IntroThis article presents a framework for building data pipelines on the Sui blockchain using Sentio, illustrated through a complete implementation to analyze Rootlets NFT holders. Sentio is used for efficient data backfilling, custom processing with TypeScript, and real-time analysis via its SQL Studio. The article culminates in demonstrating a user-friendly Sentio dashboard that queries Rootlet ownership by wallet address. This approach provides a reusable and adaptable foundation for deve...]]></description>
            <content:encoded><![CDATA[<div class="relative header-and-anchor"><h1 id="h-intro">Intro</h1></div><p>This article presents a framework for building data pipelines on the Sui blockchain using Sentio, illustrated through a complete implementation to analyze Rootlets NFT holders. Sentio is used for efficient data backfilling, custom processing with TypeScript, and real-time analysis via its SQL Studio. The article culminates in demonstrating a user-friendly Sentio <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.sentio.xyz/share/gzpkjey8llpnam4y?from=now-30d&amp;to=now"><u>dashboard</u></a> that queries Rootlet ownership by wallet address. This approach provides a reusable and adaptable foundation for developing data-driven applications and insights across the Sui ecosystem.</p><div class="relative header-and-anchor"><h1 id="h-sentio-processor-pipeline">Sentio Processor Pipeline</h1></div><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sentio.xyz/docs/processor-basic"><u>Sentio processors</u></a>, written in TypeScript, provide a flexible way to construct Sui data pipelines. They manage the essential ETL (Extract, Transform, Load) steps for interacting with Sui blockchain data.&nbsp;</p><p>Processors define how data is extracted, transformed (including handling Sui-specific features like object ownership), and loaded into Sentio's database. They serve as the central logic for data processing, ensuring only relevant and correctly structured data is stored for analysis. The complete processor code for this article is available <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/rootlets"><u>here</u></a>.</p><div class="relative header-and-anchor"><h2 id="h-defining-entities">Defining Entities</h2></div><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sentio.xyz/docs/entities"><u>Entities</u></a> in Sentio are analogous to custom tables, structuring data for optimized analysis. Entities are defined in a store.graphql file, located at src/schema/store.graphql. This file uses a GraphQL-like syntax to describe the structure of our custom tables.</p><p>For the Rootlets project, there are two main entities:</p><ul><li><p><strong>RootletStaticFields</strong>: This entity captures the static fields of a Rootlet object, such as its description, image URL, and inherent traits (theme, accessories, etc.). This is information that doesn't change over time.</p></li><li><p><strong>RootletOwner</strong>: This entity tracks changes in Rootlet ownership. Every time a Rootlet is transferred or modified, a new entry is created in this entity, recording the sender, transaction digest, and timestamp. This is crucial for tracking who owns which Rootlets over time.</p></li></ul><div class="relative header-and-anchor"><h2 id="h-generating-bindings">Generating Bindings</h2></div><p>After defining entities in src/schema/store.graphql, the command <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sentio.xyz/docs/cli-reference#sentio-build"><u>yarn sentio build</u></a> generates TypeScript code. This generated code facilitates interaction with these entities within the processor, enabling the creation and updating of records during data processing.</p><div class="relative header-and-anchor"><h1 id="h-sql-query-analytics">SQL Query Analytics</h1></div><p>Sentio provides a built-in SQL Studio, a web-based interface where you can write and execute SQL queries against your data. As the backfilling process populates your entities (which now appear as tables), you can start writing SQL queries to analyze the data in real-time.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/61e7c8d2e76fb5178d1d7fad21df732e.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgCAIAAACU62+bAAAACXBIWXMAAAsTAAALEwEAmpwYAAADvElEQVR4nH2UwW/URhTGfTEUoQFUC8lNFiJMmxwsEavJROBVOhHyWplImAqMsluyPjBpslZr02YtyggRo+Kg4jZaH2rasof4UKVSiVTVqJx6gSC1lUoOFTf6FxTUAz0W0Wo9G683qvpOtjw/v/d9/p45AECa3n358p+1tbXfHj8Ow0+fPftzfX1d2K49u/fu2b33wP4D7JYTRVGSJELI1eVlx3Fmq1XHcQghx0ZH2QlZGVE1OHS0dOjQYVEUOwDHcb8+evTH06e3bn2Zpne/Xv/mu+9T0zQ5jhME4dwCjjb8kTFp4LXBbgdBEF7ZtQsAoGma7/ue59m2TSm1bVsUxX379nMch9DUBUJkWe4CpVKpXC6HYRhFked57nbJsiwIAgAAY3zx4gfHT5zoAoIgYIw3Nzd93wcAcNvFHoEM8DwPwokeYGTFrsWscqNABiw1m30AxjOqqnZskWUAQH5ayABN0y4QoihKDhzUNC1N03a7nSTJ1tZWHMc5BjodZii90tMgiqKmaWEYUkoJIcyGHR0cx+mNxIA4joMgsG2b5/kdI2GMHfdDCGEPQAj5vh9FEXOpCPA8n7l0SVXVnuiZU6eCIKCURlGkaVpRgCRJs9V3lprNomhBVVVCiGVZruuqqsrzPMhKEF4dGBzAGBNChoeHOwDP80+e/P73ixff3rlz/8HD+w8e/vTzLz/cu/f8+V+z1eqZd/W1Hz97480jQ4eHulkCAEA4Uanok29NEkLm5xcsyzJN0zAMSTpaOjI4Ir8+cXxCUZRuWtlIkiSFYZimKfMqSZJ2u10qDbKM6Lq+snIDIdSXJUqp53mU0iAIeJ7nOG6x0VhttVZbLcdxFhuNnku5rb7vU0oNw2AfTlEUhKYQQqZ5znGcsfHxvg5smDAMt/3peMpnrfTp6aVmsxjvg5WKHmSVJAlLBwNAxujT0553qS8aCCHXdWu1mmEYEEIGKIoC4QSE8PTpt+fq9V40WFqTJOnI4rh8nrl6fWXlxsfXrzfs9z66fHlnWuM4JoQUQzFXr19dvub7vm3b/5FWy7LYDuWlKMrUyZMIodlq1XXd4kiCaZqdbHFcMdu5S5WK3ueSKIqVih6G4cbGRhAE+Vv+z6VyuWzbdhiGxamOjY5CCMfGx8+cPWtZVp9LrIPneUXRi43GV7dvfx5/Ydvv9y0QE+15XhzHGONcBkseAKBWO+/713oLlH24KRa+4m8GQlienKxU9Frt/Pz8QneB2EgY43a7TSktKiaErLZan9y8udRsUnoFIfQvSx53VFavn4kAAAAASUVORK5CYII=" nextheight="556" nextwidth="271" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><div class="relative header-and-anchor"><h2 id="h-rootlet-holder-count-growth">Rootlet Holder Count Growth</h2></div><p>A unique holder count growth over time can be constructed using the RootletOwner table. This query is crucial for taking arbitrary historical snapshots of Rootlet ownership.</p><p>Sui NFT objects are stored either in kiosks or as wrapped objects, resulting in the true owner often being a nested object value. Instead of recursive searching for the true owner, the sender address is used. Sui's consensus mechanism performs a runtime validation check, ensuring that the object mutator is already the object's owner. Therefore, if the object is mutated, it is assumed that the mutation was performed by the owner. This principle allows the storage of ownership history in a table, tracking the transaction sender each time the object is mutated.</p><p>Here is what the chart looks like in the Sentio dashboard:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6a52108480404c0269a7ae083d65d251.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAHCAIAAADmsdgtAAAACXBIWXMAAAsTAAALEwEAmpwYAAABo0lEQVR4nGNQUlK2sLDQ0zN09HLQ1NMUEhITExMTAgNBQX4BPl444ufjREWs/FzMYMQIQbysDGiIg4GBgY+Pn5uTmQEMOBhAovx8nGJi4uISUtKKatKK6rLKmrLKmtKK6vJq+nIqOnIqOoqaxkqaJhrGzqZu8aZucWaeaRY+mdYBBbbBZQ5hNc7RTd6pE/0yZ0ZVrrPwLWQQFhLUNHb0TJkQlDc/omxtTNWWoml3Gpd+aVvzb9re/7MP/J935P/cIyDGktP/F534v/jE/1UX/m+4/n/T9f/b7yHQjnv/d92Hot0PQOTZz/8Lp9xhkJCQaF/z+9ALkOi+J/8PPP+/9zFIese9/9vu/d90A2TQNjB7I9jQTddBjI3X/6+78n8tbrTqAsiEzO6zDFLScjP3/995H6EfgW6gIIg1RKKN1/8fev4/u+cSg5iEVN+W/wuO/599CBQUEDT/OIKNycUUWXgKi5olp//H1+1ikFZUs3F0N7Oyl1dSlZSWl5SWl5ZTklOEsiFIXklVRk4JzpWRU4IrhiAVdR1kBSARVQ1HRzd5RRUAiAZRVCxkb2cAAAAASUVORK5CYII=" nextheight="360" nextwidth="1600" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>The full query is available in the Appendix.</p><div class="relative header-and-anchor"><h2 id="h-rootlet-holder-count-growth">Rootlet Holder Count Growth</h2></div><p>A second query categorizes Rootlet owners qualitatively (see Appendix for the query). This query joins the theme value, a static object value stored in RootletStaticFields, to the latest owner from RootletOwner.</p><p>This dashboard integration demonstrates the flexibility of the Sentio data pipeline. Not only can queries provide aggregate statistics, but they also enable detailed, address-specific analysis, such as determining the exact Rootlet holdings of an address Moreover, this level of data granularity enables targeted airdrops, for example, rewarding holders based on specific Rootlet attributes or ownership history.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/31875b43952708e9c3726fa11636bf61.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAANCAIAAABHKvtLAAAACXBIWXMAAAsTAAALEwEAmpwYAAACiUlEQVR4nJWTzW7TQBDHXTdOu/audyfZXa/jhtSxvXGsUIJr2hICBAmJC4oq7nyIAxJC4syJExJvwAEkDrwDj8GBN+DCkWcIst2EfoQifhqtrBmt/zOzM0YURVoPer3dKIrjKNFL0jQNw1D5vpCSXgoh7iVRQ0qZJEklE6d5TCjBS8pwhYNMhFyEnLVGKUUl66MGxlgpJSs870+yALBhbNya7c9fvCZeDKRJKTuXHca23+3fODjs9Xp1QmsqsCxrlfKqWAYApOF09999W3xdLI7f/mzD+fsMGEKOHkZ37t4Ogp2/CiCELnoZcGwYV2bPP3xfPP70azZ/2o9DSs/0mlFqY6y7ncnhgfL9/xOglLmEAPcevf/x6stiePOBx92LLaKUcgaCCwZrQnVXDNNsYIwrGXfVq7pdDtp00CbzY+SKhmnalR+dAmPbxvZpT/WwJS4hUdjjXBicC6110AkG44h7YJqmtaTZ3LIsy0GmSxwAoJRaliWllyRJGPb7cT/N47Np2Q7aJOTEwzkvK6CUZlmW5/kgHQyzYZqm9RJorYfpMC6/h6PRSGutlMIY+34nCAJV4iulOBeVyTZz/MEkuv+yZW8IWfs5AJRjmiRJURSj0SjP86Io6nM8Hud5Ph6P672TUkLF8ma7Nl4hpGzZRvfg+Nqzz9AwKgF+IoCQk6bpbDbL83xaMZlMptPp0dFRfe7t7WVZVm9G+arVD13iMOrWkgCsxTnbMoLi4dUnH13DaHMJUI76SQWrRbuEVUZCekqK7qDo7GrB21J6yu9I6Qlm967fy+ZvuGN4SnWCHU+pUsCyrNVI/ZPtZpOJTnnZDyjw7WYTIdTivJ4cWtbEEELVLIgk6QO0fwONC3SrZUvxLAAAAABJRU5ErkJggg==" nextheight="654" nextwidth="1600" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><div class="relative header-and-anchor"><h2 id="h-conclusion">Conclusion</h2></div><p>This article presented a comprehensive guide to building a Sui data pipeline for the Rootlets NFT collection using Sentio. The process began with defining custom entities to structure the data, followed by utilizing Sentio's TypeScript processors to handle data extraction, transformation, and the specific challenges of Sui object ownership. The integrated SQL Studio was then used to perform real-time analysis, generating queries for key metrics like holder count growth and thematic categorization.&nbsp;</p><p>The culminating dashboard, parameterized by wallet address, demonstrates the power of this pipeline for in-depth analysis. This approach not only provides valuable insights into the Rootlets collection but also establishes a reusable framework for developing more sophisticated data pipelines and analytical tools within the Sui ecosystem.</p><div class="relative header-and-anchor"><h2 id="h-sql-query-appendix">SQL Query Appendix</h2></div><div class="relative header-and-anchor"><h3 id="h-rootlet-holder-count-growth">Rootlet Holder Count Growth</h3></div><pre data-type="codeBlock" language="sql" text="WITH OrderedEvents AS (

&nbsp;&nbsp;&nbsp;SELECT

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ro1.objectID,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ro1.sender,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toDate(ro1.timestamp / 1000) AS start_date,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ifNull(argMin(toDate(ro2.timestamp / 1000), ro2.timestamp), toDate('2106-02-07')) AS end_date

&nbsp;&nbsp;&nbsp;FROM RootletOwner ro1

&nbsp;&nbsp;&nbsp;LEFT JOIN RootletOwner ro2

&nbsp;&nbsp;&nbsp;ON ro1.objectID = ro2.objectID AND ro1.timestamp < ro2.timestamp

&nbsp;&nbsp;&nbsp;GROUP BY ro1.objectID, ro1.sender, ro1.timestamp

),

-- Convert dates to integers for range function

DateRanges AS (

&nbsp;&nbsp;&nbsp;SELECT

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;objectID,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sender,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toUInt32(start_date) AS start_date_int,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toUInt32(end_date) AS end_date_int

&nbsp;&nbsp;&nbsp;FROM OrderedEvents

),

-- Expand date ranges into individual dates

ExpandedDates AS (

&nbsp;&nbsp;&nbsp;SELECT

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;objectID,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sender,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toDate(start_date_int + number) AS day

&nbsp;&nbsp;&nbsp;FROM DateRanges

&nbsp;&nbsp;&nbsp;ARRAY JOIN range(0, end_date_int - start_date_int) AS number

),

-- Get all distinct dates

AllDates AS (

&nbsp;&nbsp;&nbsp;SELECT DISTINCT toDate(timestamp / 1000) AS day

&nbsp;&nbsp;&nbsp;FROM RootletOwner

)

-- Join expanded dates with all dates and count distinct senders

SELECT

&nbsp;&nbsp;&nbsp;ad.day,

&nbsp;&nbsp;&nbsp;count(DISTINCT ed.sender) AS unique_holder_count

FROM AllDates ad

LEFT JOIN ExpandedDates ed ON ad.day = ed.day

GROUP BY ad.day

ORDER BY ad.day;"><code><span class="hljs-keyword">WITH</span> OrderedEvents <span class="hljs-keyword">AS</span> (

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">SELECT</span>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ro1.objectID,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ro1.sender,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toDate(ro1.timestamp <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>) <span class="hljs-keyword">AS</span> start_date,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ifNull(argMin(toDate(ro2.timestamp <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>), ro2.timestamp), toDate(<span class="hljs-string">'2106-02-07'</span>)) <span class="hljs-keyword">AS</span> end_date

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">FROM</span> RootletOwner ro1

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">LEFT</span> <span class="hljs-keyword">JOIN</span> RootletOwner ro2

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">ON</span> ro1.objectID <span class="hljs-operator">=</span> ro2.objectID <span class="hljs-keyword">AND</span> ro1.timestamp <span class="hljs-operator">&lt;</span> ro2.timestamp

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> ro1.objectID, ro1.sender, ro1.timestamp

),

<span class="hljs-comment">-- Convert dates to integers for range function</span>

DateRanges <span class="hljs-keyword">AS</span> (

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">SELECT</span>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;objectID,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sender,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toUInt32(start_date) <span class="hljs-keyword">AS</span> start_date_int,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toUInt32(end_date) <span class="hljs-keyword">AS</span> end_date_int

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">FROM</span> OrderedEvents

),

<span class="hljs-comment">-- Expand date ranges into individual dates</span>

ExpandedDates <span class="hljs-keyword">AS</span> (

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">SELECT</span>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;objectID,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sender,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toDate(start_date_int <span class="hljs-operator">+</span> number) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">day</span>

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">FROM</span> DateRanges

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">ARRAY</span> <span class="hljs-keyword">JOIN</span> <span class="hljs-keyword">range</span>(<span class="hljs-number">0</span>, end_date_int <span class="hljs-operator">-</span> start_date_int) <span class="hljs-keyword">AS</span> number

),

<span class="hljs-comment">-- Get all distinct dates</span>

AllDates <span class="hljs-keyword">AS</span> (

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">DISTINCT</span> toDate(<span class="hljs-type">timestamp</span> <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">day</span>

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">FROM</span> RootletOwner

)

<span class="hljs-comment">-- Join expanded dates with all dates and count distinct senders</span>

<span class="hljs-keyword">SELECT</span>

&nbsp;&nbsp;&nbsp;ad.day,

&nbsp;&nbsp;&nbsp;<span class="hljs-built_in">count</span>(<span class="hljs-keyword">DISTINCT</span> ed.sender) <span class="hljs-keyword">AS</span> unique_holder_count

<span class="hljs-keyword">FROM</span> AllDates ad

<span class="hljs-keyword">LEFT</span> <span class="hljs-keyword">JOIN</span> ExpandedDates ed <span class="hljs-keyword">ON</span> ad.day <span class="hljs-operator">=</span> ed.day

<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> ad.day

<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> ad.day;</code></pre><div class="relative header-and-anchor"><h3 id="h-rootlet-holder-count-per-theme">Rootlet Holder Count Per Theme</h3></div><pre data-type="codeBlock" language="sql" text="WITH LatestOwners AS (

&nbsp;&nbsp;&nbsp;SELECT

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;objectID,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;argMax(sender, timestamp) AS latest_sender

&nbsp;&nbsp;&nbsp;FROM RootletOwner

&nbsp;&nbsp;&nbsp;GROUP BY objectID

)

SELECT

&nbsp;&nbsp;&nbsp;lo.latest_sender,

&nbsp;&nbsp;&nbsp;s.theme,

&nbsp;&nbsp;&nbsp;COUNT() AS theme_count

FROM LatestOwners AS lo

INNER JOIN RootletStaticFields AS s ON lo.objectID = s.id

GROUP BY lo.latest_sender, s.theme

ORDER BY theme_count DESC"><code><span class="hljs-keyword">WITH</span> LatestOwners <span class="hljs-keyword">AS</span> (

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">SELECT</span>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;objectID,

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;argMax(sender, <span class="hljs-type">timestamp</span>) <span class="hljs-keyword">AS</span> latest_sender

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">FROM</span> RootletOwner

&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> objectID

)

<span class="hljs-keyword">SELECT</span>

&nbsp;&nbsp;&nbsp;lo.latest_sender,

&nbsp;&nbsp;&nbsp;s.theme,

&nbsp;&nbsp;&nbsp;<span class="hljs-built_in">COUNT</span>() <span class="hljs-keyword">AS</span> theme_count

<span class="hljs-keyword">FROM</span> LatestOwners <span class="hljs-keyword">AS</span> lo

<span class="hljs-keyword">INNER</span> <span class="hljs-keyword">JOIN</span> RootletStaticFields <span class="hljs-keyword">AS</span> s <span class="hljs-keyword">ON</span> lo.objectID <span class="hljs-operator">=</span> s.id

<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> lo.latest_sender, s.theme

<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> theme_count <span class="hljs-keyword">DESC</span></code></pre><p><br><br></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>sui</category>
            <category>analytics</category>
            <category>data</category>
            <category>pipeline</category>
            <category>sql</category>
            <category>typescript</category>
            <category>sentio</category>
            <category>rootlet</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/3fed1f822416e091b8a54829732eae87.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Economics of Sui Shared Liquidity Pool Objects]]></title>
            <link>https://paragraph.com/@evandekim/economics-of-sui-shared-liquidity-pool-objects</link>
            <guid>ZqplkBGMjsnMIvd8e1cN</guid>
            <pubDate>Sun, 29 Dec 2024 00:47:59 GMT</pubDate>
            <description><![CDATA[Over 50% of transactions to mutate shared liquidity pool objects that pay the maximum gas fee end up below the median block position.

Users paying max gas fees have a 0.001-0.002% chance of securing the top block position, compared to 7-10% for those not paying the highest fees.SIP-45 proposes increasing the max gas price to 1 trillion MIST and making tra...]]></description>
            <content:encoded><![CDATA[<p><strong>Sentio Referral Code</strong>: Reach out to @is_ye in the <a target="_blank" rel="noreferrer" class="dont-break-out" href="https://dune.com/evandekim/t.me/sentioxyz"><u>sentio telegram chat</u></a> and give the referral code evandekim2025 for a 20% discount for Sentio subscription! </p><div class="relative header-and-anchor"><h1 id="h-tldr">TLDR</h1></div><ul><li><p>Shared liquidity pool object mutations have been consistently increasing, now occurring in nearly every Sui checkpoint (≈100% of blocks).</p></li><li><p>Over 50% of transactions to mutate shared liquidity pool objects that pay the maximum gas fee end up above the median block position.</p></li><li><p>Users paying max gas fees have a 0.001-0.002% chance of securing the top block position, compared to 7-10% for those not paying the highest fees.</p></li><li><p>SIP-45 proposes increasing the max gas price to 1 trillion MIST and making transaction inclusion faster at the expense of increased validator network bandwidth utilization.</p></li></ul><div class="relative header-and-anchor"><h1 id="h-introduction">Introduction</h1></div><p>The Sui blockchain faces unique challenges in processing shared object transactions that require consensus. This analysis examines shared object change data across 28 weeks, focusing specifically on shared liquidity pool objects and their gas fee patterns. The findings inform critical decisions about SIP-45, a proposed change to transaction prioritization mechanisms.</p><p>The following data and query can be found in the appendix section and run using <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sentio.xyz/reference/executesql">Sentio data studio</a>. Objects are filtered specifically for shared objects such as liquidity pools and swaps. Query performance took about 8:30 minutes so it is recommended to run on a lower time frame first. All of the charts can be found in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/evandekim1/suinomics#shared-liquidity-objects">Suinomics Dune dashboard</a> under shared liquidity pool objects</p><div class="relative header-and-anchor"><h1 id="h-shared-liquidity-pool-objects">Shared Liquidity Pool Objects</h1></div><p>Shared object mutations now occur in nearly every Sui checkpoint, indicating market saturationSui's shared objects are mutable, lack specific ownership, and enable complex interactions between multiple users by leveraging their own authorization logic within smart contracts. Unlike owned objects, which are tied to a single user and can be updated without consensus, shared objects require a consensus protocol to ensure safe and consistent modifications when accessed by multiple parties.</p><p>The most common examples of shared objects are liquidity pools, where users have shared access to many pools of liquidity.  Over the past six months, the number of shared liquidity pool objects has reached saturation, with at least one shared object being <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://intro.sui-book.com/unit-two/lessons/2_ownership.html">mutated</a> in almost every Sui checkpoint (equivalent to a block).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/028c31c970c574d5cb27d605f8be10f9.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAANCAIAAABHKvtLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTYXQgMjggRGVjIDIwMjQgMDU6NTg6MTggUE0gRVNUxrXzIgAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAOvSURBVHicjVRdbBRVFL7hQWOQmBgrBgtoER+MQiUmxKKGxiBFY6ISSG2BqCQG7COVookGtw8kIiGkUWt9aNQUSZFUmz5YyvYvsz/Mbmd/uu0s+zO7LbPb3dmZnZ07s3vvzszONVvK0vZB/XIezv3Jd875zrkXxFh23G6nXU4nRTkpampiwklR0TAbZJjZgD88Pz8XCiU5zqoYpl429bKBsY6xqZd1jHSMrYph6GXTNAixandqZmAElHyeZdlMJitKkihJmUxGEAQIoSRJgiBkBUGUJFXTLEIsQgxzBYZpWoQUi2gpI0Ko8als5E4imUwbpklWwTQMYGBkrt39d0CocRwPoRYMRZsPnNz/5sePbn515673m/e+d7jlBBtZqPLeJ1wJ8H94rUqFEDI+Qb+45zDY8NLGp/bVP/fW5Su/3bK7u7t7aYqOOCh6eCTC8Rg9IKwGQKpaUJT/LMJa1udIaycAz4JHXt668yB4aHexiBaT6TTLBoau+m8MTPZekUs4J2TXVODzerOCYK4FQhghXFtahKh58drA8N597YPXR6emGZ8vPDcfR0p+9IceZ/9PA5+2XXy9sefQG3ycc7ndsiwXl4E1FXhcLgjhunxlWYnFeUIIQtgwK4RU/uzp2QK2n+44z99dXEgmPTRjERL863o7ALbGHWe3PP42AKcBYMb+9vh8SY5zuW87KMeclwEMTYuSVEtW1w1CSCYjDQ7eTKdzXm8oHE7AgvLBO58AsHV62qNqMCPk8sWSKonf7NpxFIBWAI4CcAKAb1/bfXeGnmVZjKqDU1VV16s9UDXtgWrLDsfxdduaz3Z991mHre1YV/vxLx7e3DRmd5d1Yynkp368TP/68+iFr85tf9J+yfb9u/v7jhwU41Hv1f7glD2vqvcmYqUHpl5GCP8xNB5JpPv6h8cpv8cfrX/+EFjBNgAeAwCcPNVNiOX9vf9M3aYOALrqnzhTt4n3z+Sid4Qoq5eKVT0VWZXENXOh68A0DFhQWls+bGpsadjYsKeh6ZUXDjy94Rlb23FbR+elz78899GpXtuFmRsDty6eH/660/VLX3jKzrOzHEOXyuXsYhIWZFXTCrIMoYJUdXUvqxVk00upVCrh9/gnRpmxkaV5hg94AvbhmZGhVICOOiYdQ9fYyZusm2Lo2+FYHGK9WCpFOC4cSxRkWRCEYCCYSCRUWAW6/whQGacKOa2kgYVYLJ1KKVBd/RSsVc49H0Io5nLrfhurYqyz2hGEcjSdkBXpH3CW7sUgkoNWAAAAAElFTkSuQmCC" nextheight="370" nextwidth="917" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This saturation indicates growing network activity and highlights the importance of efficient transaction ordering mechanisms. The data reveals that shared object mutations have become a fundamental part of Sui's daily operations, particularly in DeFi applications where multiple users frequently interact with the same liquidity pools.</p><div class="relative header-and-anchor"><h1 id="h-max-gas-fees">Max Gas Fees</h1></div><p>Max gas fees represent the highest amount a user is willing to pay for the computational and storage resources required to process their transaction. According to <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sui.io/concepts/tokenomics/gas-pricing">Sui documentation</a>, gas fees are calculated based on the computation units and storage units consumed by a transaction, multiplied by their respective prices. The computation price itself is a combination of a network-wide reference price and an optional user-specified tip aimed at incentivizing faster processing by validators.</p><p>However, quantitative analysis of 28 weeks of transaction data, focusing on shared liquidity pool objects, reveals an intriguing insight: paying the maximum gas fee does not consistently guarantee a superior position within a block. The data shows that over 50% of transactions that opted for the highest gas fees ended up in positions that were worse than the median block position. </p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/901283a51bc2d992532df1662733e5ff.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAANCAIAAABHKvtLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTYXQgMjggRGVjIDIwMjQgMDg6MDI6MzMgUE0gRVNUeN+YPQAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAARBSURBVHicfVR9UFRVFL+UTX8UI1NoH9NOhjZIUGFmZogBo5io4xY2ZKGNZkUToDSTfIxLMow2YzJNgsMfgEAgLgwIAaJpbIJsgLS24PJ4+/kWHvt2l919LLvvvd33cfc2y8dM/pG/Of+cc+/5uOfc3wGUlTBgmBHDzHo9rtOZ9Tiu01lNJpKwECajxWi0mkxWk4ljmJAkQoF/SPhHqgIvBTgwTRBjGg1uwHF8amJiYgrHsUnMYDDguH4Kx/WGMGbt9hD6XzziCIoiCEmiIAgEYZ+cnBb8fo5hgoI4Q875fBzD8nOzTlXf8J/t10WODSEE/4NgkKfn3BBCp5PmGIaiPBTlcTo8FOkMh4ZwOQEUeFGEt2/fV3YM+Wy2ebtjfoFTD2MU5bY76FHVyMkvz52S57AupwBDHBeAEIli2NnpoDENxgtw4I521jx9V43dG9OPjWDaUd3DCUQRIdTRqW5o7neaLQ4r6fYwN2+OkeTc+ATxa3VHatJXGXF71f0jlNNro9xLzhAileofZWPfLOUpLKppqOlSlDW1tA5UVXXd6BxAaLkISRCA1+MRBLH28o3SsitWHY5rcRy3lSjqO66pMz468947xx5/MnkdSIh/7dOC72szPym/M/hAPYz9+FPbMxs+i4s/nJiUB9bsfzHmIFidkbAlJzomq7JCiRDiuIAoCFKAAVaTyeV2lygaktMLr1/ta6nvUXYMvRCbHfHSQQBSANgaGfFuLFgfu+qNSJAYk/B55CuZAGwHIBlEpIBV20HUXgCSANi8aNwBwJbK81cQQkaTyWK22A1GQBIWmvbuPqAAT+3+Yn9+dlbpvo/LX914aNO23MLi+kOZp9PiDuyJjC+UHy8+VWXQE5WVbWfK6k8W/FJRXpcrz2tq7t+WnJf8VvaO9/N37iw4klXaWNWKEPL7mUAwyDIcCEkCywajNh4BYGvC2tQnoj8AILWo4OJ9jWFwcPyP6ppLx0/8nJau6e7lQiEoSYIgQog4TsSH/+4sOh1g2aGhBxe+VdSdr1XW93bVtas6b4XnJEkr3xQKDMs/L5PHgpfjwHpZVEr06pSK/HKfd6G/ILdnk6xN9lwTeGzygoIeHws4KAglKEHONoNdPNuTtpnsajY3XurelfSbPF1V/J0q57Be2RgmhySFwkPmAYISx7A/pH5YAtbkRMWey8iq3rOv++tj3lny9/xvul+XtYKIFgC634xRH5VrS0/Y+3tNTZfUR+U9ievao56+tevta7JnlSBCCUDnhrVXAcCrzi7VvvwCZt7r9XppI05pRojRv3wzFnbGbBvXOMxmv9/vd1LTmnvm4SEfZZtzOsMrRKf1kgQzT5MGjNRpGb9v3oBZRu7adFq/jfSTBE+7FkkMfTzHcgxw2+20y8WynAThEumXGStJIUlCK0aEEO1y8QFuZQnAFQn3OnxnSV1sDhRFyAsuH73AeP8Fk7KTDoK9b8AAAAAASUVORK5CYII=" nextheight="366" nextwidth="914" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Ideally, users expect that higher gas fees would translate to higher priority and quicker inclusion in blocks, but this is only the case approximately 20% of the time. This discrepancy suggests that while the Sui network's gas pricing mechanism is designed to prioritize transactions based on fee offerings, other factors—such as network congestion and validator behavior—significantly influence transaction ordering. Consequently, users do not always benefit from setting their max gas fees to the highest possible values to maximize transaction inclusion.</p><p>Additionally, while the number of shared liquidity object mutations has increased, the number of max gas paid for these mutations has decreased. At the same time the number of shared objects that get the top position has been rising steadily. The below chart shows that a sender has a higher probability of getting top of block (~ 7 -10%)  over those paying the max gas fee (.001 - .002%).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1d18bc2aece9158a3cca825e3b0d36dd.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAANCAIAAABHKvtLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTYXQgMjggRGVjIDIwMjQgMDg6MDY6MjEgUE0gRVNUnVj4gQAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAALxSURBVHiclZNfSFNRHMcP2EsPQW9B9VA9CBERGPQS9ofKB01JQaXIl0KysSDqSUTYJmhNTRPEaerVzT9zNknTqcxCzWDOqRfzz9TrnOaf3XTH/TuXu3PdPbHdEDND/XAezjlwzvf3/Z3vAcbm5jyFoqS4uKuzQ0tRVHW1lqK0FGVsbXWzLNzccLOsD0K4ueH3engOHWkEvFtAS1EKpTI3N7e0tPStWl1YWKRS5UUmha1Go15v0Okaukwmna6BpmlyRHgOAZ7jEEJ8MMhxnN/rIYR4oRtjPiSKeBcIIXxo9gigkCgSQpDPu+50EEKW7DOCIBy12H0JC4yN2hBCuwVWmNk9AsLfy901HixQVVGxurpGCPm1smxu7wiJotM+/a8DjPEhbWGMvR7PVhjohe6wgNVqhaxrbtzWpKlEiFuYnPBBuNPQkCjCzc2m4vzPtRrH4iJN0/Pz806n83/dt8/N6fWGLpOJqq4xfTSGBRiGmRsfsZp72urrMRYWfoyvMLNSLVgSYF3yxxmyJ1lTU1PdEQYGBiLPvo8nhNBqBIZxMIwDaOvqHcx8f5vhU1V5uVLpZtnpEcuSfWbPMQg9EIYzhjHeFrZ3N+SAFG1tQci6dOp8Kl/x9N6d6bGRPoNOU/Bm7eeKpsaQKcvOkudkyXNyVSWd3f3G9j6vP0AI8QfQ8Ag9ODjMB4MRGUG6NzL5YyskhjgUAISI607HZQBOA3AMgLvR0VeiAAAg+WoMAOBs1ImMBw/Pn7oQ3opwK/ri/Ztx0ecuAQBOHj9zLeZGQlxKakrG7diEpPi0pPg0U6d5J2lhB/bJqVGbTfVCnhZ7PTMpsSpPVfT61fP09GyZrOW9urexRvuuoKFMbW6pU8mfvXyUKktO1KiUpmZdS+2HHqPB8sVsbu+oLCszNjbSFsvo0JAPQinZy+z6mmsNfDX3fv825GLZkChKPy6CKIUyJIp8EHM8TwgZo2npGXa6L30g6bqdEAtCkOcQ8vmsE7ZZZvY3m4EUGXGxr9cAAAAASUVORK5CYII=" nextheight="366" nextwidth="914" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This misalignment between gas fees and transaction positioning presents a significant challenge for users seeking predictable transaction inclusion times. The data suggests that the current gas fee mechanism may need refinement to better serve its intended purpose of transaction prioritization.</p><div class="relative header-and-anchor"><h1 id="h-sip-45-prioritized-transaction-submission"><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/shio-coder/sips/blob/main/sips/sip-45.md">SIP-45</a>: Prioritized Transaction Submission</h1></div><p>SIP-45 introduces greater economic incentives to utilize more validator bandwidth for more reliable inclusion. However, this adjustment could inadvertently drive spamming, as users and bots exploit the opportunity to secure consistent inclusion in blocks. An amplification factor of 5 has been proposed as the supremum gas price required to be immediately broadcasted. Thus if a reference gas price is 750 MIST and the amplification factor is 5, then a transaction will only need 3,750 MIST to be broadcast faster. </p><p>SIP-45: Prioritized Transaction Submission a SIP that is going to test the boundaries of consensus spam on Sui. The major changes are:</p><ol><li><p>Increase the max gas price from 99,999 MIST to 1,000,000,000,000 (1 trillion MIST).</p></li></ol><ol><li><p>Allow transactions that pay a higher gas price to be immediately broadcasted.</p></li></ol><p>Chains offering low transaction costs and high blockspace are inherently vulnerable to transactions spamming, as <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/smyyguy/status/1835030196258246692">bots exploit these conditions</a> to to maximize profits. While this increases blockspace usage, it also boosts <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/smyyguy/status/1870953803606049064">validator compensation</a>, often leading to higher yields.</p><p>A brief numerical analysis is provided <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/shio-coder/sips/blob/main/sips/sip-45.md">in the SIP-45 proposal</a> which demonstrates potential effects with an amplification factor of 5. The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/sui-foundation/sips/pull/45#discussion_r1870864858">authors make the assumption</a> that most transactions are not competitive in nature. They isolate competitive transactions based on the amount of gas being paid. An important observation in the initial data shows that the gas price bucket distribution is heavily skewed at the tail end at the 90,000 - 100,000 MIST maximum range, suggesting that a higher gas price is warranted.</p><div class="relative header-and-anchor"><h1 id="h-optimal-amplification-factor">Optimal Amplification Factor</h1></div><p>Choosing an amplification factor will strike a balance between higher yield vs increased bandwidth usage. For example a low amplification factor of 2 will make it very low cost to spam the network, soaking up additional bandwidth. A higher amplification factor such as 100 will decrease the subset of transactions that will spam the network, making the supremum gas price a much higher hurdle. </p><p>Given that the numerical analysis indicated a heavily skewed tail at the current maximum gas price range already, indicating an active market at that price point, an argument could be made that a higher amplification factor of 120 could be set as a conservative factor, making the supremum gas price 750 MIST * 120 =  90,000 MIST a good starting point and can be gradually lowered from there as a tradeoff to introducing a new priority fee feature while still conserving validator bandwidth. </p><p>Another reason to set a more conservative amplification factor is to also to understand what the effects of increasing the max gas fee will have on network activity. With the current fee cap, it is not clear what the upper bounds for max gas fees will be for contentious shared objects and whether the higher certainty of transaction inclusion will also spark higher demand that is not reflected in historical data numbers. Intuitively as the surface area of mutated shared liquidity pool objects increases, so should too the number of contentious opportunities. </p><p></p><div class="relative header-and-anchor"><h1 id="h-conclusion">Conclusion</h1></div><p>This article expands on the initial results so that a more data informed decision can be made. A more sui-native way to quantify contentious transactions based on ownership. If an object has a single owner, then these transactions get filtered out into a fast path, skipping the consensus mechanism. If an object is a shared object and can have different owners, then these objects will fall into state contention based on user is claiming ownership and getting the state lock on that object. </p><p>The implementation of SIP-45 represents a critical evolution in Sui's transaction processing mechanism. While the proposed changes offer potential improvements in transaction inclusion predictability, careful consideration must be given to the amplification factor to prevent network congestion while maintaining economic efficiency. Ongoing monitoring and analysis of shared object mutations and gas fee patterns will be essential to fine-tune these parameters and ensure optimal network performance.</p><p></p><div class="relative header-and-anchor"><h2 id="h-appendix-sentio-sql-query">Appendix: Sentio SQL Query</h2></div><pre data-type="codeBlock" language="sql" text="WITH object_changes_filter AS (
    SELECT DISTINCT
        digest,
        checkpoint
    FROM sui.object_changes
    WHERE owner_type = 'shared'
      AND timestamp >= now() - INTERVAL 28 WEEK
      AND (object_type LIKE '%pool%' OR object_type LIKE '%swap%')
),
recent_transactions AS (
    SELECT 
        t.digest, 
        t.checkpoint, 
        t.sender,
        t.gas_price, 
        t.transaction_position,
        toStartOfHour(t.timestamp) AS hour
    FROM sui.transactions AS t
    INNER JOIN object_changes_filter AS ocf
    ON t.digest = ocf.digest AND t.checkpoint = ocf.checkpoint
),
precomputed_metrics AS (
    SELECT
        hour,
        min(rt.gas_price) AS min_gas_price,
        max(rt.gas_price) AS max_gas_price,
        min(rt.transaction_position) AS min_position,
        max(rt.transaction_position) AS max_position,
        median(rt.transaction_position) AS median_position
    FROM recent_transactions AS rt
    GROUP BY hour
),
aggregated_data AS (
    SELECT
        rt.hour,
        avg(rt.gas_price) AS avg_gas_price,
        pm.min_gas_price,
        pm.max_gas_price,
        avg(rt.transaction_position) AS avg_position,
        pm.min_position,
        pm.max_position,
        pm.median_position,
        countIf(rt.gas_price = pm.min_gas_price) AS count_min_gas_price,
        countIf(rt.gas_price = pm.max_gas_price) AS count_max_gas_price,
        countIf(rt.transaction_position = pm.min_position) AS count_min_position,
        countIf(rt.transaction_position = pm.max_position) AS count_max_position,
        countIf(rt.gas_price = pm.max_gas_price AND rt.transaction_position = pm.min_position) AS max_gas_in_min_position,
        countIf(rt.gas_price = pm.max_gas_price AND rt.transaction_position > pm.median_position) AS max_gas_higher_than_median_position,
        uniqExact(rt.checkpoint) AS unique_checkpoints_count,
        count() AS total_rows
    FROM recent_transactions AS rt
    INNER JOIN precomputed_metrics AS pm
    ON rt.hour = pm.hour
    GROUP BY rt.hour, pm.min_gas_price, pm.max_gas_price, pm.min_position, pm.max_position, pm.median_position
)

SELECT
    hour,
    avg_gas_price,
    min_gas_price,
    max_gas_price,
    avg_position,
    min_position,
    max_position,
    median_position,
    count_min_gas_price,
    count_max_gas_price,
    count_min_position,
    count_max_position,
    max_gas_in_min_position,
    max_gas_higher_than_median_position,
    unique_checkpoints_count,
    total_rows AS total_shared_objects_hourly
FROM aggregated_data
ORDER BY hour DESC;
"><code><span class="hljs-keyword">WITH</span> object_changes_filter <span class="hljs-keyword">AS</span> (
    <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">DISTINCT</span>
        digest,
        checkpoint
    <span class="hljs-keyword">FROM</span> sui.object_changes
    <span class="hljs-keyword">WHERE</span> owner_type <span class="hljs-operator">=</span> <span class="hljs-string">'shared'</span>
      <span class="hljs-keyword">AND</span> <span class="hljs-type">timestamp</span> <span class="hljs-operator">&gt;=</span> now() <span class="hljs-operator">-</span> <span class="hljs-type">INTERVAL</span> <span class="hljs-number">28</span> WEEK
      <span class="hljs-keyword">AND</span> (object_type <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%pool%'</span> <span class="hljs-keyword">OR</span> object_type <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%swap%'</span>)
),
recent_transactions <span class="hljs-keyword">AS</span> (
    <span class="hljs-keyword">SELECT</span> 
        t.digest, 
        t.checkpoint, 
        t.sender,
        t.gas_price, 
        t.transaction_position,
        toStartOfHour(t.timestamp) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">hour</span>
    <span class="hljs-keyword">FROM</span> sui.transactions <span class="hljs-keyword">AS</span> t
    <span class="hljs-keyword">INNER</span> <span class="hljs-keyword">JOIN</span> object_changes_filter <span class="hljs-keyword">AS</span> ocf
    <span class="hljs-keyword">ON</span> t.digest <span class="hljs-operator">=</span> ocf.digest <span class="hljs-keyword">AND</span> t.checkpoint <span class="hljs-operator">=</span> ocf.checkpoint
),
precomputed_metrics <span class="hljs-keyword">AS</span> (
    <span class="hljs-keyword">SELECT</span>
        <span class="hljs-keyword">hour</span>,
        <span class="hljs-built_in">min</span>(rt.gas_price) <span class="hljs-keyword">AS</span> min_gas_price,
        <span class="hljs-built_in">max</span>(rt.gas_price) <span class="hljs-keyword">AS</span> max_gas_price,
        <span class="hljs-built_in">min</span>(rt.transaction_position) <span class="hljs-keyword">AS</span> min_position,
        <span class="hljs-built_in">max</span>(rt.transaction_position) <span class="hljs-keyword">AS</span> max_position,
        median(rt.transaction_position) <span class="hljs-keyword">AS</span> median_position
    <span class="hljs-keyword">FROM</span> recent_transactions <span class="hljs-keyword">AS</span> rt
    <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">hour</span>
),
aggregated_data <span class="hljs-keyword">AS</span> (
    <span class="hljs-keyword">SELECT</span>
        rt.hour,
        <span class="hljs-built_in">avg</span>(rt.gas_price) <span class="hljs-keyword">AS</span> avg_gas_price,
        pm.min_gas_price,
        pm.max_gas_price,
        <span class="hljs-built_in">avg</span>(rt.transaction_position) <span class="hljs-keyword">AS</span> avg_position,
        pm.min_position,
        pm.max_position,
        pm.median_position,
        countIf(rt.gas_price <span class="hljs-operator">=</span> pm.min_gas_price) <span class="hljs-keyword">AS</span> count_min_gas_price,
        countIf(rt.gas_price <span class="hljs-operator">=</span> pm.max_gas_price) <span class="hljs-keyword">AS</span> count_max_gas_price,
        countIf(rt.transaction_position <span class="hljs-operator">=</span> pm.min_position) <span class="hljs-keyword">AS</span> count_min_position,
        countIf(rt.transaction_position <span class="hljs-operator">=</span> pm.max_position) <span class="hljs-keyword">AS</span> count_max_position,
        countIf(rt.gas_price <span class="hljs-operator">=</span> pm.max_gas_price <span class="hljs-keyword">AND</span> rt.transaction_position <span class="hljs-operator">=</span> pm.min_position) <span class="hljs-keyword">AS</span> max_gas_in_min_position,
        countIf(rt.gas_price <span class="hljs-operator">=</span> pm.max_gas_price <span class="hljs-keyword">AND</span> rt.transaction_position <span class="hljs-operator">&gt;</span> pm.median_position) <span class="hljs-keyword">AS</span> max_gas_higher_than_median_position,
        uniqExact(rt.checkpoint) <span class="hljs-keyword">AS</span> unique_checkpoints_count,
        <span class="hljs-built_in">count</span>() <span class="hljs-keyword">AS</span> total_rows
    <span class="hljs-keyword">FROM</span> recent_transactions <span class="hljs-keyword">AS</span> rt
    <span class="hljs-keyword">INNER</span> <span class="hljs-keyword">JOIN</span> precomputed_metrics <span class="hljs-keyword">AS</span> pm
    <span class="hljs-keyword">ON</span> rt.hour <span class="hljs-operator">=</span> pm.hour
    <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> rt.hour, pm.min_gas_price, pm.max_gas_price, pm.min_position, pm.max_position, pm.median_position
)

<span class="hljs-keyword">SELECT</span>
    <span class="hljs-keyword">hour</span>,
    avg_gas_price,
    min_gas_price,
    max_gas_price,
    avg_position,
    min_position,
    max_position,
    median_position,
    count_min_gas_price,
    count_max_gas_price,
    count_min_position,
    count_max_position,
    max_gas_in_min_position,
    max_gas_higher_than_median_position,
    unique_checkpoints_count,
    total_rows <span class="hljs-keyword">AS</span> total_shared_objects_hourly
<span class="hljs-keyword">FROM</span> aggregated_data
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">hour</span> <span class="hljs-keyword">DESC</span>;
</code></pre><p></p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>sui</category>
            <category>sip-45</category>
            <category>priority</category>
            <category>gas</category>
            <category>fee</category>
            <category>economics</category>
            <category>sentio</category>
            <category>sql</category>
            <category>data</category>
            <category>analytics</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/63bfae2172b8bcfdb09b1f4d91c23a60.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Data Observability with Sentio Pipelines
]]></title>
            <link>https://paragraph.com/@evandekim/data-observability-with-sentio-pipelines</link>
            <guid>ULF4OFPuUIiUOzKeJSYu</guid>
            <pubDate>Wed, 18 Dec 2024 01:26:59 GMT</pubDate>
            <description><![CDATA[Sentio Referral Code: Reach out to @is_ye in the sentio telegram chat and give the referral code evandekim2025 for a 20% discount for Sentio subscription! IntroSentio is a game-changing development platform that enables end-to-end data observability for web3 data pipelines. The platform provides a fully integrated, hosted experience that combines modern monitoring, alerting, and debugging capabilities. What makes Sentio particularly powerful is its real-time processing architecture: developer...]]></description>
            <content:encoded><![CDATA[<p><strong><em>Sentio Referral Code</em></strong><em>: Reach out to @is_ye in the </em><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://dune.com/evandekim/t.me/sentioxyz"><em><u>sentio telegram chat</u></em></a><em> and give the referral code evandekim2025 for a 20% discount for Sentio subscription! </em></p><div class="relative header-and-anchor"><h2 id="h-intro">Intro</h2></div><p>Sentio is a game-changing development platform that enables end-to-end data observability for web3 data pipelines. The platform provides a fully integrated, hosted experience that combines modern monitoring, alerting, and debugging capabilities.</p><p>What makes Sentio particularly powerful is its real-time processing architecture: developers write and deploy typed processor code to Sentio, which automatically handles everything from historical data backfilling to real-time blockchain streaming, including automatic adjustments for chain reorganizations.</p><div class="relative header-and-anchor"><h2 id="h-what-problems-does-sentio-solve">What problems does Sentio solve?</h2></div><p>Sentio's TypeScript pipeline interface transforms complex multi-chain development into a straightforward process, eliminating the traditional headaches of handling different blockchain protocols across multiple smart contract languages. The platform excels at:</p><ol><li><p>Supporting diverse blockchains- EVM, SVM, MOVE, Fuel, and Starknet <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sentio.xyz/docs/supported-networks"><u>(supported networks)</u></a></p></li><li><p>Strongly typed objects for events and functions</p></li><li><p>Data processing, observability, and debugging wrapped up in a hosted db solution with easy sql and graphql endpoint creation for analytics</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://sentioxyz.medium.com/complete-blockchain-data-testing-suites-ensure-data-reliability-and-availability-992009b60f71"><u>Robust testing suites</u></a> for data pipelines</p></li></ol><div class="relative header-and-anchor"><h2 id="h-real-world-example-sui-lst-dashboard-with-dune-frontend">Real-World Example: Sui LST Dashboard with Dune Frontend</h2></div><p>To illustrate Sentio's capabilities, let's consider the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/evandekim/sui-liquid-staking"><u>Sui LST Dashboard</u></a>, a comprehensive analytics tool built using Sentio pipelines and integrated with Dune's frontend. The sentio processor code can be found <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/springsui_lsts/tree/main"><u>here</u></a>. This dashboard provides real-time insights into the liquid staking ecosystem on the Sui blockchain.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/15c25e53c803dd7627edf7afead02843.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAARCAIAAAAzPjmrAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBUdWUgMTcgRGVjIDIwMjQgMDg6MDg6NTAgUE0gRVNUFYy7vQAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAQwSURBVHicjVTdaxxVFL+pLRosSh4CEfTBEmhFRBD9A+qzIBYFwWff+mBBoSA+llIMiJAWNQ+1RLu1MQZim+8scd2Q3aS7bDbZ3cnuzM7szO7MzvfsfNydyZ3ZKzNDJmkl6OHHcM6dmXPu/d3fOaBQKO1XKlSTbpAkyXBUkz6o1yGE+H9YEAwwxrpulPf2GY5rtdgG2WRarKZpsqyOvnb5whsfAF0WifIeUd5rVCpEea9Zq/FMU+SYvqH1De3QsSVJZFstRZY1VVFkGSEUIDQIghi2abZZVuCFPuwjhPrQoSmqUa8fEAd1oiEKEqhsZdcfphZS0+m5mUe/3FudebDyWyo7P6eSdZmoWiIPITR7PQid0NE017EwxnH2APmuY1mW5Zg93+tjjF3oMDTNthimXlMUyewZwOQ5lSRUsqGS9RgGR+sMFfmELXcTQlhW+P3ezPc371T2yZifqAbqQ7e8uZVf/8u2nUHgQ+jmMrnHqblSsToIAmBwjExUoxrPIj4BPrJuV9nJ766t5RoNNl4ZBIHveRhjmuIIgg5PhQ4xHjB0J5N5IkpaWACqkslzvQ5nibwptA2OSaAzTagqsihB2455T/IG0U0YtqGriq6qQfTK6Tu9nqbLsue6GGOEkGYagCzvZh8/2tlY31pZIos7Js8lOCpD6UwzelJak4wRh8ni02Ej+cwU2kDmWq1KWWgQBsdaIm+0WaFBxBApMkJDoZuWKPwXQgIidCyRd2TREgSoKqBOEIVCoVYj2p2OpmrQtgReEHih2+2qqqrruiRJmqqdlH/CVWI+8p/qD+Q7ph05CAQIRZ4XO8jzYkL/1VPHloToyDDGYlecuPntZ1c+nZr88frVr6YmfwpvxbYBxlju8LVsliyVoyY6TBKhU0wmSdu0kH+861TqwaWLl4aGzpw5e3b4hWEAwOTkJMa4vrcfFhA4YeVROpst+MhHnhfv6DQbBIHM0m2a2lhfWl2Yn/556pMrH46cP/fSi+fGRkfGRkdeGRsdfv65O1GBFk0DFCo3lNTgSNfI97m2lMvtz/+ZST1cnZ1Nz/6Rvn9/8e4PqdsTU999M3Hj6vX3x9+6CIZeB+BVAC4A8CYA7wDwboS3ARgHYPrWrTCt2wfRkFFDOrxDzTQMXRN4vkmxpWJ1Y2N7eXlzdW0rnc4tL2ZDLG8uLv69sJBZXd/OZHdz+VouX8vvEPmdanG7XHxSLRWr+8VKMVeSRS0IkGZqQBIEhqZpimrRdJfnuzwvtDmzZ5zGT09TpS6vymKX53RVdl3btDQbhprBOByuJ5vR9zzgQoh8P+Qn0YbvJw7yfdd1IYSu6wbBILwh7zCepD4KBnjw643UtfGxLwBY/vrLQUT1SbGFo8Jz3ZOrz5h3nD38QVMVFA2fOFxZW3oZnL/9+bW7H733MQCl6alY+8k5FFn5BxtQgyYpHuGJAAAAAElFTkSuQmCC" nextheight="974" nextwidth="1846" class="image-node embed"><figcaption htmlattributes="[object Object]" class="">dune lst dashboard</figcaption></figure><div class="relative header-and-anchor"><h3 id="h-key-features-of-the-sui-lst-dashboard"><strong>Key Features of the Sui LST Dashboard:</strong></h3></div><ul><li><p><strong>Real-Time Data Streaming:</strong> Continuously monitors transactions and events on the Sui network.</p></li><li><p><strong>Comprehensive Analytics:</strong> Utilizes SQL and GraphQL endpoints for detailed data queries and aggregations.</p></li><li><p><strong>Seamless Integration with Dune:</strong> Enables the creation of dynamic and interactive visualizations, making data accessible and actionable for stakeholders.</p></li></ul><p>This infrastructure enables developers to quickly spin up observable data platforms that emit <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sentio.xyz/docs/metrics-in-processors"><u>pipeline metrics</u></a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sentio.xyz/docs/logs-in-processor"><u>event logs</u></a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.sentio.xyz/docs/entities"><u>entities</u></a>, webhook and http endpoint integrations. The great thing here is that it’s all done in the background and all that’s left are for developers to use a polished product.</p><p>The integrated nature of these features creates a seamless development experience, where monitoring, debugging, and analytics all work together in real-time, making it significantly easier to build and maintain robust cross chain analytics solutions.</p><div class="relative header-and-anchor"><h2 id="h-data-pipeline-process-with-sentio">Data Pipeline Process with Sentio</h2></div><p>This section is an overview that shows an end to end flow from setting up a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/springsui_lsts/tree/main"><u>Sentio processor</u></a> with a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/evandekim/sui-liquid-staking"><u>dune dashboard</u></a> as an end product. To replicate this process, simply fork the repository and add it to your own Sentio project.</p><div class="relative header-and-anchor"><h3 id="h-define-sentio-processor">Define Sentio Processor</h3></div><p>The main entrypoint that Sentio uses is the <code>processor.ts</code> file for instructions on how to index data. Contracts can be added with the command <code>yarn sentio add –chain sui_mainnet contract_address</code>. The abi is generally automatically fetched. Then the command <code>yarn sentio build</code> to generate the typed files.&nbsp;</p><p>A processor can be defined by importing a specific class generated from the typings that contain functions. For example you can CTRL + F for <code>onEvent…</code> to find the event function bindings and click through the code to find the decoded data fields that each event holds as well. Some well prompted AI requests can make this process delightfully easy to set up multiple events from a single contract.&nbsp;</p><div class="relative header-and-anchor"><h3 id="h-sentio-indexing">Sentio Indexing</h3></div><p>Once the processor code has been defined, run the command <code>yarn sentio upload</code> to upload the processor code to Sentio, which will be queued up for indexing. Right out of the box, Sentio is pretty good about automatically finding the best start block point. The indexing (backfilling) process is relatively fast as well. Along the way, additional logs can be implemented into the processor to troubleshoot a data pipeline such as if there is data missing or something is not working the way it should be. These logs will appear in real time when the data gets backfilled.&nbsp;</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8e6577e0de457ccf7ab278521918f5bb.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAUCAIAAABj86gYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEc0lEQVR4nI1Vz1fiVhRm056piDx+JPAgyoM8CZCgUaLESWxRooITHWkAAQ0jtMQhHMeeTlv7Y+Ms7NhNN130zF8wu86qm+67776bbvpf9Jz2JE85Mzrj6Xe+w/nuJbn3ve/dJB7lw5K2pcuqmuN5iqIhhBRFI4QghAAArwMfuIYbesHbEaQoekwmFjs1dv/9+09POidK99f5OVFRVmR5RZIkURQ1bQNjDADgOI50IkAIcRx3uzZFUQgh3+SkG/ldgghNR2jak0omU0nE8/wsxgdNQ5IkWZY1TWNZFmO8t7cnyyuiuKCqKsZYlmXT7ErSkiRJUrFIhFgoYIw3t7bI4tzkEkIJAMC9iQkPQkhWFEHIi8v53HzG6/VdmxB0PfEBAKb8/im/f5yhrkHWTsR4Kzcynkwmq6qrAARd9x1IkuReESQhhLGSC4QQKeF2cqiqKkIpojHGqro6/ve6TdATCodmM2woHAIgGAhQMM7sGs1CYWFcBUKIHKQoih7f6YLWdR3CGABBr9eH8eze3h7P8+6mr+51GuR4wajXVxT1my+/ePHqj89/fFUQhVJpTRQXbjhAXWvXxiuj3mz5BmYS06FwyLO4rOy3DmRFQQhNJ1JxJkFRVCQaHQ8ccBbiDIbP+57PO8Ew09jF7eqvh5FoRCzOxeLQOWRd16VlyZkz70Qw4HcIJgO+ewHf+6GpD2IQJlAqhXP5Qkkslnmed6d56fWpfzcp55BN02QYJplks/wcxy8WimW13NgyRs3ji6OzF/azX85++v3y5V8///bP8PmvGLMx6AChBMMwCCUQSsxMTxM9zhBBUbQHY6zr+v2Ptjd3u3pn9PGjs4Z1bljnnZPn+/blvn3ZPPnesM4bw/PG8Nlq9QAhhNPZWS6XYtNE4HQWp7MkvME4M+0hcwJhzLXbmSQA/DQVCgb8xDE6GIjQYUJidCQKaTpy25BINEro6quajkW2PdK0jWwmK8vkbbHkPHfi4oqiCkJeEPI5XshmsjleICHP89lMNu/qt/7m3WtEcWEGIY8gzH119m2/b5lm7+zsO9s+ffr063bbNM3excUPljX6dDC0rJFljfr9Qb9vjWmavXbbvIOm2RPFBQ8djZZKa7q+4/QsFGLxWLvd4TguFA41W61er2+aXdPsttsd0+z2ev1PBgPLOj44PJxJzEz5p0Lh0J0Me2A8rmkblUpVEPJSsciyrFGv53g+zjC1mtFo7Bv1plFv1mqGru80Gg2SrNWMNMfBeCzOMHfQOYNINCoriqZpOdc1lmUrlSrHcXGGqVSq21W9Uqlubm2RRej6DmGlUmVZ9u7q4wbQqNdNsyuKoqquppJJyzrOC0KEpi3r2LZHj4fDJ08+s6zjwWBg2yPbHjnhcJhKJoOBAHnpv4vOg0ZR9FJRlhUFYzw3P48QWi+XMcYQwvVyWdM2SqU1QhK6W6msl8vkq3c3rnaw/eDBo6Mj0cECQqjR2Ccfst2HDw8ODx8Phycnp+12R9d32u1Os9Vqtlq1mvE/G/wHgcBJ/FiWfzwAAAAASUVORK5CYII=" nextheight="1011" nextwidth="1600" class="image-node embed"><figcaption htmlattributes="[object Object]" class="">data indexing dashboard</figcaption></figure><div class="relative header-and-anchor"><h3 id="h-data-studio-endpoints-and-dune">Data Studio, Endpoints, and Dune</h3></div><p>While Sentio is backfilling data, the project tables get populated in real time, so the query construction work can get started before data has finished backfilling completely. The Data Studio is where exploratory data analytics can be done and queries created for analytics such as daily aggregations, joins, and other transformations.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5ede0503c3132a5478623bf2d67410f4.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAASCAIAAAC1qksFAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEu0lEQVR4nI2TTU8bRxjHt8GCUPOSLI6XejHrlzFe22vCGjOA12ZtZyCzhjHLGNu8LLBJvASchoXUrcoliZSqIqoq9dBbVeVU9Z5bVKmnSP0AveSSb1Gpx1TeLTSVUNOfHo3+mtU+/+d5Zob5/sXL316/1paX641mvdHMF/MT8bhvxMeyLAAT2RkoiomJuCiKiZSUzs5ACOem5Ex2BrorhHOZTHZKzihKwRXZGeh8nR4XwgE+yOxt72xtb09nZJCcKpONwkJRFBM8H0gl4moZ3Tuwj7/4cveepdebs7l8aQkTneYKqlpeyhVUdLui15tLFaIUSkSny6tULSO8TLQVXS0vxeLJXElj1ur1ygq5KctxMblz+Hm5ss5x/kgoeGQ1AQD9Xu9avd5uH2Yy2aXK8mq1qqrFdHoymUoJwrjHc8XjudLX29vX2+tqT5e/N4cGvcEIYOp6ZSoJwmNcLJ6obtwnzVZ6KhsJBR+YNVFMBPjgWr3x9MmTlnUA4awsy8lUKjszAwDgOI5lfU50ueH33/D7Helu+lj22pgQYd78+e7Xt+8+/frnWDxBmi1c2x0XwpFQ8NCkopgIhcKJVGr42oDnPfq9/QMDA8NDw+wHcAx++f2PF6/eHjz9IRZPVmq7pNnKlbR4PHZo0nR6kuNGV9fXT09PFaWgreiUUozxzcmpibgoy/J5yf9psIzUcmE2GhyNxZNabY9s7M/mUQxEHphUkqQAHyQ6tW375OSkquvzOSUSBfO5nCRJHyr/3ODIPqnqNJlKhaOx9a1WYXE5HJ0IhwRza1WSpKAgxJPJfm8/wzAD5/yP1O8ZmKapKHlJkjiO2zG2JSnlGxnheZ6QKoTQz3G11dXjhw/N1v3SEr51q5TLzbPstUvz+UZGfCMjF+L60FA4CroGqqqKoshxnGEYEELegRAymZb8PhbC2Tt3LGPvboAP+3yfBHjBO8heGp7+Qe8g29c3fMVz9eOB7nrdH2AMw8AYux1YlqWqKsdxPM9jjGu7n9lPf/SnNIbpY5irTI/vo0Ef06WHYbyXRI+PYa4zPf5BdrzHO9o7EBhkx5lOp2Oa5oWBK3ier2jazqNvj85ePvrqp+29/VKxmC/Ma/pihW6kpEnhMuAclGUZQthoNFRVpbSG0CJDCLkY0f7+QafTURRFkqTN5qZhGCfHR1vNmmFsqWpxOjOtrdRevXlXM6xQMBgKhS+1cVJThBB1YCRJkmXZLdw0zePjE4wxAIBSur9/2LIOCgtFSmmxXHZ/KxfzGGOEFimlmla5CEIIxpg4KEpeFBOi2H0rjOxwPqIWAGB0dDQSihBSpZRaltVutxFCa2urz549QwgdH5/Yti2K4unpabvdNk0TAIAd3GuCELroBgDQ7cCF47iHR0eNRoPnx9wOKK21HVRVxRg/fvwYIWQ5AABs27YsyzAMQRCQA8uylFL3Hv5jYDhMptPhKCDru/rG3em5QgxE1vBCVaedziPbtlVVJYScnZ1hjG0Ht4NOp2NZliAI7nBYlnWP918G333zXNMqkiRFogDdrqDbFa6Lf4znFEXZ3N6sNxoQwsLCwsHhoaIoVV1vNBoAAMMw3InzPA8hVBSFZVmEkCzL7ktybf4CScRnVFh/gqUAAAAASUVORK5CYII=" nextheight="898" nextwidth="1600" class="image-node embed"><figcaption htmlattributes="[object Object]" class="">data studio</figcaption></figure><p>After the query is ready, endpoints can be created from saved queries in 2 clicks. After the endpoint is created, the data is ready to be used outside of Sentio. In this case, Dune is used as a hosted frontend of choice. An AI connector can be used to craft Dune queries from the Sentio endpoint (see AI Prompt Miscellaneous Section).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d9ae316cd2bbd7f8990b40b8ca7ed010.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTUgRGVjIDIwMjQgMDY6MTA6MzkgUE0gRVNUpQ1eIAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAASgSURBVHic3ZX/TxtlHMcfv0QTk+lgAcYyrEBXDiiU5748ba+0d60HFPApbdlOvhz26LiuPWFlQGvKdMyxaMdWHZyYhZmwFU0F4kyYGmP8yYgx/qw/6Q/+Ef5iYjJzdxmZypZs60++88qT53nucu98nns/zwMEQVAMqaoqSRIsqxBCIBqNFgqFfD5fLBbn5uYIgkBlFQgGgzOzs8lUaur06UwmG5Nl07lsBhiHNG318pUryysry9oHqqqW0QBCCBBCJxUlkUiGwuEJJRmNRsti4PF4MA7pFXAcn0gkJUmSZdmcQsiJyliBJEnf7+4Wixul0mYmky3vT4YQ6hUsLl7M5XKapuXzS8XiRjqdFgQh+NjCGAuCAARBSKfT5j7AGPt5nuM4z35Cxso+QPsWAURxaPeHH69/tP5J6VNJGrM1NdEMQ5JUByRNSJKCENKM/vUHL8i+loBiWH9wJIhf68ZjYiw9prwREhMhMRkSE9ERdTw1L8bSXa/oT0cleenS0jv5S4VCYXHxoqat5vNLCwvn3zp3TtNW375wQRAE7h4ZQx5wnD8ijkVOSN3BiMfX5fF1sV7ByfrdnoCT9Xdy3U6Wd7J+Gnn5wMsYhzAORaNRjEMnRNHoD4bC4Wh0EGPs9fnuheM4N8vqKbq9c+v2zq1U4mQzYaNgO0U6nIg2oGiyw+1k7g5pioQGDop0dLTb91qTvdeciGYokiIdeoqgy1/vlhpcI+zAGWHorCuUZvomj9LiHi+5hs1Og2vE7Ni8cjMffwAtvEz3TXoHs7SbB62uYI/8YVdMK333x29/3tn9/c43v9yJTN14NVMKTlwLTlwLT673KWvD2a3w5HrvxNpodqutO1eHUg3s1L7YfDMvNEvT7/90/du/GloYQHf2OvoyDd4EP3S+N750+t3P5dzGdH5nOr8Tn9+Iz2+kLmydWtjMFL5MLmyeWticLXzlDJ+t9ypNgUkTqy/1X+q9itWXcnmDgGZQO6QpxpWamrl0ZbnO0lhRVXuo+sheW1FVW1l9+EBF9cGqw4eqjxyoqK458mJ947GjlgaT+sZj9Y22e6mzNNrbIHJ30gwC7W3tLqcTIWZubqZYLJ6ZnpZ0jSiKIsvy8OiwJI3IsixJUnx8XFVVURQVZeL44KCiKPHxcUVR7PbWysqDNTXVe1RWHrRYLHZ7K0IMgBBardY6i8XW1EQQzRzH/zNtXiNw+iQfCPCBgDn0eDrNsHt9PpphHBD+i5bWVgeEekxjsnzj5s2t7e2t7W1NW6VI8hHuxftJjynG2Diux2RDwsPLz/P3we/xdAKvzwchtBsiCKK8Nz5nHBWcWUvZhXQD7v9kgBCijKO//AaODgdBEMes1mb9QmbKBcu6dQNIIqu909rKQnewzkaDcupZBrkBxbj4nuP9kVh/JOby9T/xXO0jAJ6uAaBSb5+qAOAZgycBeF4/i+6uWIfdbocQeh5eHMepqjozOxOJDAgDsWT2vWT28uvzV0fV8yTjBuYWexw5IMzlcqVSqa+3l+2Lr33289WN3Y+//vXNlS8IBwK9/f1dPT2PiRFFZ1dPjz/gN/LDQNiBEMI49DdFyOvLugbZdAAAAABJRU5ErkJggg==" nextheight="694" nextwidth="689" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Once the data is in dune, then it can be treated as if it was any other Dune query and the dashboard can be created as usual. Note that the query has to be private to hide the Sentio API keys.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9e7fa21bd0c0ad730a44c0fd712d4bc3.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAUCAIAAABj86gYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTUgRGVjIDIwMjQgMDY6MTE6MzAgUE0gRVNUe8yaywAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAW2SURBVHicnZN/bBNlGMef6B9KQKIGA4lB0WzAIArqdAuMwUA3NIKJEfnhYANhwQV/RGWwoINBjAjMsVEY+wFd6Vi7tV1/cOvWddd157UdV3prb22vrh1DYMMRr1l/cIPd5Mz1tNEETPTNJ2++7/s+eZ983+d5QddpblW1lXx9oKzsSE1NvdPV73SRLtLtIt0E4XKRbqerv4+44qG8/wOfPwAapNPaa5fJVLX1supT9QbErNG3Ix0o0m5p0xqRjm7dpQ6lRoc7iL4r7v8E3udye/3QgVrf2vEtZG6akbf78dxdkL0VVm6b9W7xzLzCmWt3zsjbDSu3wZqdL364VyRlyz6R5M4DSdm494n1n55u7YL2bnRV0WF4dDE8thBmLIbpiwBSYXY6pCyHeekwNwNmpcHc5fDa+7D0PVi8DlLz4PmVsPAdyC6AnB0PZfXHsOyjDXsOgaHTnFv8HUxPh6degqeXCPMjKTAtDdLehHlLBVJegZRMSF0D8zOETNPSAOYJMc9lQW7RQ8krguyCV9duEZ4oPb8UZqbDk0tgTgbMXQGz34Bns2DR27BgFSzIgfkrIHUlpK2Hl3MhbZ1wNCcTnkkX5szNsKJAIDtBUohkbFq6djP0/GTf+En5C7mFqbn5i9ftzsgvWfrBZ8sL9mdt35dVWJJdeCB7e9mqHftyispWFx3M2fVNTlFZVmHJsq1f5hSVrdqx//XNXxQfrmlQm2taTfXq7lpVl0RhrFV1JUTHD9Vnod3YefpM7QX5xTatVi5v7u3FKKHDBnx+v8/vo4RWoxPaT1Femg4kBOXz+3x+2uPx9Hs8NE1r1Jry8vLS0tImudyColKp9MjhI3V1dQThglDomtdPY7jN3ncZt/UFBoNDw9e9flps5MDgUGAw6KG8bs/A0PB1ZatafwkJDA65Kb/XTwcGh27cHL1xc9Tp6sdwG9qDmcxoL97n6HNacQduu+zz02Aw9Gg0yOb8gg2b8ov3fH684uRX+0orqyRV1ZLKKolc3lxVLTl9pvaU5PSJipPmbvSC/GJlVXVt3bljJyp6rL38P4dUduH7o8fbtHrcZmvT6i/IL4JKpRYuqDt37FiFVCo1mcwnKn70+308z09x3F2WneK4f0ccokhu8jxPUZRCoYTuDiNiMDhwHLOgDhxDDAYc651g2XsTLBuPT05MTHGTbDwuEIsKJLR4+mDEmFj03oRwCVxsOHu2stJuNl2xWhokp6zGdk2T7OjBsgbJKY8Dl9ZITpQfapU1Eija0ig1atSaJpm9u/u8RNLSKG1qqHdiVlOb6rxEommS6ZXNHgfeKmvUtyibGuqNGrW7xww4htlsNofdPjw8HAwG701OEgSh1WptNlskEnHYHVqtliRJhmEoiqJpOhaNjo2NWa1WNjE4jiNJkqIojuNYlmUYhqZp8ejeJOc0G8FsMnWZuiwoOhQKBWia53kURZUKhV6ni0WjKIqqVKouk2l0ZESpUDRKpUOhUCwaVSoUYnl4nneTJIqiYs3C4bBGrYlFo2Il4uPhPx04CWJ0ZGQoFOJ5niAII2K0WnrusqzDbm9HECdBhMNhC4q2I4iYoLOzU7yF5/kATTvsdp6/P8VxsWjUgqLJ1picmBAcoKgFw7Dr164H6EDCgUWlUiEIIkYrFQqbzXZ7bEyv07coWrwDA7FoVKVSJROQJGn5y0EkEtFqtUkHQgIHjjvsdiLhIBgM8jyPYZhep7darXdZFsMwjVrjTtSgy2TS63SBRBnaEST5RN4BL4ZhooNwOIyiaDKB0EU21Nxl0PX19nidl38J+G6GfpY3K+TyZl1La8jrJe341VAoEolwHDceiTAMkyjsFMMwyc8Vv3MnEomImuM4hmG4ROLf799n2TvgsFpJJzE2OhIfD8fHx+Pj47/dvj0eZm7f+jXChO/EoomvIJhNfqLkTpK/L6e4SVGM3hoJXA3+AV+VFhwCz6pdAAAAAElFTkSuQmCC" nextheight="856" nextwidth="1378" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><div class="relative header-and-anchor"><h2 id="h-conclusion">Conclusion</h2></div><p>Maintaining robust and observable data pipelines is paramount for the success of blockchain applications. Sentio stands out as a powerful tool for developers and data engineers, providing a seamless, integrated platform that streamlines multi-chain development and boosts real-time data observability.</p><p>By automating complex processes such as historical data backfilling and real-time blockchain streaming, Sentio not only alleviates the technical burden but also accelerates the development lifecycle. Its comprehensive feature set, including strongly typed objects and robust testing suites, empowers teams to build, monitor, and debug their data pipelines with ease and confidence.&nbsp;</p><p>As blockchain technology continues to scale and diversify, data observability tools like Sentio become indispensable for ensuring that data remains reliable, accessible, and actionable.</p><p></p><div class="relative header-and-anchor"><h2 id="h-ai-prompt-miscellaneous">AI Prompt (Miscellaneous)</h2></div><div class="relative header-and-anchor"><h3 id="h-dune-livefetch-query-generator"><strong>Dune LiveFetch Query Generator</strong></h3></div><p><strong>Context:</strong> You are using Dune's LiveFetch functions to execute SQL queries that interact with external APIs. Below is the documentation for the LiveFetch feature, followed by an example endpoint. Use this information to generate SQL queries tailored to specific API endpoints.</p><div class="relative header-and-anchor"><h3 id="h-dune-livefetch-documentation"><strong>Dune LiveFetch Documentation:</strong></h3></div><p><em>Functions and Operators</em></p><p><strong>LiveFetch functions</strong></p><ul><li><p><strong>Description:</strong> LiveFetch functions send HTTP requests to a specified URL, allowing interaction with HTTP servers directly within SQL queries. They can fetch data from external APIs or invoke remote procedure calls.</p></li></ul><p><strong>Supported Functions:</strong></p><ol><li><p><code>http_get(url: varchar) → varchar</code></p></li><li><p><code>http_get(url: varchar, headers: array(varchar)) → varchar</code></p></li><li><p><code>http_post(url: varchar, body: varchar) → varchar</code></p></li><li><p><code>http_post(url: varchar, body: varchar, headers: array(varchar)) → varchar</code></p></li></ol><p><em>Additional Details:</em></p><ul><li><p><strong>Call timeout:</strong> 5 seconds</p></li><li><p><strong>Throttling:</strong> 50 requests per second per proxy</p></li><li><p><strong>Response size limit:</strong> 2 MiB</p></li></ul><p><strong>Request size limit (http_post):</strong> 1,000,000 bytes</p><div class="relative header-and-anchor"><h3 id="h-example-endpoint"><em>Example Endpoint:</em></h3></div><p>You have an API endpoint accessible via the following curl command:</p><p>curl -L -X POST '<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts">https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts</a>' \</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-H 'api-key: api_key_here \</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-H 'Content-Type: application/json' \</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--data-raw '{}'</p><p></p><hr><p><strong>Your Task:</strong></p><p>Generate a Dune SQL query using the LiveFetch functions to interact with the provided API endpoint. Ensure that all necessary headers and the request body are correctly included. If the response is in JSON format, demonstrate how to parse and extract specific fields from the response.</p><div class="relative header-and-anchor"><h3 id="h-api-endpoint-details"><strong>API Endpoint Details:</strong></h3></div><ul><li><p><strong>URL:</strong> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts">https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts</a></p></li><li><p><strong>Method:</strong> POST</p></li><li><p><strong>Headers:</strong></p><ul><li><p>api-key: api_key_here</p></li><li><p>Content-Type: application/json</p></li></ul></li><li><p><strong>Body:</strong> {}</p></li></ul><p><strong>Requirements:</strong></p><ol><li><p>Use http_post to send the POST request.</p></li><li><p>Include the necessary headers.</p></li><li><p>Handle the JSON response to extract relevant data (e.g., list of circulating LSTs).</p></li><li><p>Ensure the query adheres to Dune's LiveFetch limitations.</p></li></ol><div class="relative header-and-anchor"><h3 id="h-example-generated-query"><strong>Example Generated Query:</strong></h3></div><p>SELECT json_parse(</p><p>&nbsp;&nbsp;&nbsp;&nbsp;http_post(</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts">https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts</a>',</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'{}',</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ARRAY[</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'api-key: api_key_here,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'Content-Type: application/json'</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]</p><p>&nbsp;&nbsp;&nbsp;&nbsp;)</p><p>) AS response_data</p><p>To further process the JSON response, you can use Dune's JSON functions. For example:</p><p>SELECT</p><p>&nbsp;&nbsp;&nbsp;&nbsp;json_extract_scalar(response_data, '$.field_name') AS extracted_field</p><p>FROM (</p><p>&nbsp;&nbsp;&nbsp;&nbsp;SELECT json_parse(</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;http_post(</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts">https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts</a>',</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'{}',</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ARRAY[</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'api-key: api_key_here,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'Content-Type: application/json'</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)</p><p>&nbsp;&nbsp;&nbsp;&nbsp;) AS response_data</p><p>)</p><div class="relative header-and-anchor"><h3 id="h-example-query-with-row-extraction-and-filter">Example Query With row extraction and filter:</h3></div><p>WITH response AS (</p><p>&nbsp;&nbsp;&nbsp;&nbsp;SELECT http_post(</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts">https://endpoint.sentio.xyz/evandekim/sui-lst/circulating_lsts</a>',</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'{}',</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ARRAY[</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'api-key: api_key_here,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'Content-Type: application/json'</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]</p><p>&nbsp;&nbsp;&nbsp;&nbsp;) AS resp</p><p>),</p><p>parsed AS (</p><p>&nbsp;&nbsp;&nbsp;&nbsp;SELECT json_parse(resp) AS j&nbsp;</p><p>&nbsp;&nbsp;&nbsp;&nbsp;FROM response</p><p>),</p><p>rows_array AS (</p><p>&nbsp;&nbsp;&nbsp;&nbsp;SELECT json_extract(j, '$.syncSqlResponse.result.rows') AS rows_j</p><p>&nbsp;&nbsp;&nbsp;&nbsp;FROM parsed</p><p>),</p><p>unnested_rows AS (</p><p>&nbsp;&nbsp;&nbsp;&nbsp;SELECT row_data</p><p>&nbsp;&nbsp;&nbsp;&nbsp;FROM rows_array</p><p>&nbsp;&nbsp;&nbsp;&nbsp;CROSS JOIN UNNEST(CAST(rows_j AS array(json))) AS t(row_data)</p><p>)</p><p>SELECT</p><p>&nbsp;&nbsp;&nbsp;&nbsp;json_extract_scalar(row_data, '$.event_date') AS event_date,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;json_extract_scalar(row_data, '$.token_name') AS token_name,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;CAST(json_extract_scalar(row_data, '$.circulating_supply') AS double) AS circulating_supply,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;CAST(json_extract_scalar(row_data, '$.cumulative_minted_conv') AS double) AS cumulative_minted,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;CAST(json_extract_scalar(row_data, '$.cumulative_redeemed_conv') AS double) AS cumulative_redeemed,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;CAST(json_extract_scalar(row_data, '$.circulating_supply_usd') AS double) AS circulating_supply_usd,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;CAST(json_extract_scalar(row_data, '$.cumulative_minted_usd') AS double) AS cumulative_minted_usd,</p><p>&nbsp;&nbsp;&nbsp;&nbsp;CAST(json_extract_scalar(row_data, '$.cumulative_redeemed_usd') AS double) AS cumulative_redeemed_usd</p><p>FROM unnested_rows</p><p>WHERE json_extract_scalar(row_data, '$.token_name') not in ('afsui', 'vsui', 'hasui', 'springsui_lsts', 'spring_sui')</p><p></p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>sentio</category>
            <category>dune</category>
            <category>sui</category>
            <category>data</category>
            <category>pipeline</category>
            <category>lst</category>
            <category>sql</category>
            <category>dashboard</category>
            <category>web3</category>
            <category>datascience</category>
            <category>analytics</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/709de3dca48a166cdc54bd5f6b06d67f.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Streamlining Cross-Chain Queries with Hypermanager
]]></title>
            <link>https://paragraph.com/@evandekim/streamlining-cross-chain-queries-with-hypermanager</link>
            <guid>rtvxYvODdD3ZqsK5P8jX</guid>
            <pubDate>Fri, 01 Nov 2024 02:03:46 GMT</pubDate>
            <description><![CDATA[IntroductionIn today's blockchain ecosystem, querying transaction data across multiple chains has become an essential task for developers seeking to analyze trends, monitor transactions, and build cross-chain applications. Hypermanager enters as a solution to the challenges inherent in cross-chain querying. Built on Hypersync—a client that operates atop Envio's Hyperindex—Hypermanager enables seamless, efficient orchestration of cross-chain log queries across EVM networks. Hypersync provides ...]]></description>
            <content:encoded><![CDATA[<div class="relative header-and-anchor"><h2 id="h-introduction">Introduction</h2></div><p>In today's blockchain ecosystem, querying transaction data across multiple chains has become an essential task for developers seeking to analyze trends, monitor transactions, and build cross-chain applications.  Hypermanager enters as a solution to the challenges inherent in cross-chain querying. Built on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/enviodev/hypersync-client-python">Hypersync</a>—a client that operates atop Envio's <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.envio.dev/docs/HyperIndex/overview">Hyperindex</a>—<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/hypermanager-py/tree/master">Hypermanager</a> enables seamless, efficient orchestration of cross-chain log queries across <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.envio.dev/docs/HyperSync/hypersync-supported-networks">EVM networks</a>.</p><p>Hypersync provides many powerful features, including querying transactions, blocks, logs, and traces and writing results directly to Polars DataFrames, decoding logs, and saving data as parquet files. However, re-using Hypersync queries across different applications can be tedious. Developers often need to repeat the same boilerplate code to fetch blocks, logs, and transactions, leading to redundancy and inefficiency.</p><p>Hypermanager addresses these challenges by serving as a query orchestration tool that enables reusable queries across various applications and multiple chains. By centralizing configurations and reducing boilerplate code, Hypermanager streamlines the process of cross-chain data analysis, offering developers a more efficient and scalable solution. This revised version maintains the core information while providing a bit more context and a smoother transition to the next section about Hypermanager's specific benefits.</p><div class="relative header-and-anchor"><h3 id="h-ease-of-use">Ease of Use</h3></div><p>Hypermanager serves as a powerful query orchestration tool, enabling reusable queries across various applications and multiple chains. By reducing repetitive coding and centralizing configuration, it streamlines workflows and saves developers significant time and effort. </p><p>Developers new to Hypermanager will find the learning curve manageable, especially those already familiar with blockchain queries. The tool abstracts away the complexities of the Hypersync client, allowing users to focus on their data analysis tasks rather than worrying about low-level implementation details.</p><p>Hypermanager's intuitive API, comprehensive documentation, and practical examples accelerate the onboarding process for new users. The significant reduction in boilerplate code not only enhances efficiency but also lowers the entry barrier for developers venturing into blockchain data analysis.</p><div class="relative header-and-anchor"><h2 id="h-the-power-of-hypermanager-efficiency-in-action">The Power of Hypermanager: Efficiency in Action</h2></div><p>The following example illustrates the contrast between using raw Hypersync code and Hypermanager to query blocks and transactions. Without Hypermanager, developers must continuously redefine transaction and block columns within each application, leading to verbose and redundant code.</p><pre data-type="codeBlock" language="python" text="import asyncio
import hypersync
import polars as pl
import time

from hypersync import ColumnMapping, DataType, TransactionField, BlockField, TraceField, TransactionSelection


async def historical_blocks_txs_sync():
    client = hypersync.HypersyncClient(
        hypersync.ClientConfig(url='http://eth.hypersync.xyz')
    )

    to_block: int = 20035000
    from_block: int = 20000000

    # # add +/-1 to the block range because the query is exclusive to the block number
    query = hypersync.Query(
        from_block=from_block-1,
        to_block=to_block+1,
        include_all_blocks=True,
        transactions=[TransactionSelection()],
        field_selection=hypersync.FieldSelection(
            block=[e.value for e in BlockField],
            transaction=[e.value for e in TransactionField],
        )
    )
    config = hypersync.StreamConfig(
        hex_output=hypersync.HexOutput.PREFIXED,
        column_mapping=ColumnMapping(
            transaction={
                TransactionField.GAS_USED: DataType.FLOAT64,
                TransactionField.MAX_FEE_PER_BLOB_GAS: DataType.FLOAT64,
                TransactionField.MAX_PRIORITY_FEE_PER_GAS: DataType.FLOAT64,
                TransactionField.GAS_PRICE: DataType.FLOAT64,
                TransactionField.CUMULATIVE_GAS_USED: DataType.FLOAT64,
                TransactionField.EFFECTIVE_GAS_PRICE: DataType.FLOAT64,
                TransactionField.NONCE: DataType.INT64,
                TransactionField.GAS: DataType.FLOAT64,
                TransactionField.MAX_FEE_PER_GAS: DataType.FLOAT64,
                TransactionField.MAX_FEE_PER_BLOB_GAS: DataType.FLOAT64,
                TransactionField.VALUE: DataType.FLOAT64,
            },
            block={
                BlockField.GAS_LIMIT: DataType.FLOAT64,
                BlockField.GAS_USED: DataType.FLOAT64,
                BlockField.SIZE: DataType.FLOAT64,
                BlockField.BLOB_GAS_USED: DataType.FLOAT64,
                BlockField.EXCESS_BLOB_GAS: DataType.FLOAT64,
                BlockField.BASE_FEE_PER_GAS: DataType.FLOAT64,
                BlockField.TIMESTAMP: DataType.INT64,
            }
        )
    )
	
	# save data into parquet files in &quot;data&quot; folder
    return await client.collect_parquet(&quot;data&quot;, query, config)

"><code><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> hypersync
<span class="hljs-keyword">import</span> polars <span class="hljs-keyword">as</span> pl
<span class="hljs-keyword">import</span> time

<span class="hljs-keyword">from</span> hypersync <span class="hljs-keyword">import</span> ColumnMapping, DataType, TransactionField, BlockField, TraceField, TransactionSelection


<span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">historical_blocks_txs_sync</span>():
    client = hypersync.HypersyncClient(
        hypersync.ClientConfig(url=<span class="hljs-string">'http://eth.hypersync.xyz'</span>)
    )

    to_block: <span class="hljs-built_in">int</span> = <span class="hljs-number">20035000</span>
    from_block: <span class="hljs-built_in">int</span> = <span class="hljs-number">20000000</span>

    <span class="hljs-comment"># # add +/-1 to the block range because the query is exclusive to the block number</span>
    query = hypersync.Query(
        from_block=from_block-<span class="hljs-number">1</span>,
        to_block=to_block+<span class="hljs-number">1</span>,
        include_all_blocks=<span class="hljs-literal">True</span>,
        transactions=[TransactionSelection()],
        field_selection=hypersync.FieldSelection(
            block=[e.value <span class="hljs-keyword">for</span> e <span class="hljs-keyword">in</span> BlockField],
            transaction=[e.value <span class="hljs-keyword">for</span> e <span class="hljs-keyword">in</span> TransactionField],
        )
    )
    config = hypersync.StreamConfig(
        hex_output=hypersync.HexOutput.PREFIXED,
        column_mapping=ColumnMapping(
            transaction={
                TransactionField.GAS_USED: DataType.FLOAT64,
                TransactionField.MAX_FEE_PER_BLOB_GAS: DataType.FLOAT64,
                TransactionField.MAX_PRIORITY_FEE_PER_GAS: DataType.FLOAT64,
                TransactionField.GAS_PRICE: DataType.FLOAT64,
                TransactionField.CUMULATIVE_GAS_USED: DataType.FLOAT64,
                TransactionField.EFFECTIVE_GAS_PRICE: DataType.FLOAT64,
                TransactionField.NONCE: DataType.INT64,
                TransactionField.GAS: DataType.FLOAT64,
                TransactionField.MAX_FEE_PER_GAS: DataType.FLOAT64,
                TransactionField.MAX_FEE_PER_BLOB_GAS: DataType.FLOAT64,
                TransactionField.VALUE: DataType.FLOAT64,
            },
            block={
                BlockField.GAS_LIMIT: DataType.FLOAT64,
                BlockField.GAS_USED: DataType.FLOAT64,
                BlockField.SIZE: DataType.FLOAT64,
                BlockField.BLOB_GAS_USED: DataType.FLOAT64,
                BlockField.EXCESS_BLOB_GAS: DataType.FLOAT64,
                BlockField.BASE_FEE_PER_GAS: DataType.FLOAT64,
                BlockField.TIMESTAMP: DataType.INT64,
            }
        )
    )
	
	<span class="hljs-comment"># save data into parquet files in "data" folder</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> client.collect_parquet(<span class="hljs-string">"data"</span>, query, config)

</code></pre><p><strong>Efficiency Gains with Hypermanager:</strong></p><p>By using Hypermanager, the same query can be completed in about 10 lines of code, reducing boilerplate by 6x and greatly improving readability:</p><pre data-type="codeBlock" language="python" text="import asyncio
import polars as pl

from hypermanager.manager import HyperManager

hypersync_client: str = &quot;https://eth.hypersync.xyz&quot;

async def get_events():
    manager = HyperManager(url=hypersync_client)

    df: pl.DataFrame = await manager.get_blocks(
        from_block=20000000, to_block=20035000, save_data=True
    )

if __name__ == &quot;__main__&quot;:
    asyncio.run(get_events())"><code><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> polars <span class="hljs-keyword">as</span> pl

<span class="hljs-keyword">from</span> hypermanager.manager <span class="hljs-keyword">import</span> HyperManager

hypersync_client: <span class="hljs-built_in">str</span> = <span class="hljs-string">"https://eth.hypersync.xyz"</span>

<span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_events</span>():
    manager = HyperManager(url=hypersync_client)

    df: pl.DataFrame = <span class="hljs-keyword">await</span> manager.get_blocks(
        from_block=<span class="hljs-number">20000000</span>, to_block=<span class="hljs-number">20035000</span>, save_data=<span class="hljs-literal">True</span>
    )

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    asyncio.run(get_events())</code></pre><p>With Hypermanager, developers no longer need to manually define columns or repeatedly initialize data maps. The tool handles all standard configurations, allowing users to focus more on data analysis and customizing the data pipeline than setting all up.</p><div class="relative header-and-anchor"><h2 id="h-streamlined-event-decoding">Streamlined Event Decoding</h2></div><p>When using Hypersync directly, developers must define and decode each event signature individually. Below is an example where each event signature is defined in Hypersync:</p><pre data-type="codeBlock" language="python" text="import hypersync
import asyncio


# Define events (matching the structure from the second code block)
event_1 = (
    &quot;V3FundsDeposited(address inputToken,address outputToken,uint256 inputAmount,&quot;
    &quot;uint256 outputAmount,uint256 indexed destinationChainId,uint32 indexed depositId,&quot;
    &quot;uint32 quoteTimestamp,uint32 fillDeadline,uint32 exclusivityDeadline,&quot;
    &quot;address indexed depositor,address recipient,address exclusiveRelayer,bytes message)&quot;
)
event_2 = (
    &quot;RequestedSpeedUpV3Deposit(uint256 updatedOutputAmount,uint32 indexed depositId,&quot;
    &quot;address indexed depositor,address updatedRecipient,bytes updatedMessage,&quot;
    &quot;bytes depositorSignature)&quot;
)
event_3 = (
    &quot;FilledV3Relay(address inputToken,address outputToken,uint256 inputAmount,&quot;
    &quot;uint256 outputAmount,uint256 repaymentChainId,uint256 indexed originChainId,&quot;
    &quot;uint32 indexed depositId,uint32 fillDeadline,uint32 exclusivityDeadline,&quot;
    &quot;address exclusiveRelayer,address indexed relayer,address depositor,&quot;
    &quot;address recipient,bytes message,V3RelayExecutionEventInfo relayExecutionInfo)&quot;
)
event_4 = (
    &quot;RequestedV3SlowFill(address inputToken,address outputToken,uint256 inputAmount,&quot;
    &quot;uint256 outputAmount,uint256 indexed originChainId,uint32 indexed depositId,&quot;
    &quot;uint32 fillDeadline,uint32 exclusivityDeadline,address exclusiveRelayer,&quot;
    &quot;address depositor,address recipient,bytes message)&quot;
)

# List of events
events_set = [event_1, event_2, event_3, event_4]

# Hypersync client configuration
hypersync_client_url = &quot;https://base.hypersync.xyz&quot;
contract = &quot;0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64&quot;


async def main():
    # Create Hypersync client using the provided base URL
    client = hypersync.HypersyncClient(hypersync.ClientConfig(url=hypersync_client_url))

    # Lowercase the smart contract address
    smart_contract = contract.lower()

    # Iterate over the list of events
    for event in events_set:
        try:
            # Convert event signature to topic0 (hash of the event signature)
            topic = hypersync.signature_to_topic0(event)

            # Prepare the query to fetch logs for the event
            query = hypersync.preset_query_logs_of_event(
                smart_contract, topic, from_block=19_500_000
            )

            print(&quot;Running the query for event...&quot;)

            # Run the query and fetch the logs
            res = await client.get(query)

            # Check if logs were returned
            if len(res.data.logs) == 0:
                print(&quot;No logs found for event&quot;)
                continue

            # Process the logs (print the log count)
            print(f&quot;Query returned {len(res.data.logs)} logs for event&quot;)

        except Exception as e:
            print(f&quot;Error querying event, error: {e}&quot;)
            continue  # Continue to the next event


# Run the asynchronous main function
asyncio.run(main())
"><code><span class="hljs-keyword">import</span> hypersync
<span class="hljs-keyword">import</span> asyncio


<span class="hljs-comment"># Define events (matching the structure from the second code block)</span>
event_1 = (
    <span class="hljs-string">"V3FundsDeposited(address inputToken,address outputToken,uint256 inputAmount,"</span>
    <span class="hljs-string">"uint256 outputAmount,uint256 indexed destinationChainId,uint32 indexed depositId,"</span>
    <span class="hljs-string">"uint32 quoteTimestamp,uint32 fillDeadline,uint32 exclusivityDeadline,"</span>
    <span class="hljs-string">"address indexed depositor,address recipient,address exclusiveRelayer,bytes message)"</span>
)
event_2 = (
    <span class="hljs-string">"RequestedSpeedUpV3Deposit(uint256 updatedOutputAmount,uint32 indexed depositId,"</span>
    <span class="hljs-string">"address indexed depositor,address updatedRecipient,bytes updatedMessage,"</span>
    <span class="hljs-string">"bytes depositorSignature)"</span>
)
event_3 = (
    <span class="hljs-string">"FilledV3Relay(address inputToken,address outputToken,uint256 inputAmount,"</span>
    <span class="hljs-string">"uint256 outputAmount,uint256 repaymentChainId,uint256 indexed originChainId,"</span>
    <span class="hljs-string">"uint32 indexed depositId,uint32 fillDeadline,uint32 exclusivityDeadline,"</span>
    <span class="hljs-string">"address exclusiveRelayer,address indexed relayer,address depositor,"</span>
    <span class="hljs-string">"address recipient,bytes message,V3RelayExecutionEventInfo relayExecutionInfo)"</span>
)
event_4 = (
    <span class="hljs-string">"RequestedV3SlowFill(address inputToken,address outputToken,uint256 inputAmount,"</span>
    <span class="hljs-string">"uint256 outputAmount,uint256 indexed originChainId,uint32 indexed depositId,"</span>
    <span class="hljs-string">"uint32 fillDeadline,uint32 exclusivityDeadline,address exclusiveRelayer,"</span>
    <span class="hljs-string">"address depositor,address recipient,bytes message)"</span>
)

<span class="hljs-comment"># List of events</span>
events_set = [event_1, event_2, event_3, event_4]

<span class="hljs-comment"># Hypersync client configuration</span>
hypersync_client_url = <span class="hljs-string">"https://base.hypersync.xyz"</span>
contract = <span class="hljs-string">"0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64"</span>


<span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">main</span>():
    <span class="hljs-comment"># Create Hypersync client using the provided base URL</span>
    client = hypersync.HypersyncClient(hypersync.ClientConfig(url=hypersync_client_url))

    <span class="hljs-comment"># Lowercase the smart contract address</span>
    smart_contract = contract.lower()

    <span class="hljs-comment"># Iterate over the list of events</span>
    <span class="hljs-keyword">for</span> event <span class="hljs-keyword">in</span> events_set:
        <span class="hljs-keyword">try</span>:
            <span class="hljs-comment"># Convert event signature to topic0 (hash of the event signature)</span>
            topic = hypersync.signature_to_topic0(event)

            <span class="hljs-comment"># Prepare the query to fetch logs for the event</span>
            query = hypersync.preset_query_logs_of_event(
                smart_contract, topic, from_block=<span class="hljs-number">19_500_000</span>
            )

            <span class="hljs-built_in">print</span>(<span class="hljs-string">"Running the query for event..."</span>)

            <span class="hljs-comment"># Run the query and fetch the logs</span>
            res = <span class="hljs-keyword">await</span> client.get(query)

            <span class="hljs-comment"># Check if logs were returned</span>
            <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(res.data.logs) == <span class="hljs-number">0</span>:
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"No logs found for event"</span>)
                <span class="hljs-keyword">continue</span>

            <span class="hljs-comment"># Process the logs (print the log count)</span>
            <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Query returned <span class="hljs-subst">{<span class="hljs-built_in">len</span>(res.data.logs)}</span> logs for event"</span>)

        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Error querying event, error: <span class="hljs-subst">{e}</span>"</span>)
            <span class="hljs-keyword">continue</span>  <span class="hljs-comment"># Continue to the next event</span>


<span class="hljs-comment"># Run the asynchronous main function</span>
asyncio.run(main())
</code></pre><p>Something like the below. Plus the transactions and blocks decoding too. </p><pre data-type="codeBlock" language="python" text="       decoded_log={
                &quot;inputAmount&quot;: DataType.FLOAT64,
                &quot;outputAmount&quot;: DataType.FLOAT64,
                &quot;quoteTimestamp&quot;: DataType.INT64,
                &quot;fillDeadline&quot;: DataType.UINT64,
                &quot;exclusivityDeadline&quot;: DataType.INT64,
                &quot;destinationChainId&quot;: DataType.UINT64,
                &quot;depositId&quot;: DataType.UINT64,
            },"><code>       decoded_log={
                <span class="hljs-string">"inputAmount"</span>: DataType.FLOAT64,
                <span class="hljs-string">"outputAmount"</span>: DataType.FLOAT64,
                <span class="hljs-string">"quoteTimestamp"</span>: DataType.INT64,
                <span class="hljs-string">"fillDeadline"</span>: DataType.UINT64,
                <span class="hljs-string">"exclusivityDeadline"</span>: DataType.INT64,
                <span class="hljs-string">"destinationChainId"</span>: DataType.UINT64,
                <span class="hljs-string">"depositId"</span>: DataType.UINT64,
            },</code></pre><p>This setup requires manual event signature definitions and individual data type configurations for each event, increasing development time and complexity.</p><p><strong>Hypermanager’s Simplified Decoding Process:</strong></p><p>Hypermanager centralizes event decoding, enabling developers to query events across multiple chains without needing to redefine them. This simplifies cross-chain queries, as the configuration only needs to be written once. The following Hypermanager example demonstrates querying multiple events with pre-loaded decoded values:</p><pre data-type="codeBlock" language="python" text="import asyncio
import os
import polars as pl
from hypermanager.events import EventConfig
from hypermanager.manager import HyperManager
from hypermanager.protocols.across import (
    client_config,
    across_config,
)

async def get_events():
    # Iterate through the different HyperSync clients and their associated SpokePool addresses
    for client, spoke_pool_address in client_config.items():
        print(f&quot;Querying events for {client.name}...&quot;)
        print(f&quot;SpokePool Address: {spoke_pool_address.value}&quot;)

        # **Modified Loop**: Iterate over the values of the across_config dictionary
        for base_event_config in across_config.values():
            try:
                manager = HyperManager(url=client.client)

                # Query events using the event configuration and return the result as a Polars DataFrame
                df: pl.DataFrame = await manager.execute_event_query(
                    base_event_config,
                    tx_data=True,
                    block_range=10_000,  # query the most recent 10,000 blocks from each chain
                )

                # Check if any events were found
                if df.is_empty():
                    print(
                        f&quot;No events found for {base_event_config.name} on {client.name}, continuing...&quot;
                    )
                    continue  # Move to the next event if none found


                print(f&quot;Events found for {base_event_config.name} on {client.name}:&quot;)
                print(df.shape)  


            except Exception as e:
                print(f&quot;Error querying {base_event_config.name} on {
                      client.name}: {e}&quot;)


if __name__ == &quot;__main__&quot;:
    asyncio.run(get_events())
"><code><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> polars <span class="hljs-keyword">as</span> pl
<span class="hljs-keyword">from</span> hypermanager.events <span class="hljs-keyword">import</span> EventConfig
<span class="hljs-keyword">from</span> hypermanager.manager <span class="hljs-keyword">import</span> HyperManager
<span class="hljs-keyword">from</span> hypermanager.protocols.across <span class="hljs-keyword">import</span> (
    client_config,
    across_config,
)

<span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_events</span>():
    <span class="hljs-comment"># Iterate through the different HyperSync clients and their associated SpokePool addresses</span>
    <span class="hljs-keyword">for</span> client, spoke_pool_address <span class="hljs-keyword">in</span> client_config.items():
        <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Querying events for <span class="hljs-subst">{client.name}</span>..."</span>)
        <span class="hljs-built_in">print</span>(<span class="hljs-string">f"SpokePool Address: <span class="hljs-subst">{spoke_pool_address.value}</span>"</span>)

        <span class="hljs-comment"># **Modified Loop**: Iterate over the values of the across_config dictionary</span>
        <span class="hljs-keyword">for</span> base_event_config <span class="hljs-keyword">in</span> across_config.values():
            <span class="hljs-keyword">try</span>:
                manager = HyperManager(url=client.client)

                <span class="hljs-comment"># Query events using the event configuration and return the result as a Polars DataFrame</span>
                df: pl.DataFrame = <span class="hljs-keyword">await</span> manager.execute_event_query(
                    base_event_config,
                    tx_data=<span class="hljs-literal">True</span>,
                    block_range=<span class="hljs-number">10_000</span>,  <span class="hljs-comment"># query the most recent 10,000 blocks from each chain</span>
                )

                <span class="hljs-comment"># Check if any events were found</span>
                <span class="hljs-keyword">if</span> df.is_empty():
                    <span class="hljs-built_in">print</span>(
                        <span class="hljs-string">f"No events found for <span class="hljs-subst">{base_event_config.name}</span> on <span class="hljs-subst">{client.name}</span>, continuing..."</span>
                    )
                    <span class="hljs-keyword">continue</span>  <span class="hljs-comment"># Move to the next event if none found</span>


                <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Events found for <span class="hljs-subst">{base_event_config.name}</span> on <span class="hljs-subst">{client.name}</span>:"</span>)
                <span class="hljs-built_in">print</span>(df.shape)  


            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
                <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Error querying <span class="hljs-subst">{base_event_config.name}</span> on <span class="hljs-subst">{
                      client.name}</span>: <span class="hljs-subst">{e}</span>"</span>)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    asyncio.run(get_events())
</code></pre><p>With Hypermanager, events, transactions, and blocks can all be queried with decoded values already <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/hypermanager-py/blob/master/src/hypermanager/protocols/across.py">pre-loaded</a>. This eliminates the need for repetitive event definitions and custom data type decoding across applications.</p><div class="relative header-and-anchor"><h3 id="h-centralized-configuration-and-reusability">Centralized Configuration and Reusability</h3></div><p>One of Hypermanager’s core strengths is its centralized <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/hypermanager-py/tree/master/src/hypermanager/protocols">data decoding setup.</a> Once configured, the event decoding information is reusable across queries Hypermanager not only reduces setup time but also makes cross-chain querying more efficient and scalable.</p><p>In summary, Hypermanager’s orchestration abilities streamline the complexity of cross-chain log queries by centralizing configurations, reducing boilerplate, and enabling developers to quickly reuse queries across applications. With Hypermanager, the barrier to efficient cross-chain analysis is lowered, making it a vital tool for anyone working with blockchain data.</p><p>Hypermanager's centralized log repository is a collaborative effort, and developers are encouraged to contribute new logs to expand its coverage and utility. By submitting pull requests with new log configurations, developers can help grow the repository, benefiting the entire community.</p><div class="relative header-and-anchor"><h3 id="h-hyperemanager-duckdb-example">Hyperemanager + DuckDB Example:</h3></div><p>To demonstrate the practicality and ease of integrating Hypermanager into a real-world data pipeline, I developed a backend MVP focused on key log events from the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.primev.xyz/get-started/welcome-to-primev">mev-commit chain</a>. This proof of concept showcases the seamless integration of Hypermanager, Polars, and DuckDB, highlighting the efficiency and ergonomics of building a data pipeline for querying and storing sparse events from the mev-commit chain. </p><p>The data pipeline architecture consists of the following components:</p><ol><li><p><strong>Hypermanager</strong></p></li><li><p><strong>Polars</strong></p></li><li><p><strong>DuckDB</strong></p></li></ol><p>By leveraging Hypermanager, the data pipeline benefits from simplified cross-chain querying and reduced boilerplate code. Hypermanager's centralized event decoding allows for seamless retrieval of log events from the mev-commit chain without the need for repetitive event definitions.</p><p>The retrieved log data is then processed using Polars, taking advantage of its high-performance DataFrame operations. Polars' integration with DuckDB enables efficient storage and querying of the processed data within the embedded OLAP database. To streamline the deployment and testing process, I dockerized the data pipeline using Docker Compose. This allows for easy setup and execution of the pipeline with a single command: <code>docker-compose up --build db</code>. </p><p></p><p></p><p></p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>hypersync</category>
            <category>envio</category>
            <category>python</category>
            <category>polars</category>
            <category>on-chain</category>
            <category>query</category>
            <category>hypermanager</category>
        </item>
        <item>
            <title><![CDATA[Clustering Analysis on Zeta <> Pyth Transaction Logs]]></title>
            <link>https://paragraph.com/@evandekim/clustering-analysis-on-zeta-lessgreater-pyth-transaction-logs</link>
            <guid>vpdKkFr1sU3sfxOsUwTu</guid>
            <pubDate>Sun, 18 Aug 2024 20:53:51 GMT</pubDate>
            <description><![CDATA[IntroPrevious research looked at transaction faliure rates on Solana and identified higher than average failure rates with the Pyth pull ...]]></description>
            <content:encoded><![CDATA[<p></p><div class="relative header-and-anchor"><h2 id="h-intro">Intro</h2></div><p>Previous research looked at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/economic-costs-of-failed-transactions-on-solana">transaction faliure rates</a> on Solana and identified higher than average failure rates with the Pyth pull oracle contracts. Specifically the signer contract Zeta seems to be failing with many of its price updates, leading to upwards of 20 SOL a week in lost fees from submitting failed transactions. </p><p>This post will follow up, examining the on-chain sample data (~1000 blocks is the data cap) more closely with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://pola.rs/">Polars DataFrame</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://scikit-learn.org/stable/modules/generated/sklearn.cluster.AgglomerativeClustering.html">Agglomerative Clustering</a> to analyze log message patterns.</p><p>Data is abstracted using the Clickhouse python client from Cryptohouse. Once extracted, data is transformed via Polars. Polars allows for complex data transformations and powerful exploratory analysis by structuring data in DataFrames. It excels at handling arrays, unnesting columns, reusing transformations, and supports ad-hoc visualizations—all within a single notebook.</p><p>The notebook for all of the code <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/pyth_analysis/blob/main/cryptohouse_historical.ipynb">can be found here</a>. </p><div class="relative header-and-anchor"><h2 id="h-jito-blocks">Jito Blocks</h2></div><p>Compared to the average 8% transaction failure rate on Solana, transactions that utilize Jito have <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://crypto.clickhouse.com?query=V0lUSCBtYXhfaW5kZXhfcGVyX3Nsb3QgQVMgKAogICAgU0VMRUNUCiAgICAgICAgYmxvY2tfc2xvdCwKICAgICAgICBNQVgoaW5kZXgpIEFTIGJsb2NrX3NpemUKICAgIEZST00KICAgICAgICBzb2xhbmEudHJhbnNhY3Rpb25zCiAgICBHUk9VUCBCWQogICAgICAgIGJsb2NrX3Nsb3QKKQpTRUxFQ1QgCiAgICBzdGF0dXMsCiAgICBDT1VOVCgqKSBBUyBzdGF0dXNfY291bnQsCiAgICBBVkcoaW5kZXgpIEFTIGF2Z19pbmRleCwKICAgIG1lZGlhbihpbmRleCkgQVMgbWVkaWFuX2luZGV4LAogICAgQVZHKG1heF9pbmRleF9wZXJfc2xvdC5ibG9ja19zaXplKSBBUyBhdmdfYmxvY2tfc2l6ZQpGUk9NCiAgICBzb2xhbmEudHJhbnNhY3Rpb25zCkpPSU4KICAgIG1heF9pbmRleF9wZXJfc2xvdApPTgogICAgc29sYW5hLnRyYW5zYWN0aW9ucy5ibG9ja19zbG90ID0gbWF4X2luZGV4X3Blcl9zbG90LmJsb2NrX3Nsb3QKV0hFUkUKICAgIGFycmF5RXhpc3RzKAogICAgICAgIHggLT4geC4xIElOIFsKICAgICAgICAgICAgJzk2Z1laR0xuSllWRm1ianpvcFBTVTZRaUVWNWZHcVpOeU45bm1OaHZyWlU1JywKICAgICAgICAgICAgJ0hGcVU1eDYzVlRxdlFzczhocDExaTR3VlY4YkQ0NFB2d3VjZloyYlU3Z1JlJywKICAgICAgICAgICAgJ0N3OENGeU05RmtvTWk3SzdDcmY2SE5RcWY0dUVNenBLdzZRTmdoWEx2TGtZJywKICAgICAgICAgICAgJ0FEYVVNaWQ5eWZVeXRxTUJnb3B3amIyRFRMU29rVFN6TDF6dDZpR1BhUzQ5JywKICAgICAgICAgICAgJ0RmWHlnU200akN5TkN5YlZZWUs2RHd2V3FqS2VlOHBiRG1KR2NMV05EWGpoJywKICAgICAgICAgICAgJ0FEdVVrUjR2cUxVTVdYeFc5Z2g2RDZMOHBNU2F3aW1jdGNOWjVwR3dEY0V0JywKICAgICAgICAgICAgJ0R0dFdhTXVWdlRpZHVaUm5ndUxGN2pOeFRnaU1CWjFoeUF1bUtVaUwyS1JMJywKICAgICAgICAgICAgJzNBVmk5VGc5VW82OHRKZnV2b0t2cUtOV0trQzV3UGRTU2RlQm5pektaNmpUJwogICAgICAgIF0sCiAgICAgICAgYWNjb3VudHMKICAgICkKICAgIEFORCBzb2xhbmEudHJhbnNhY3Rpb25zLmJsb2NrX3Nsb3QgPiAyODI2MDAwMDAKICAgIEFORCBzb2xhbmEudHJhbnNhY3Rpb25zLmJsb2NrX3Nsb3QgPCAyODI5MDAwMDAKR1JPVVAgQlkKICAgIHN0YXR1cwo">much lower failure rates, around 0.8%</a>. The majority of Zeta transactions do not go in Jito bundles. When using Jito, the two charts below show that the jito transactions experience an overall lower failure rate.</p><figure float="none" width="548px" data-type="figure" class="img-center" style="max-width: 548px;"><img src="https://storage.googleapis.com/papyrus_images/76cc26f8cde536ea713c29e0a98736f3.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAcCAIAAACPoCp1AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTggQXVnIDIwMjQgMDM6NTc6MDYgUE0gRURUw+1HjwAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAcwSURBVHichZZ9UBP5Gccf22vn/ujNdM7Oeef16lBrnRPBOysCx13VIoiO+AJzDNzRgyKgqQPlRcXDUtCDQw6UGOMLLxVMGlAmgUE4byIXSBFcTFhDcAmElGRhIUSW5Za1MTbEbGezIeJb+0zym9/b83z2+/3tb2aB9QTDMCRJUp6Y++EH5tF/+D5N0ziOt7a2ikQiqVRaU1Mjk8kaGxuveKK+vr6qqgpBkKXpvpibm6MoCniA2Wy2Wq189QeT+Chyi6K4ZT5omn6u89IhH7MUNU/Tmn6dorm15/ZtcLlcCIIYDAaXy+V+8oRl2bGe1mN+XvCrw+35vSRcLhfLsqfFtfmlwgG0nyvkcDgIgnA6nTzApFZkLf+/gFcGX2RyQD2u7bhvGOYKuVyuZwHyY35AoJ1VcX6+hCX5CyzLDihEqjOCxSEnZd5qeTgzwW/x/lmWIAjOIrVavdSi0c6mvNUwrJTmvA0vAlwLTpZl5bkRZVs9D7fgdC1wyPqUDU3Z4b4NLMs+mps2j2BeK6xWq0fBgg8w2tn05dpXAlpP7BNFv7EI4Gaq4vyk6UHcjPMxn3IhZsX1/M+5Ekaj8UUFo52Neau9z+gz17WwwA9b8qKFO1/nVxceO1iWrU1cIxOE8jN8nbKtcDVrF5AkaeJi1M2ydmrapJY/B3A/q4Av5wMsPHbwFtV8tnoJgJupiARpzl5Qq9VOp3PaOuVyuy1Ie/ZbnDnH18DI97LCD2Fc29EgCPPaiPWg1yr5vg/Avda9bbNj9+uS18sEIUstKt8OkpxoQBCku7t7CLvvZlm877u81WBQyQ9xh3z1ZBCg1yqP/NJ7Th3lB4tDYRq7g7XVtp+Mvxi7clgp0f6jTHJgo0zw0bWMbdK0TbyCh+SU2wdQqVQkSeK4xeV243eVR1eBtL0TYP2QskHwAehbxPnvewFdosyLsSuVpclnIn90q+xAdfyqGwUxdcnr5LlRTdnhjRmhkrQAfueFmBU91XkXY96WZO8CpVJJ0/QkQfgANW3dAB/WKJTw5scDzUIesPD4UZco83z0m12izMtxK5uLBeWf+n9XkigTfCTPjbj+l0+Op8YeS4jgb0DZVugoP3g57j3ukI1Go88iC3Lz6Cq40t4NEHBJ3gHL/6CTlxeHwoBCdHbHT/vqikTRb3RWZohjfhKWLYSQpJvF+xsEYfLcHdezN8OeAtjBXT2n230mElSVaRAcH5542Ct/kpjwKfAAAi8rOuAX23iAtqGsOBTuSk4tAn68NksMwak3i/csAoJh7wmIOsgDKraDqjIFgtL9E3K4m8y9pMYR/pBfCkCvlZ/e6gWozmWJY14PzBJBcMoSwGbYmw9R6c8CUgITsjiA0Wj0Al6hoL/xm9O/5wE/V1UKxDHgzylI4S1qyo3yKMjnFTiePKnYvoyzKOhAYEKO1yKCIFiW/VaNALzzHOCrENA2lJRyCr46t/s14dcFgkj/kGwhb5H04Po1cdmRn6XDvlyI8p5BeTioziZBUKoXgGHYIIaxLNukRgECngKWb7snLz0VAtK6ms3r/O5KTp3bvWzDsVrYGB+dW+EBxEkPbYLYIog9umx/NuzJbFFrFYghactv677OgaDkgIRsDmC32y04zrKsggOs8wA2XFJ8D29tUcjqQta+KzjfAu/tKhLXw8ad+/96ATbF7zxyFsK+iMwsfS36z+/G5y3/NBf2Zf3mjycgPO136Sfh4z9FHBFCcGLg50e9Fg0ZDCbs3pUbXQCrhLJ2gF+flrQBBAjOyACCDpfVws82JZ6sgV9FhGeUwpqdQamF4L93XVIBfJIEuwQQmQbhqbD7MGxJXp9UAP57wtKLYO3uFdGHvAruIEhh4d9u96HxOSVdd7RffFl+659IepHw286erJLzLUp1Rom4qV114lzd35vaC8WS6sbWU5dkYomi4krTN9UNwqvNJZek4oYbxRck9c3K4+VVsraOvPLqmus3vArsdvsghjHM/KB+4N8PHw4PG0hyZnjYMDdHmUZHSHJm1GicJCYmiQmzeWxifNxiHrNap3CzeZacsZjH5mlaP3BvliSxQT1N0wM6lCs1qJ+bW/yqYBhGo9HMM0yfRkvTtLYfJUlyQK9/MDOjv49N22xDhmGz2TJq+teY2TxmtgybTBZ8fHhkZHJqCsOGpm02fjOKorMUpe3vp2m6T6N9MDPzFICiKMMwOp2Opmm9Xk9RFN9iGEaSpNFoxHHc7AmCIEwmk9VqNRqNNpvNYDCQJIlhGJ9C07ROp+NLPf0uoigKRVF+jQfwOTZPPl/LB8Bx3GQyEQTxvwEoytnwFNDd3U3TdG9vL0VRCILYbDaNRmO1WlEUJQhCr9cbF8NsNmMYhuO4Xq8nCEKn01mtVo1GY7PZEAShKKq3t5cvZbPZvACn00mSpK+lKMrhcCxtaZpmGMZut6Moqlar7XY7wzA0Tdvt9hc3+0o5HI7/An2/eBoHtyp6AAAAAElFTkSuQmCC" nextheight="751" nextwidth="845" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><figure float="none" width="580px" data-type="figure" class="img-center" style="max-width: 580px;"><img src="https://storage.googleapis.com/papyrus_images/cd8908cab80f8485974aeb2b1629d0bb.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTggQXVnIDIwMjQgMDM6NTQ6NTAgUE0gRURU1vdtRgAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAU0SURBVHictZZ/TFNXFMfPkqlEUWAsQuJ0w58x4n5pJ+LYD42bm5txOn9NISoTNxO2WbMakkVFEdmsVnlAoRde7S29pQ9uyWNVZgyOGYxEModTaGeG0+ESsU42QV7hAXd5vYhTs+li9/3j5PXde87nfE/ve3nAGFNVVVEUHjvuk6IohBCDwZCZmZmVlaXX6wVByMzM1Ov1GRkZlFJVVdvb2+9PDOZ2AWNMUZTDVVWHilFNTc1PPp/3PtXX18uyLEkSjx6Ph0dKaU1Njfcf5PP5TtXXQ7eqXvA1p23ZNntR8qmTJ7kh9mjqC1Zoqqve+v4s6A4ETp85u+OzDTmb1zY1exnrVx9ZAaWrj7Ga0oPzYkAbUUdHR9P5cz2BLkVRWCjUG3RwQrIsjLsN8Hq9ISl9N6BIAyiKUknpxStXKw9mXGg4MTjBkAEYY81N59tudqa/FlPrzP9fADeu+9tudhrejqujKPQASunlS5f+VAKrkyZXu6yhBSwaD4AxLrKgbuUmjEvYXURC8hyowQq5CAMM0wA/njvfqwZg3KwcS2gA3cEKOwsdAKNAEIRmr6+/vzvkgGyLAyBcc3CmsVFVu0IF6NNemgHG2O4iB8AI7TmooG6lsx2eTsi2OB4C0H/3Kbjn5x0HAwDG2DX/de5gj8V+G9DPD0Pff3HDNx+z7W+oLmOM7crH9wL+fUS9wfuOrE1y3jbGWE9A6VXVVt8Pe1KS+FJPQJvMtqXPWQwfMMYyBesA4HTD912dNyD+rTnT4lrOftd4vNJj3skYq3XmN9VV9wUz+1S1J9DFGMtak2jWL+PIy00NqbqwVF1YT0B7S/KYtWaObfsGxtiO3GLtFKmq6vEcbr92BRKWjwU4UZFbmfvFvrQ32i5dSALYsuAZPuhBB1+um2cxrGSMXf3Fu3/jguTp8PGc6MCtzj5V5RuyU5IObFp473+gOdAteQKg3oM95l1C+mJ/a8v8cPh8waSfL/92zLbf39rSeLzStj1t2SRwHzBca734oS78nVhYr3v8o8TIgHKLMea/cpExZtYvnz8SLFuXbkzfDBCtAdp+/6PeY4eprwBAarrBsnXpzpUJtc7CNyMgRRcFEZMXjYF1M4ak6sJWTYVogFdn6/asnb0wFlZMgZcnRqx98bHG4+4iw4p1Mx7/1pmXnZK0eCysnwkThgJAnAbwtfy6ZAoMiR0PMHH86LDk6ZCW+GRi3HAYGZv8UgyET1gSPzxlxsi01+M+mRsDUU8Ni4zekDDi3WkREZFREDN1/cyo9ybD4omw+vkRqQmjl8cPXfXCqE/njokfGwkxz2oAv99vtYr7TQfspcRqPWSziiXIkl9QUICsNmtJmUuy40MOO95n3OtyEoQsxQiVIPTVXmMesjudTocdI2TJy8uzFJrLJVdBnmCzig47LijIr/Ic0QCMMY/HQ0gppVSSJErdlLoxxk7iqKC00u0uc7lKHQ6EkCRJGGNKaanDIYriN0eOyLL8tZZLEEJmc6EkSSWiWOZyYYxFUaytrR0AyLKMMSaEOJ3Oco1RUeooxRiXS+VSUE6nE2Nc6XYTQioolWW5GCFCCF+toNRut2Nsq6qqIoTwRkVRrKur+7uDgQUaFAmKBsWrYIxlWeY3ZVkWRZFf8BRCCMaYfzLxLIzxHQAhxGQyCYJgNpu5O0EQTCYTQkgUxaB9s8lkMpvNCCG+wWg0CoIgBoUQMplMRqORX/OYk5Nz9OjRuxzwTuWgJEkadMBNEEJ447zTQQc8PsCB8nBijPEO+MfOA/erqvoX4tV0cHn4HekAAAAASUVORK5CYII=" nextheight="752" nextwidth="837" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Although utilizing Jito transactions shows a correlative relationship for lower failure rates, it's not clear whether there is a causation with the current data. Noticeably there are still failure rate spikes in the Jito blocks which would be data point outliers that could be explored at a later time.</p><div class="relative header-and-anchor"><h2 id="h-log-message-text-clustering">Log Message Text Clustering</h2></div><p>Agglomerative clustering is chosen for this analysis to group similar log messages together because it excels at identifying natural groupings in data without requiring a predefined number of clusters. This is particularly useful when exploring log messages, where the goal is to uncover patterns or anomalies that might not be immediately apparent from a large selection of verbose log messages.</p><p>Two clusters are generated, a failed log cluster and a successful log cluster to provide a more succinct overview of the log messages. </p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7c8e2c59a00d52fe3f00979df148bde7.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAARCAIAAAAzPjmrAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTggQXVnIDIwMjQgMDQ6NDE6MzMgUE0gRURUpF1ZwgAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAXBSURBVHicVVV9UNJnHH+63f5Yu93W1rZr17aultd7aXWruzRzQaYZW2VOk/AtCwwGuBQrzZeaphVaVpY5S7oorrCMUIcgkQiCkKBZ+YJoGmnwsx/ibz/i5dkZRe1zzx/Pc/e95/N9+Xy/XwAhdL124e/g9rghhDjuhO/geu2CEDLojMDAoICAgODgYABAOpsNIURRFMMwj9eL47jP2HfxeL2+AyEEPrtEyr7kBGpKEkPafNc8orKNo739jxqlNc2t/AZZtbyVd1tUrtXJtbqOvr7+jCN5e9kHyDR6MpMdStpOIidQ0hjRyakZuQUkcgIzKxtF7X7/gJ9ZrdHWCG6azGaxrIlCZ54oPxebuicoLDiempKefTCrIC8zP4+dk5uRf3QXnb5ycwQxNi6BxQJffgtmfLM4JHTW0sAZPy0An80EAJAoSeYXFu2jLiuCAH9cC4PXAwAWh4R+/MNcAMDyMMLikFDw0fQgQgTnaNHaSBIAoKzyUu/gQFOL4rqoli+qVWq1dySNwkYxr1YoVbXK29TGp4+bVC2V168l7k37q+TUFAGOOzEMu8wXLPo5mEDaKZHfL6u8dLGGd/4Kr0Emu1jDu3xDIFMqq69fX7b5l/TCvOpbfAihQiM2dKsto1aT2WxFkBGLpcc00GMyYRgGIdS0aSMJpML8444JB1DpdJt2xSez/1yzJWpR6IZVERFrtkQRY+PyuWUUJnt1eGQMdX8MlbqCQEhk0iMp8YGEsChK/DICMZC4KS3rMJWTlchKT2SxA4nhFCbLX+T3NcBxHBkfH7NZTc+GBKK6qps3hp6P4v/aO4wi04Cmo0va1S0df2X2et0Wi95m7bdZ+5RtfASx2e2Oe7KGWmmdWNmE4/jOhNQNUdt9BMj4eE9vn95gfDE6NpUiCCGvVihvU0MIWx6oHj40uj2ul9Zhq83ifG2fnET0nVJ9p7TfbNR0iOUqgWW073GPRm+U9A329j8z9w8N4LjTZB7q6e33EegNxmMlpTRWpt5gfKsiH1DUXso9W13Fw3GcFL17Y8T2iF9jZ36/sEF2w9AtftIr7+4Vq3X8nr7WgSGV4UndhAOBEJ4qORO6bpO/FXyqUel0Yql0iiDqt9+nfzGbmsZmsbOWrw6J2rar4FhJ+Jboz7+eE0tOnRMQBAAIj4ohRMStCyMRNscSNscezCma9sl3S4PW79nLYrA4c+cvXxa07gDnSCotncE6FEYkfTVrHgDTAAA7yMngXMXfyamMTE5u+blKBotTeroik5NbWMwtLC6j7NlXeaUqMZWak1t4KItKo8Vu27aBSAxK2bMjnpKSycmlMdILi7lFJ8sqq2oOZuelZ+zPO5Zffv4Sg8VJo2fExCXXiRqmUuT2uNwet9vjxnHc6cLsDvTN06V4+I9AcsXY0z6JTT4b6RgdfTQ8onnxwoiig04Xhk4ginZxk/o2v+G8tK0OeTWq1Fy6r7wwNvZybOwliqLvO1llaMk5wymsLODX8w6fzuDyjidkxZfVnJJrZHcVtbpH7c1aSZ28VtvVLtfJhc23RS2SqyKeRF0vlAqLqgq5V07w7/EFjXyFvpl7tjxuV1JMTIJQWOebab4ie62IdR+NebyY+yYg923RXTJ5r0KhhBBqdR16g9HnTv3dxuhoMvh05vwlK5l0Nqsgu0JwuZxfffrqxUls8kO92Kw2m9X24SxyDg8/lzY/OHm2wopMaQPHnW6P2+NxNsmbJXKpx4N7ve61q9YuCVhKDCHOnvnjigWrBkxm/4+OCYfvU8eEo+WBasG8ZX/Q2P4I3gLDMGb2kZLzF0iJSYMjzz0eXN0h1nfJVPo7pdX5Sn3j08EHrQaBrltkRYZcbhxBxyxjQ6MvhyGEFeUXIjdupaXsT9mdGr015ib/lm/U/4/AtxhQ1G4yD/l17dsQ7Z2dVmQq5PZOhdoglalqlfpGSYugXnFNoa1DJ2z+pYJOwe7fKP8BZSRfilt81dwAAAAASUVORK5CYII=" nextheight="422" nextwidth="784" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p></p><p>It's an interesting observation that the successful log cluster tends to have a lot more addresses. One hypothesis is that if the transactions are successful, more accounts end up being utilized. In contrast, failed transactions might fail earlier in the process, not utilizing the maximum number of accounts. </p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a1970af7e380893ad6881de4d2717990.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAARCAIAAAAzPjmrAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTggQXVnIDIwMjQgMDQ6NDE6MzkgUE0gRURUNZ1FCgAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAZfSURBVHicVZJtUFILAobP7v65M/tnZ2o/Zu79sU3aVTe3Nu4NUyzMEPOi5neWiPmV+JFFtWkp1w+6pOQ1hTBCJIM8isJBPBxAOHgyj6Ik4zHFNhuHlErbwFpM3VFjZ9qd2d1n3pn3/fW+f17A//9sbm74/f71jU/r62vbO1vrG5/8/s8pKalhYeS/HCTRomkk0qH09LTAwEAGg+HxeH0+36p31edb+6L/5P8tBP5t3lXv1IvZ2fnpDkgAIm1SdWOzoqrHKAGRtknniFzTIuzk3QMF1wS55iFk1fvB4/G0/CwuPFeWmswMJ9OCgw6TDkQGBhyi01IQC4pO4it/f7/s8fx3YPH13CAOOeftfGnRLTnb+ERxD6xs7+NKum9gE6qJZ8Y+s1AJ8wUdpfMux+bG6traysqKa3llsQsGeeKfbtzm3pI0ltdzLt+6LlFJNah6fGrc5hjZ2dkA3iy/HZka06EPdOj9x3b1IK5CR/sseK8OlaoNbYO4SmcRmUZArUWqQsRu9+SwHRIqqmBU+u4d4fEsLC7ZFxdHPO8Jt9vucmGvFm1ut93tnvR6XhDT6MzzSWCUmCysqyi5+SP13OnvMk8FMqJPsnOCTsWkXinLrrqaxCnal3gi5XLx8YKzUQVnim/+yOZxw7NTS3+qqWsThp9JlWlUt2SSmAJWYmlho1zaY4DL+LUKGOrQ9iTk5jFLLgImdIhMpVNi4jML2cFkSkj4sV37gkjUmAQm60x+ceLZbAoj4cAxWlj0yUORx4HdfwgmU2LTzoZFx4WQIw8co6Vm5V6r44VGUqMSkoOPUBJOZ/8+ICT0CDXwIJlEjWGVXgJkcmVUbFJqVu73EdHMc8UCgejS5er9BykXyivOMM+L7sry8i+Eko4GfUeJoMVn5JzPYV+kMdJOxqUBX/gh/nRA8OH9ByNDDkREHI2LoMYBwK5f/e6bsKP0Pd+SpIouYPvzthxW9aJwLwr7/f6pOacJHzaP4z0GeACzgohOi1m0mEXwsL1adIcnvXu1iS/sVqgHjTklZbnFZQ9UqpxLnIzSEgYrp7qxUW9CzVZsa3trfXPT7/fvfP4MfPj4MfNKWc71yyV1VZyG+vii3OQLReGZycDer0MT6LsopL0x1BAGPYqVeTgtYW8MNbWcXVrPDWacSOSww5gpwYn0E/lZR7PTT+RnJV0sKqyprG1rYdfd4DTUl/NriedOYHtnSznQ064GB4ZQlUHX+khWI2ro0IAgAmHjuAEzoWNWeAiRqOQKXbfOCuusSD+KQKZ+aHAA1KvvdslkaoV+yKiEVby2JrlGCerVvQbIOoGBcO/r5dfA1vbmvBtHJzo0lp9HpyCrXQEPi4bsYAdUYcA7kVH1wONuh3NoaWXGtTKztDIz7NDhzxBssm9y1jrxzES8ROdctmFH/+B4t30GnVl48nTO8nTOYneaXcvEB98y4PF4a3h8XoPgjqgVNholUhkrny1obpbJ2yu4tVEpSXlXObyG5kdd6gG9UdAkvC1ubn3QmpiTEh57LKuUxa4oLangFFdeaBALagW89IKsPE5BWl5mHqewuo43P78ALC6+IUfEnmdfocdlpJ3O3/enI7/46uug/ZG7/xj65Sa/Kb9YFRjyfXI68+q1GnpcRsA+ciQ14duQCAD4dXAoZU8AiXwkNup4Ujjlhz8fov72m+Cvdu+JpCczUrMB4Jd6Ewq8ffdy6Q2x8c9V70f30tuZl68I39p7r+/VIK5FR1E9Bj1fmMCJHtOYSK2XnMtj0uNoyWkZhSVn/1pZVlZexsrNjj8VV3A+l8nKLizKGzBDop7WavGNeml9pej631wzgI3QqY38Ebtqeg41Dt+fmNZNz5knpiFsXOGY0U3PofCQyPxE5piFUZtixAHijkfPXSPI8J3HTxU2AiKcxrEp9ew8OjuPOheshNM0Md1vtbUjWKvxsZBwWoC19U8KRK1A1FxxI7dNIO8HH8Ld3DYBXy5shxRKpE+h720BpS2glCturGi5WS+5ze9okWiUfLmoHzM0PRRLeh8KOts6dd0isKMfM8kgUAR2NCnvK/S90/NO4B+ffHe6JBCqh1C9ElaZxlCNVa/FEC2G4ITNNIZqMUQISrUYQrx4prHC/ZgBJ+wIbn4E92mscO29xj504J66U2PVT72YNY2hkAXWWGEEN+OEzfth9V823r/if+Lm0gAAAABJRU5ErkJggg==" nextheight="422" nextwidth="784" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>The raw data from these word clouds can be found here:</p><p>log messages in failed transactions</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0ac32d93641ff4f54639a98380a1c341.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAICAIAAAAX52r4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTggQXVnIDIwMjQgMDU6MDQ6MDggUE0gRURUL+QPGgAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAFHSURBVHicfZIhksMwDEVNjMVCjEKMwhYVmRkFGZm0YWJGZmFChkZhZWWBYWVhOUVv0BMU7azVerOdnT6g+RaIpP8j1nW93++Px2PbNuecLwzD4D/i/qN/Ya1FxNvttm2b0IWu64Zh+Cp0Xccdrm9orY+nYwhhLMQYiSgUUmEcx5QSEbEQUkoAkFK2bQsAQvyM5C36vjfGWGtNoQrvfQiBD0XEelbO+XK5TNN0Pp+naZrnOcb4HAAASqkqtNZt26qPNE0DANZaKuSc+SYiSinFgnPudwBfIAtCiMPhEGPkHYcC+8CCO4hIRNfrdVmWeZ5rXdd1KRCRAICmoJRiW9gf9pSKlbW+CXYcd3jvETGEgIgppRDCnwH7bDl89mpfK/x0zvEXiSjGyLHz1JzzMwOG/REvap+b++eepmmMMbwy+87h849ujPkG703pY7z4/g8AAAAASUVORK5CYII=" nextheight="332" nextwidth="1378" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>log messages in successful transactions</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d1accd1a3b80819c93e452b87a6d1ddb.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAHCAIAAADmsdgtAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTggQXVnIDIwMjQgMDU6MDQ6NDMgUE0gRURUL2JCQAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAD/SURBVHicpZKhkocgEMZ9A5rFZjH5AiaTiUYz2UxGE41Go9lsNBqRRqNt4w18CsN/jh05vHQ39wsMfMvst7tQHccRQgCA67q2bVv/B+dcJJRSMcb7vqs6QQgZx5ExRillCdzQtM4PGEKlvJxFKeWZkFIKITjnVfPQ932Z/UfSZVnysQxND5RSzrlKCCHQad/3rw6ywTRNZV3zPJd9oE0uHxFCOOe01gBgrQWAEIJzDgC890qpl0HTNISQPLRyX7+VrDPGlFIhBO99jBGz+4QxRkr5bdB1XR7Xb6jrmjEmE1rr8zyttTphjEHlZdC27V8NhmHgCRw6/h+0xBde1/UDTe/p3e8vxV4AAAAASUVORK5CYII=" nextheight="312" nextwidth="1374" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p></p><p></p><p></p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>solana</category>
            <category>pyth</category>
            <category>zeta</category>
            <category>data science</category>
            <category>polars</category>
            <category>dataframe</category>
            <category>cryptohouse</category>
            <category>python</category>
        </item>
        <item>
            <title><![CDATA[Solana Data Analytics Part 1 - Cryptohouse, Dune, Flipside]]></title>
            <link>https://paragraph.com/@evandekim/conquering-solana-data-analytics-part-1-tooling</link>
            <guid>bfxAlwn5xZ09L8ipekzK</guid>
            <pubDate>Tue, 13 Aug 2024 21:15:48 GMT</pubDate>
            <description><![CDATA[IntroThis is the start of a series of posts that examines how to analyze Solana data in SQL using different platforms. We start off the s...]]></description>
            <content:encoded><![CDATA[<p></p><div class="relative header-and-anchor"><h2 id="h-intro">Intro</h2></div><p>This is the start of a series of posts that examines how to analyze Solana data in SQL using different platforms. We start off the series with a couple of key queries that yield insights to both the data platforms and the data themselves. </p><p>The best free SQL based tools are <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://crypto.clickhouse.com/">Cryptohouse (powered by Goldksy)</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://dune.com">Dune</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://flipsidecrypto.xyz/">Flipside</a> for accessing Solana data. This post evaluates the tradeoffs between using each of these platforms by using a test of useful queries. These queries look at the most fundamental table for Solana data, the <code>transactions</code> table, which forms the basis of most fundamental research on Solana.</p><p>We'll examine three queries:</p><ol><li><p><strong>Transaction Sample</strong>: A basic query to quickly inspect the dataset.</p></li><li><p><strong>Filter Array for Accounts</strong>: A more complex query that filters transactions based on a subset of accounts, an essential task given that many Solana transactions have columns stored as arrays.</p></li><li><p><strong>Filter + GroupBy Aggregation</strong>: A sophisticated query that filters account arrays and performs a groupby aggregation, a common task in data analysis.</p></li></ol><div class="relative header-and-anchor"><h3 id="h-transactions-dataset"><code>Transactions</code> Dataset</h3></div><p>The <code>transactions</code> table is a fundamental table for Solana blockchain data and understanding how to effectively navigate through the table is key to unlocking more on-chain insights. Every Solana transaction touches a set of accounts, which are stored in an array. There is also a pre-balance and post-balance column arrays which store the SOL amount before and after the transaction, which are important to derive SOL related volume for fees, compute costs, and validator revenue.</p><p>This table also has a status/success column that determines whether the transaction was successful or not. Continuously higher than normal transaction failures can indicate an interesting research direction. </p><p>Finally the <code>transactions</code> table contains all of the instructions required to execute the transaction in a set of logical steps. For those familiar with EVM data, Solana instructions are similar to EVM log event data. </p><div class="relative header-and-anchor"><h3 id="h-tooling">Tooling</h3></div><p>There are three primary criteria for evaluating these tools: performance, features, and data availability. The importance of each criterion depends on the specific research outcomes desired.</p><p>For instance, Cryptohouse tends to have faster query results, but it currently lacks features like dashboards and full data availability compared to Flipside and Dune. Dune has custom Trino-based DuneSQL, while Flipside uses a more standardized SQL dialect, making it easier to use with AI models like ChatGPT. Dune also offers intelligent caching, speeding up queries that are run multiple times.</p><p></p><div class="relative header-and-anchor"><h2 id="h-query-1-select-most-recent-transactions">Query #1 - select most recent transactions</h2></div><p>This is a basic query that is commonly used to take a quick look at the dataset that you are working with. It is important to see that the dataset you choose has both the columns and accurate (e.g. non null) data in those columns. Although blockchain data is standardized at the node level, each data indexer chooses the most efficient way to store and format their data to serve to users. </p><p>Some notable comments about the <code>transactions</code> dataset differences is namely what is available and what format it shows up in. If the columns you want do not exist in the dataset, then that automatically restricts what data platform suits your needs.</p><p>For example Cryptohouse does not have an <code>instructions</code> column. While dune has <code>instructions</code>, the output is not friendly for longer formats so it's not possible to inspect the output of larger sized columns. While Flipside has <code>instructions</code> as well, the layout is the opposite of dune where each column is maximally sized. This is not the most ergonomical because the width of the data returned is quite large to scroll through. Another example is that both Cryptohouse and Dune have a <code>tx_index</code> column whereas flipside doesn't.  </p><div class="relative header-and-anchor"><h3 id="h-performance">Performance</h3></div><table style="minWidth: 572px"><colgroup><col style="width: 116px"><col style="width: 222px"><col style="width: 209px"><col></colgroup><tbody><tr><th colspan="1" rowspan="1" colwidth="116"><p>Platform</p></th><th colspan="1" rowspan="1" colwidth="222"><p>Query Execution Time (seconds)</p></th><th colspan="1" rowspan="1" colwidth="209"><p>Rows Read</p></th><th colspan="1" rowspan="1"><p>Bytes Read</p></th></tr></tbody></table><table style="minWidth: 571px"><colgroup><col style="width: 114px"><col style="width: 226px"><col style="width: 206px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="114"><p><strong>Dune</strong></p></td><td colspan="1" rowspan="1" colwidth="226"><p>15</p></td><td colspan="1" rowspan="1" colwidth="206"><p>30,228,504 rows</p></td><td colspan="1" rowspan="1"><p>50.0 GB</p></td></tr></tbody></table><table style="minWidth: 571px"><colgroup><col style="width: 115px"><col style="width: 225px"><col style="width: 206px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="115"><p><strong>Crypto-house</strong></p></td><td colspan="1" rowspan="1" colwidth="225"><p>13.437</p></td><td colspan="1" rowspan="1" colwidth="206"><p>149,395,554 rows</p></td><td colspan="1" rowspan="1"><p>248.11 GiB</p></td></tr></tbody></table><table style="minWidth: 571px"><colgroup><col style="width: 114px"><col style="width: 227px"><col style="width: 205px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="114"><p><strong>Flipside</strong></p></td><td colspan="1" rowspan="1" colwidth="227"><p>8</p></td><td colspan="1" rowspan="1" colwidth="205"><p>N/A</p></td><td colspan="1" rowspan="1"><p>N/A</p></td></tr></tbody></table><div class="relative header-and-anchor"><h3 id="h-cryptohouse">Cryptohouse</h3></div><pre data-type="codeBlock" language="clickhouse"><code>SELECT
  <span class="hljs-operator">*</span>
FROM
  solana.transactions
WHERE block_timestamp <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-built_in">now</span>() <span class="hljs-operator">-</span> INTERVAL <span class="hljs-number">12</span> HOUR
ORDER BY
  block_slot,
  index
LIMIT <span class="hljs-number">1000</span></code></pre><div class="relative header-and-anchor"><h3 id="h-dune">Dune</h3></div><pre data-type="codeBlock" language="sql"><code><span class="hljs-keyword">SELECT</span>
  <span class="hljs-operator">*</span>
<span class="hljs-keyword">FROM</span> solana.transactions
<span class="hljs-keyword">WHERE</span> block_time <span class="hljs-operator">&gt;=</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-operator">-</span> <span class="hljs-type">INTERVAL</span> <span class="hljs-string">'12'</span> <span class="hljs-keyword">HOUR</span>
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span>
  block_slot,
  index
LIMIT <span class="hljs-number">1000</span></code></pre><div class="relative header-and-anchor"><h3 id="h-flipside">Flipside</h3></div><pre data-type="codeBlock" language="sql"><code><span class="hljs-keyword">SELECT</span> 
  <span class="hljs-operator">*</span>
<span class="hljs-keyword">FROM</span> 
  solana.core.fact_transactions
<span class="hljs-keyword">WHERE</span> 
  block_timestamp <span class="hljs-operator">&gt;=</span> DATEADD(<span class="hljs-keyword">HOUR</span>, <span class="hljs-number">-12</span>, <span class="hljs-built_in">CURRENT_TIMESTAMP</span>)
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> 
  block_id
LIMIT <span class="hljs-number">1000</span>;</code></pre><div class="relative header-and-anchor"><h2 id="h-query-2-filter-array-for-accounts">Query #2 - filter array for accounts</h2></div><p>This is a more complex query that filters transactions based on a subset of accounts. This is a very useful query because a lot of Solana <code>transactions</code> columns are stored as arrays so it's important to know how to filter through arrays. The second is that most analysis will be geared towards specific accounts (protocol accounts, mev accounts, trading accounts, etc). </p><p>Notably Crytpohouse starts to shine with these kinds of queries and offers superior performance compared to Dune and Flipside. Cryptohouse also scanned nearly 4x as much data as the Dune. Flipside also performed fairly well with the filter task as well, but not as much data is available on the query statistics.</p><div class="relative header-and-anchor"><h3 id="h-performance">Performance</h3></div><table style="minWidth: 579px"><colgroup><col style="width: 116px"><col style="width: 298px"><col style="width: 140px"><col></colgroup><tbody><tr><th colspan="1" rowspan="1" colwidth="116"><p>Platform</p></th><th colspan="1" rowspan="1" colwidth="298"><p>Query Execution Time (seconds)</p></th><th colspan="1" rowspan="1" colwidth="140"><p>Rows Read</p></th><th colspan="1" rowspan="1"><p>Bytes Read</p></th></tr></tbody></table><table style="minWidth: 579px"><colgroup><col style="width: 116px"><col style="width: 297px"><col style="width: 141px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="116"><p><strong>Dune</strong></p></td><td colspan="1" rowspan="1" colwidth="297"><p>115.874</p></td><td colspan="1" rowspan="1" colwidth="141"><p>30,428,286 rows</p></td><td colspan="1" rowspan="1"><p>50.5 GB</p></td></tr></tbody></table><table style="minWidth: 580px"><colgroup><col style="width: 115px"><col style="width: 298px"><col style="width: 142px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="115"><p><strong>Crypto-house</strong></p></td><td colspan="1" rowspan="1" colwidth="298"><p>16.269 </p></td><td colspan="1" rowspan="1" colwidth="142"><p>149,123,945 rows</p></td><td colspan="1" rowspan="1"><p>198.12 GiB</p></td></tr></tbody></table><table style="minWidth: 578px"><colgroup><col style="width: 114px"><col style="width: 297px"><col style="width: 142px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="114"><p><strong>Flipside</strong></p></td><td colspan="1" rowspan="1" colwidth="297"><p>37 </p></td><td colspan="1" rowspan="1" colwidth="142"><p>N/A</p></td><td colspan="1" rowspan="1"><p>N/A</p></td></tr></tbody></table><div class="relative header-and-anchor"><h3 id="h-cryptohouse">Cryptohouse</h3></div><pre data-type="codeBlock" language="clickhouse"><code>SELECT
  <span class="hljs-operator">*</span>
FROM
  solana.transactions
WHERE
  arrayExists(
    x <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> x<span class="hljs-number">.1</span> IN (
      <span class="hljs-string">'Fc8bpeCMifWYv97pQ3k5xDvd98nuVg6yAaZrwmy4RRp6'</span>,
      <span class="hljs-operator">-</span><span class="hljs-operator">-</span> zeta <span class="hljs-class"><span class="hljs-keyword">contract</span>
      '<span class="hljs-title">rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ</span>' -- <span class="hljs-title">pyth</span> <span class="hljs-title"><span class="hljs-keyword">contract</span></span>
    ),
    <span class="hljs-title">accounts</span>
  )
  <span class="hljs-title">AND</span> <span class="hljs-title">block_timestamp</span> &gt;= <span class="hljs-title"><span class="hljs-built_in">now</span></span>(<span class="hljs-params"></span>) - <span class="hljs-title">INTERVAL</span> 12 <span class="hljs-title">HOUR</span>
<span class="hljs-title">ORDER</span> <span class="hljs-title">BY</span>
  <span class="hljs-title">block_slot</span>,
  <span class="hljs-title">index</span>
<span class="hljs-title">LIMIT</span> 10000</span></code></pre><div class="relative header-and-anchor"><h3 id="h-dune">Dune</h3></div><pre data-type="codeBlock" language="sql"><code><span class="hljs-keyword">SELECT</span>
  <span class="hljs-operator">*</span>
<span class="hljs-keyword">FROM</span>
  solana.transactions
<span class="hljs-keyword">WHERE</span>
  arrayExists(
    x <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> x<span class="hljs-number">.1</span> <span class="hljs-keyword">IN</span> (
      <span class="hljs-string">'Fc8bpeCMifWYv97pQ3k5xDvd98nuVg6yAaZrwmy4RRp6'</span>,
      <span class="hljs-comment">-- zeta contract</span>
      <span class="hljs-string">'rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ'</span> <span class="hljs-comment">-- pyth contract</span>
    ),
    accounts
  )
  <span class="hljs-keyword">AND</span> block_timestamp <span class="hljs-operator">&gt;=</span> now() <span class="hljs-operator">-</span> <span class="hljs-type">INTERVAL</span> <span class="hljs-number">12</span> <span class="hljs-keyword">HOUR</span>
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span>
  block_slot,
  index
LIMIT <span class="hljs-number">10000</span></code></pre><div class="relative header-and-anchor"><h3 id="h-flipside">Flipside</h3></div><pre data-type="codeBlock" language="sql"><code><span class="hljs-keyword">WITH</span> filtered_accounts <span class="hljs-keyword">AS</span> (
  <span class="hljs-keyword">SELECT</span> 
    <span class="hljs-operator">*</span>
  <span class="hljs-keyword">FROM</span> 
    solana.core.fact_transactions
  <span class="hljs-keyword">WHERE</span> 
    block_timestamp <span class="hljs-operator">&gt;=</span> DATEADD(<span class="hljs-keyword">HOUR</span>, <span class="hljs-number">-12</span>, <span class="hljs-built_in">CURRENT_TIMESTAMP</span>)
)
<span class="hljs-keyword">SELECT</span> 
  <span class="hljs-operator">*</span>
<span class="hljs-keyword">FROM</span> 
  filtered_accounts f,
  <span class="hljs-keyword">LATERAL</span> FLATTEN(input <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> f.account_keys) <span class="hljs-keyword">AS</span> a
<span class="hljs-keyword">WHERE</span> 
  a.value:pubkey <span class="hljs-keyword">IN</span> (
    <span class="hljs-string">'Fc8bpeCMifWYv97pQ3k5xDvd98nuVg6yAaZrwmy4RRp6'</span>, 
    <span class="hljs-string">'rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ'</span>
  )
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> 
  block_id
LIMIT <span class="hljs-number">10000</span>;</code></pre><div class="relative header-and-anchor"><h2 id="h-query-3-filter-groupby-aggregation">Query #3 - filter + groupby aggregation</h2></div><p>This is a more complex query that filters the account arrays and then performs a groupby aggregation. Groupby aggregations are the bread and butter for most analytics questions and provides strong intuition on the overall shape of the data. In this particular query, we filter accounts for the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.pyth.network/price-feeds/contract-addresses/solana">pyth contract</a> and groupby whether the transaction was a failure or success to analyze the failure rates specific to that contract. It turned out that the Pyth contract is <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/economic-costs-of-failed-transactions-on-solana">experiencing 3x higher failure</a> rates than the average Solana transaction (more on this soon). </p><p>The Pyth example is a query example that does not rely on a dashboard to gain insights into the data. These types of queries would be best suited for the fastest performing tool, which happens to be Cryptohouse. Here Cryptohouse really shines, reading more rows and processing more bytes than Dune while being multiples faster than both Dune and Flipside.</p><div class="relative header-and-anchor"><h3 id="h-performance">Performance</h3></div><table style="minWidth: 306px"><colgroup><col style="width: 116px"><col><col style="width: 140px"><col></colgroup><tbody><tr><th colspan="1" rowspan="1" colwidth="116"><p>Platform</p></th><th colspan="1" rowspan="1"><p>Query Execution Time (seconds)</p></th><th colspan="1" rowspan="1" colwidth="140"><p>Rows Read</p></th><th colspan="1" rowspan="1"><p>Bytes Read</p></th></tr></tbody></table><table style="minWidth: 572px"><colgroup><col style="width: 118px"><col style="width: 293px"><col style="width: 136px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="118"><p><strong>Dune</strong></p></td><td colspan="1" rowspan="1" colwidth="293"><p>54.143</p></td><td colspan="1" rowspan="1" colwidth="136"><p>150,493,063 rows</p></td><td colspan="1" rowspan="1"><p>50.3 GB</p></td></tr></tbody></table><table style="minWidth: 574px"><colgroup><col style="width: 115px"><col style="width: 295px"><col style="width: 139px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="115"><p><strong>Crypto-house</strong></p></td><td colspan="1" rowspan="1" colwidth="295"><p>13.247</p></td><td colspan="1" rowspan="1" colwidth="139"><p>704,857,665 rows</p></td><td colspan="1" rowspan="1"><p>225.74 GiB</p></td></tr></tbody></table><table style="minWidth: 572px"><colgroup><col style="width: 114px"><col style="width: 297px"><col style="width: 136px"><col></colgroup><tbody><tr><td colspan="1" rowspan="1" colwidth="114"><p><strong>Flipside</strong></p></td><td colspan="1" rowspan="1" colwidth="297"><p>38</p></td><td colspan="1" rowspan="1" colwidth="136"><p>N/A</p></td><td colspan="1" rowspan="1"><p>N/A</p></td></tr></tbody></table><div class="relative header-and-anchor"><h3 id="h-cryptohouse">Cryptohouse</h3></div><pre data-type="codeBlock" language="clickhouse"><code>SELECT
    status,
    COUNT(<span class="hljs-operator">*</span>) AS status_count
FROM
    solana.transactions
WHERE
    arrayExists(
        x <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> x<span class="hljs-number">.1</span> IN [
            <span class="hljs-string">'rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ'</span>
        ],
        accounts
    )
    AND block_slot <span class="hljs-operator">&gt;</span> <span class="hljs-number">282400000</span>
    AND block_slot <span class="hljs-operator">&lt;</span> <span class="hljs-number">282900000</span>
GROUP BY
    status</code></pre><div class="relative header-and-anchor"><h3 id="h-dune">Dune</h3></div><pre data-type="codeBlock" language="sql"><code><span class="hljs-keyword">SELECT</span>
  success,
  <span class="hljs-built_in">COUNT</span>(<span class="hljs-operator">*</span>) <span class="hljs-keyword">AS</span> transaction_count
<span class="hljs-keyword">FROM</span> solana.transactions
<span class="hljs-keyword">WHERE</span>
  block_slot <span class="hljs-operator">&gt;</span> <span class="hljs-number">282400000</span>
  <span class="hljs-keyword">AND</span> block_slot <span class="hljs-operator">&lt;</span> <span class="hljs-number">282900000</span>
  <span class="hljs-keyword">AND</span> ANY_MATCH(account_keys, x <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> x <span class="hljs-operator">=</span> <span class="hljs-string">'rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ'</span>)
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span>
  success</code></pre><div class="relative header-and-anchor"><h3 id="h-flipside">Flipside</h3></div><pre data-type="codeBlock" language="sql"><code><span class="hljs-keyword">WITH</span> filtered_transactions <span class="hljs-keyword">AS</span> (
  <span class="hljs-keyword">SELECT</span> 
    <span class="hljs-operator">*</span>
  <span class="hljs-keyword">FROM</span> 
    solana.core.fact_transactions
  <span class="hljs-keyword">WHERE</span> 
    block_id <span class="hljs-operator">&gt;</span> <span class="hljs-number">282400000</span>
    <span class="hljs-keyword">AND</span> block_id <span class="hljs-operator">&lt;</span> <span class="hljs-number">282900000</span>
)
<span class="hljs-keyword">SELECT</span> 
  SUCCEEDED,
  <span class="hljs-built_in">COUNT</span>(<span class="hljs-operator">*</span>) <span class="hljs-keyword">AS</span> status_count
<span class="hljs-keyword">FROM</span> 
  filtered_transactions f,
  <span class="hljs-keyword">LATERAL</span> FLATTEN(input <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> f.account_keys) <span class="hljs-keyword">AS</span> a
<span class="hljs-keyword">WHERE</span> 
  a.value:pubkey <span class="hljs-keyword">IN</span> (
    <span class="hljs-string">'rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ'</span>
  )
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> 
  SUCCEEDED;</code></pre><div class="relative header-and-anchor"><h3 id="h-conclusion">Conclusion</h3></div><p>In conclusion, while all three platforms—Cryptohouse, Dune, and Flipside—offer valuable tools for analyzing Solana data, each has its strengths and weaknesses. Cryptohouse excels in performance, particularly with more complex queries, but lacks some features and full data availability. Dune offers flexibility with its custom SQL dialect and intelligent caching, while Flipside provides a more standardized experience with solid performance. The best choice of platform will depend on your specific needs, whether it be speed, ease of use, or the availability of specific features.</p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>solana</category>
            <category>clickhouse</category>
            <category>python</category>
            <category>dune</category>
            <category>analytics</category>
            <category>data science</category>
            <category>sql</category>
            <category>cryptohouse</category>
            <category>goldsky</category>
            <category>flipside</category>
        </item>
        <item>
            <title><![CDATA[Economic Costs of Failed Transactions on Solana]]></title>
            <link>https://paragraph.com/@evandekim/economic-costs-of-failed-transactions-on-solana</link>
            <guid>UhHaxHCkERfkvJNNRh5R</guid>
            <pubDate>Sun, 11 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[TLDR:Pyth contract has a 26.5% tx failure rate (292,165 txs) across 1.1m transactions, resulting in 1.45 SOL in lost fees. The failure rate....]]></description>
            <content:encoded><![CDATA[<div class="relative header-and-anchor"><h1 id="h-tldr">TLDR:</h1></div><ul><li><p>Pyth contract has a 26.5% tx failure rate (292,165 txs) across 1.1m transactions, resulting in 1.45 SOL in lost fees. The failure rate is cross referenced with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/queries/3980333?category=canonical&amp;namespace=solana&amp;id=solana.transactions">Dune</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://solscan.io/account/rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ">solscan</a>. </p></li><li><p>Total tx failure rate during this period is 8% (69,356,928 txs failed) across ~781m transactions, resulting in 346 SOL in lost fees. The failure rate cross referenced with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/queries/3980339">Dune</a>. </p></li><li><p>Pyth receiver contract has a 3.3x higher tx failure rate than the general tx failure rate. </p></li><li><p>Tx failures are an additional revenue source for Solana validators and there is no economic incentive to lower the tx failure rates. </p></li></ul><div class="relative header-and-anchor"><h1 id="h-introduction">Introduction</h1></div><p>This research looks at some behavior related to failed Solana transactions. Specifically we compare the general tx failure rate to the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.pyth.network/price-feeds/contract-addresses/solana">Pyth receiver contract</a>. This contract has critical functionality to the Pyth oracle model and receives price feed updates from publishers. We see that the failure rate and see that the Pyth contract generates nearly 3x more failed transactions on average, costing about ~10 SOL monthly in lost fees to the publishers. </p><div class="relative header-and-anchor"><h3 id="h-cryptohouse-and-dune">Cryptohouse and Dune</h3></div><p>The results are validated by replicating the queries between Dune and Cryptohouse, two strong data sources for free Solana data. A 600,000 slot range [282300000, 282900000], about 3 days, is chosen to keep resource usage in check. For the duplicate queries between Cryptohouse and Dune, Dune was generally faster (~5 seconds) vs (~15 seconds).</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/ClickHouseDB/status/1821192971896627585">Cryptohouse</a> is a new public goods data source for Solana data powered by Clickhouse and Goldsky. Current functionality is limited - can't save and share queries through a url, a non-trivial amount of data columns are missing, can't perform joins or multi-statements. Please refer to the Appendix section for the Cryptohouse queries. </p></li><li><p>Dune needs no introduction. Currently the crypto data standard for public goods dashboards. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/queries/3980333?category=canonical&amp;namespace=solana&amp;id=solana.transactions">Query here</a> for pyth contract tx failure rates. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/queries/3980339">Query here</a> for general tx failure rates.</p></li></ul><div class="relative header-and-anchor"><h1 id="h-results">Results</h1></div><p>The analysis of transaction failures on the Solana network reveals significant insights, particularly regarding the performance of the Pyth contract. Over a three-day period, the Pyth contract experienced a high transaction failure rate of 26.5%, resulting in 1.45 SOL in lost fees. </p><p>This failure rate is 3.3 times higher than the network-wide average of 8%, which led to 346 SOL in lost fees over the same period. Extrapolating these figures to a monthly timeframe, the Pyth contract could lose 10.45 SOL, while network-wide losses could reach 3,460 SOL. The high failure rate of the Pyth contract suggests potential inefficiencies or persistent issues within its logic. </p><p>Moreover, the economic implications of transaction failures are significant, as they contribute to validator revenue, presenting little incentive for reducing these rates, despite the potential for wasted resources. This complex interplay between transaction failure rates, validator incentives, and network performance highlights the challenges and opportunities within the Solana ecosystem.</p><div class="relative header-and-anchor"><h3 id="h-pyth"><strong>Pyth</strong></h3></div><p>If we extrapolate these 3 day values to monthly values, then the amount of lost fees for the Pyth contract becomes 10.45 SOL and 3460 SOL for all transactions. Manually checking the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://solscan.io/account/rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ">solscan contract transactions page</a>, the main tx failure reasons are:</p><ul><li><p><code>Program Error: "Instruction #1 Failed - The posted VAA account has the wrong owner"</code></p></li><li><p><code>"Instruction #1 Failed - Failed to deserialize the account" </code></p></li><li><p><code>Program Error: "Instruction #1 Failed - UnverifiedVaa"</code></p></li></ul><p>A hypothesis would be that there is some logic that keeps producing these errors. A deeper dive into solana instructions data would be required to prove the hypothesis though. It would be very important to know whether these failure rates are delaying Pyth prices to be published on-chain by multiple slots as this would present an additional hidden latency for using Pyth oracles. </p><div class="relative header-and-anchor"><h3 id="h-validator-revenue">Validator Revenue</h3></div><p>Although the 8% tx failure rate is an extra tax on the users who submit failed transactions, conversely it represents an alternative form of Validator revenue because they are the ones who ultimately keep the fees from failed transactions. </p><p>Although failed transaction rates end up wasting blockspace and computation power, the corollary is that validators do not have an economic disincentive to lower the transaction failure rates because they are still being economically rewarded. A major reason for tx failure rates is due to mev-searcher competition on Solana. Searchers will use spam strategies to land profitable mev opportunities which end up making spamming strategies economically viable. Instead of being seen as a "waste of blockspace", this could also be interpreted as an "abundance of blockspace".</p><div class="relative header-and-anchor"><h1 id="h-conclusion">Conclusion</h1></div><p>In conclusion, the analysis of transaction failures on the Solana network reveals a significant discrepancy between the Pyth contract's failure rate and the network-wide average. The Pyth contract's 26.5% failure rate indicates possible inefficiencies or recurring issues within its logic, resulting in considerable lost fees. </p><p>These transaction failures also play a crucial economic role, generating revenue for validators and offering little incentive to reduce failure rates. This interplay between high failure rates, validator incentives, and overall network performance highlights both the challenges and opportunities present in the Solana ecosystem. </p><p>Further investigation into the underlying causes of the Pyth contract's high failure rate, as well as its potential impact on price feed latency, is essential to fully address these concerns.</p><div class="relative header-and-anchor"><h1 id="h-appendix-cryptohouse-queries">Appendix: Cryptohouse Queries</h1></div><div class="relative header-and-anchor"><h3 id="h-pyth-receiver-contract-tx-failure-rates">Pyth Receiver Contract Tx Failure Rates</h3></div><pre data-type="codeBlock" language="clickhouse"><code>SELECT
    status,
    COUNT(<span class="hljs-operator">*</span>) AS status_count
FROM
    solana.transactions
WHERE
    arrayExists(
        x <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> x<span class="hljs-number">.1</span> IN [
            <span class="hljs-string">'rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ'</span>
        ],
        accounts
    )
    AND block_slot <span class="hljs-operator">&gt;</span> <span class="hljs-number">282300000</span>
    AND block_slot <span class="hljs-operator">&lt;</span> <span class="hljs-number">282900000</span>
GROUP BY
    status</code></pre><div class="relative header-and-anchor"><h3 id="h-general-contract-tx-failure-rates">General Contract Tx Failure Rates</h3></div><pre data-type="codeBlock" language="clickhouse"><code><span class="hljs-keyword">SELECT</span>
    status,
    COUNT(*) <span class="hljs-keyword">AS</span> status_count
<span class="hljs-keyword">FROM</span>
    solana.transactions
<span class="hljs-keyword">WHERE</span> block_slot &gt; <span class="hljs-number">282300000</span>
    <span class="hljs-built_in">AND</span> block_slot &lt; <span class="hljs-number">282900000</span>
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span>
    status</code></pre><p></p><p><strong>A note on lamports:</strong></p><ul><li><p>The average lamports per slot for the Pyth contract is 5.7m lamports (0.00575 SOL). </p></li><li><p>The average lamports per slot is 4.6b lamports (4.6 SOL).</p></li><li><p>I am interpreting lamports as the total "cost" for compute units used per transaction. The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://console.cloud.google.com/bigquery?p=bigquery-public-data&amp;d=crypto_solana_mainnet_us&amp;page=dataset&amp;project=quickstart-tutorial-245520&amp;ws=!1m5!1m4!4m3!1sbigquery-public-data!2scrypto_solana_mainnet_us!3sAccounts">google bigquery definition</a>, where cryptohouse data is sourced from, defines lamport column as <code>The account's balance, in Lamports</code> which doesn't seem as useful though.</p></li></ul><div class="relative header-and-anchor"><h3 id="h-pyth-average-lamports-per-block">Pyth Average Lamports per Block</h3></div><pre data-type="codeBlock" language="clickhouse"><code><span class="hljs-keyword">SELECT</span> 
    AVG(block_slot_avg) <span class="hljs-keyword">AS</span> average_lamports_per_block_slot
<span class="hljs-keyword">FROM</span> (
    <span class="hljs-keyword">SELECT</span> 
        block_slot,
        AVG(lamports) <span class="hljs-keyword">AS</span> block_slot_avg
    <span class="hljs-keyword">FROM</span> 
        solana.accounts
    <span class="hljs-keyword">WHERE</span> 
        owner = <span class="hljs-comment">'rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ' -- pyth receiver contract</span>
    <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> 
        block_slot
) <span class="hljs-keyword">AS</span> subquery;</code></pre><div class="relative header-and-anchor"><h3 id="h-general-average-lamports-per-block">General Average Lamports per Block</h3></div><pre data-type="codeBlock" language="clickhouse"><code><span class="hljs-keyword">SELECT</span> 
    AVG(block_slot_avg) <span class="hljs-keyword">AS</span> average_lamports_per_block_slot
<span class="hljs-keyword">FROM</span> (
    <span class="hljs-keyword">SELECT</span> 
        block_slot,
        AVG(lamports) <span class="hljs-keyword">AS</span> block_slot_avg
    <span class="hljs-keyword">FROM</span> 
        solana.accounts
    <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> 
        block_slot
) <span class="hljs-keyword">AS</span> subquery;</code></pre><p></p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>pyth</category>
            <category>solana</category>
            <category>goldsky</category>
            <category>clickhouse</category>
            <category>sql</category>
            <category>dune</category>
            <category>crypto</category>
            <category>data science</category>
            <category>validator revenue</category>
        </item>
        <item>
            <title><![CDATA[Calculate Jito Tips in Solana]]></title>
            <link>https://paragraph.com/@evandekim/setup-a-solana-etl-pipeline-and-calculate-jito-tips</link>
            <guid>43LD4byEXgCxiVQafnS5</guid>
            <pubDate>Sun, 14 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[In this article, we'll explore how to build a Solana data pipeline in Python to analyze Jito fees. We'll use Solana-py to examine block and tran...]]></description>
            <content:encoded><![CDATA[<p></p><div class="relative header-and-anchor"><h3 id="h-intro">Intro</h3></div><p>In this article, we'll explore how to build a Solana data pipeline in Python to analyze Jito fees. We'll use <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/michaelhly/solana-py">Solana-py</a> to examine block and transaction-level data for a single slot using a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://solana.com/docs/core/clusters#mainnet-beta-endpoint">free Solana RPC</a>. A full notebook example with extensive comments <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://colab.research.google.com/drive/12z_WrntJoVRREMTWzg-lmHsk7QZs5Os7?usp=sharing">is available</a> to accompany this post. I highly encourage you to read through the notebook and experiment with the code.</p><p>The Solana data ecosystem is still in its early stages, offering limited tools and resources for data analysis. Analyzing Solana data <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://youtu.be/643Ipy0IIlQ?si=Wxkq_FuIyyrW3XHh">presents unique challenges</a> due to the lack of transaction standardization and the high resource costs required to sift through data generated by Solana's 1000+ TPS speed.</p><div class="relative header-and-anchor"><h3 id="h-planning-etl-pipeline">Planning ETL Pipeline</h3></div><p>Before diving into the technical details, it's crucial to understand the importance of planning your ETL (Extract, Transform, Load) pipeline. A well-thought-out pipeline not only simplifies the data extraction process but also makes the subsequent analysis more straightforward and efficient. Careful planning helps to identify the necessary data, the transformations required, and how the data will be loaded into the analysis environment. This preparation can save significant time and effort, especially when dealing with large datasets like those on Solana.</p><div class="relative header-and-anchor"><h3 id="h-etl-transformations">ETL transformations</h3></div><ul><li><p>Solana data is stored in various sections of the Solana block object. For example, fees, compute units, and account balances are found in <code>transactions.meta</code>, while account keys are stored in <code>transaction.message</code>. The block number and timestamp are located in <code>blockHeight</code> and <code>blockTime</code>, respectively. Instead of storing each part of the block object separately and then combining them, we extract and combine all this data into a single dataframe, creating a unified dataset.</p></li><li><p>One challenge in extracting Solana data is dealing with multiple signatures, locked account states, and balance changes within each transaction. To analyze Jito fees, we need to isolate the set of Jito tip accounts within the transactions. We applied a transformation at the data extraction level to process each account key and pre/post balance as a separate row, simplifying the complexity. The tradeoff is an increase in the row count for each block by approximately five times. This technique is effective for analyzing a single block with a few thousand rows but may be less feasible for analyzing 10 million blocks with billions of rows.</p></li></ul><div class="relative header-and-anchor"><h3 id="h-results">Results</h3></div><p>The end results show some transaction-level insights for a single block (277533216), which had 2,588 transactions. Unlike Ethereum, where the top of the block is a contentious area, the same does not apply to the Solana slot. The chart below illustrates that some of the highest fees are paid in the 500-1000 transaction index range.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d694b33110e327f18d53f621bc3b1c41.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAZCAIAAADfbbvGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTQgSnVsIDIwMjQgMDI6NTg6MjIgUE0gRURUEjPR9wAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAN1SURBVHictVZNTBNBFB4vepCDB001MR78xXjQqIknPXj1zskYE5LGEDlUa4IhtqZSpYqawOqq6EoKCCWUolZYMqVLGh2wanHVrYBW10hkDVv6w8ICYx2z3aYgUrU1fplsZvJ237fve2/eDCCE4DRIbuC01el0WizWmpoam81msVjtaQSDwewLSwJkZ6qqIoSCC8DzL0OhF2/DAkKI53mKokwmk9lsLi8vLysrKy0tNRqNDMPwPB/MAYSQRsCybGVlpc1mgxCqqipriM7OqG3e3oEXr45VVMtyNB6PK4qix6qqanaiKEo8BxRFgRBqBLIsi6Ko/8jCeKOx5LQ6E/k8liIFIhOBDlmW/yhogQQQQrvdTtN0IBBYSIDxN0LIk2dvwu8+EUJSBROIoshxXCAQWDKCaCw5qUzn73wBwX+XCCFE03R9ff0iiXRNevqC8aTyTwTDw8Nut5tl2SUjGB0bnys0Jo2ApmlJkv6jRCaTyev1Mgzj9XoRQr8S4HQtFU5AUVRnZ6fL5eI4LlcEqfxdp7IEDodDFMXfS4QLDSKTZJ7nm5ubIYS5kjw6Np6va73HzJcpRVEul2vJMhVGPjZ5YL4E8aTy/NXI7zZaclLRy39anamovlXYZs4QRCIRlmUXSTQw+FZvQRh/00derlPfU/MEHMdZLFaapvUy1ds9IcRcRd9xPSKEzGE8h/PGn3tRNJZUplVCSJMH3uv82zTMaQfRbDSWnIhNBoNPNQJJkgQhrB9y/YNDt1u7Wh76a5kOALYeOXkB7Dpk2F8Cthxseei/3drV0N7T6PE1enx329n0gE0eX5PH19Dew7i6H0B06vxNw/6Sw6aq05eZofDrzHlgNptttnO9Pnig5DgAG8GaXWDZZgAMYEUxAOuAYTcAG7Tn2j1g1Q6wshgUFWtLw25QtF2br94J1u8Da/dq1qLtAGwCy7cdPWHv4/zzEmGMEUL3O9oTE5L89cusmuh/3Hv10nlVmUhMSFOJ8dbGu9VVVvnrKMZTM+pkc0N93RVH2iR/GAmfPVPR0XYvGR+fSshDb0LVVVZ3W4sgCBkCXXeMcSg0mM3BE9R/7foNfZkixOO5T1FUKDQoCGG3u8PhuOh0NupWSZIu1Vzu6u7WP3z3PlJbR/n9fT9dW7IXpEWVsGgJIaRp2mg0DqQrInvD+PVNffIDNwJIL07I/cIAAAAASUVORK5CYII=" nextheight="451" nextwidth="578" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>The next chart demonstrates that fees paid do not correlate with higher computational costs. In fact, the majority of higher fees are clustered in transactions with lower compute units. This indicates that the most highly contested states take very little of the overall compute cost in Solana blocks. The majority of transactions take less than 200,000 compute costs.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5700830b3e3b01405d8749a7d85b14ad.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAZCAIAAADfbbvGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTQgSnVsIDIwMjQgMDI6NTk6MjkgUE0gRURUjtcSYQAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAMKSURBVHictZZPaNNQHMczZOA9wvxT8JRD9LjDMqEHizjECPaSi1HIJacKxssuRjAoUbQOqbhFlCc0sPVNMXrIxXTOOGFsLGzSQunKDJTGPzUDTdmWqnuyPtdt6A6y7HMI7/14733z+77kvR+BWoRhiP4CBxVFicVikiQlk8lEIsEwjKIogiDE43GWZfv6+lRVpWkaQvjPdYh2NAxD2OLp6Gg+/wpCqOu6aZqiKJIkSVFUV1dXLBbr7OzkeZ6iKJIkCYKgaToej5MkKYqiaZq6rsNN6Lq+JqBpGsMw3d3d6XT6R7P5+s27A0d6vXV830cIBUEQroPb7Yjv+/jZntKemMlk1gQ8z3McB+sjhL59D6bniigKdJwBxnXd7XzcqQAAIJFIcBynaQ8RQrfvP6rWPkUpUCgUAACapo3kcgihYqmytLISpcCuW2QYBs/zbYvA8POIN9m2bUVRVFXFGRRLlfpXPzIBQRAcx9lFi2ialmVZkiRFUQAA0QtwHKcoiizL6XQa/2gRC7Asu7sWIYQsy8IZQAiXV8KpmblSZaFUWYhMAELI87wkSU8ACJaW1QFtbGJyerYQmQCmbdHlK2rNi/SocBwnk8moqvryxfPyQvUBGG7tRDMyAQghwzAcxwEABrRsteb9XP0ZBI0gaLQO/Ob6jdTcqUVjeWsQwOPJCzt/9y0CrutalgUAKM7NEB2HCMzew8TBo/ceZntPnxubmPpc//pf31UQNKZmCxv3AU3TPT09ADwmiH3EFvYQHfuvXr8Dn728OTBYmZ8vt/A8z3Vd3HZd1/d93MZdz6uN2xMnzvKqqm5YhBAayeXOnDr5yfvQ+F4PV4J7d2/dvHHt88fqW3scwtyxXsY0Tdu2y+WyIAipVMpxHMMwAAAURQEADMOwLItf4/xYPl8svLdt+48A3sbFxcVU6mLN+/hrdRUhNDg0BOEoHvClXr8kSZOTk0YLjuNYlrVtG1/poigGQYBH4noCIZTNZgEAWzJo1y/bIcsywzC4TGJZVhAEHN88q71Of39/KpX6DYq6Cb45+ZYbAAAAAElFTkSuQmCC" nextheight="451" nextwidth="578" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><div class="relative header-and-anchor"><h3 id="h-jito">Jito</h3></div><p>The jito tips for this block are distributed throughout the block. Looking at the next chart, it looks like there is a small cluster of jito tips towards the end of the block (highest transaction indexes), but all of the high outlier jito tips are dispersed throughout the block.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/91fdd158741a6ebb1849d09f2ac07e43.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAIAAAAUMWhjAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTQgSnVsIDIwMjQgMDM6MDQ6NDQgUE0gRURUqPClWwAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAM3SURBVHicvZZNTBNBFMfHE0cSExJ7sCbilUsve+kJTDhYLxXTRGgkNmKBrEQNXxW0SNiWdoHCalIkugTTKjaoJSQQD42HmggFzULCJiSYKF2IXWjaJdItCX2mbFnWogVC9X+anZk3v5n35r1ZBIdJFEUAKC8vLyoqKigoUKlUSCGNRiMIQg5zJC/BcZzFYqFp2n1ANE3X1taaTCaDwWDcldTW6/WNjY0URbndbop63NlFKE0IgvB4PGmAJJZlfT6fzDuuvofXHNQQACST21IPx3EZQCgU8vv9FEV5PJ4cAFEh+TMHch9AURSGYQaD4SQnAMXeswF5cdFB/UcAx3EMwwQCgbwAorF4LC78BrBYLIWFhSaTKXeQjxiDR+QT79gEAKyEw2mATqdjGOa4LtpJpQ6dkzmBWq1mWTaPMRD3zDOA0tLSQCAgZUOWi5LJ7WgsLrclz0qKxQVB2DzSCbRarU6nKy4u1uv1WYCV8Kp9IJ2cALD8LSwlqjTqG5/yjU/l9lUGgGHY5OSkKIqhUOifXFOO46RymBUD79jEwuKS0kC52aykzZI0un9NpRUZhlECFhaX1n5E8nACACAIQqVSabXavOQBACx/XdlKJNdWVzMAnuc5jgsGgycEwB7DTb/6NDcf3dj4ay2StZNKbSUSO6lUMrmt7JeGlNV7c/PnF2ZRCpX8gv0ZIAiCsuLLOtijGEpF+HWrfSDCr4uiuJVIvP/wcXpmNgMgSVKtVmMY1tdPDQ6/Tr8QT19cumauuds+/PKt9nKVa3DEQT0z33vgpkfd9KiDGsJbOl2DI/aBIdfgyHPvG+/YRDvRX1GNo8LzET56TlOKEDp9QTP/eSYNYFnWv6uOjo7qGzdJkuzrH2hubbtqqOwibE0tbfU4Xo/j3U6ys4uorLr+0Npp63bWmOtqzHVOstfucN4y19bhDd1Osrm1Db/dgN9p6ulzVRmNvT09+y4CgHB4BSFUUlICAG33LQghmqbj8RhCSK0+CwB2mw0hZLVaAUClOnMKIZ7n/f53CKGKK3oAuFhWhhCamZmem51FKF0mMgDZy8oAyP8jwq6kTqPRqNFoEELBYFA2lK2kmcr2LyTlq8SvWW4hAAAAAElFTkSuQmCC" nextheight="447" nextwidth="585" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Finally we also compare the jito tips being for transactions with varying compute costs. This chart shows that there is a skew towards higher jito fees for less compute units with a large cluster of fees paid for less than 25,000 compute cost. It is also observed that Jito fees are also widely distributed within the the block, but it is not clear given this sample data why that is.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0d375e9004cfac9a2d503235e62e3d7a.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAIAAAAUMWhjAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKHRFWHRDcmVhdGlvbgBTdW4gMTQgSnVsIDIwMjQgMDM6MDY6MDQgUE0gRURUrXBNlgAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAMSSURBVHic1ZVPSBRRGMDfKVjoMidDrRyD8TAQPqoRZBPHhcZDS4WXVrE9FAthCE6RdGgvaWBjUuPBMYUtag6JYe0a+Adj8TIEoQsqW7Ik7qi4LyT2sr5VnBfNLO666fpn8dDv9GbmvfnN+75vvgfIfiCEAAA0TTMMQ1GU2+1mWdZmswEAIIScCSEEY7zrcrD9LBKJSJKk/IMkSUVFRRBC2sTlclljiqIEQYAQCoLg8/myVskmKYFFaHq664XcIfdtJJN4J2QnmTfxHsTj8bRgbm4uGAwqihIIDO+6WbxTljXYKziKoqQEkiQxDMNx3ODgYI5oHpa0wELTNL/f/38KdBO/SZYgNBNeWY3lK2hqaiooKHA6nZkCjJOEEIZztD/v2b48ioDneU3TjjFENE1PTU3tJUgkEluGkZcAQqgoiq7rgyYz4chiVD/I+hhay52etEAQBJZlnc6rb9+pjXcfDgz9/d1yfDg28zEyMTk0PJ5jZkpgt9tHR0ePt0yx+cbQ9PSrfl/v6/cHWbxlGPvWVVoQiUQ0TVNVNRAYXvsd30geuiL3EbS1tVEUBSHMClGOWC1El60E5Nsq8B5xWIguj0xM5iv4+i0UmgmTo5I+DxBCVi/66PfP/fj5fT6SmtH7puZa4+bGZlRfIoRE9aUtw9gyjA+B0Q65L5FIrMZQDK3Nhudnw/OhmfB2vWKcXFmNybKMMU7lAABQVlbW8awTnCisvHKDEIMQ49PnMQCA1N0vdfcRYrQ8eoLQry+TGqDom7ebe33qS8XX41MZznHJcf0kXb4Y1dfXExjjqL70uL0rvQPrhEMISZLU0nxvfGyE53kAgNt9q/WBCABoqHd5PJ5TZkNsbW2t4C7Y7ZX3RfF0cfG50lKP504Fd/FMcWFDfT3DMDabzVFTU15+HgCgqmo6B1bfHhgYIIR4vV4IoSzLktRJ03R7+1NN086WlDAMU3W5CphwHFddXc3zvNfrra2ttVqOKIoQQlEU6+rqWJYVRXGHIOuMzTx7CSHBYNDpdFqN3eVy2e12hNCuZZ1Zh38A7XyIqtCMAeoAAAAASUVORK5CYII=" nextheight="453" nextwidth="600" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><div class="relative header-and-anchor"><h3 id="h-conclusion">Conclusion</h3></div><p>Initial findings reveal interesting transaction-level insights for a single block. For instance, higher transaction fees in Solana are not necessarily correlated with higher computational costs, and Jito fees are distributed throughout the block with a notable cluster towards the end. It remains to be seen whether these transaction-level insights hold for analysis over larger blocks, but the probability that this pseudo-random block selected is an outlier block is low. </p><p>These insights highlight an ETL workflow to further understand the dynamics of Solana transactions, fees paid (including Jito), and compute costs. With the current data landscape, it is no small feat to analyze even a single week's worth of blocks (216000*7 = ~1.5m  blocks). With an average blocksize of 1000 transactions, this equates to 1.5b transactions. </p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>solana</category>
            <category>jito tips</category>
            <category>polars</category>
            <category>mev</category>
            <category>data science</category>
        </item>
        <item>
            <title><![CDATA[Past Writings]]></title>
            <link>https://paragraph.com/@evandekim/past-writings</link>
            <guid>s5INTxXKDHjKH4whM3M6</guid>
            <pubDate>Sun, 12 May 2024 20:26:14 GMT</pubDate>
            <description><![CDATA[defi, data, degen, mev, math]]></description>
            <content:encoded><![CDATA[<p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028">Github</a> | <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/EvanDeKim">Twitter</a> | <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dune.com/evandekim">Dune</a> | <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.sentio.xyz/evandekim/sui-lst/dashboards/6fO9DNrQ">Sui LST Dash on Sentio</a></p><br><h2 id="h-latest" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Latest</h2><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.com/editor/31o6StqKYWR7B5fGsWYv">On-chain Atomic Gaussian Math</a>  (December 2025) </p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.com/editor/IZ7hruMsuonAs8DFhJXt">Building an Agentic Native MCP for Data Science </a>(October 2025)</p></li></ul><br><h2 id="h-highlights" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Highlights</h2><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/sui-nft-collection-analytics">Sui NFT Analytics</a> (February 2025)</p></li></ul><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/economics-of-sui-shared-liquidity-pool-objects">Economics of Sui Shared Liquidity Pool Objects</a> (December 2024)</p></li></ul><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/data-observability-with-sentio-pipelines">Data Observability with Sentio Pipelines</a> (December 2024)</p></li></ul><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/preconf.eth/xX2wu_3DC76qVYy1GHi1WVeOV2cXDF_rtDh7GlD6ZEU"><strong>Tackling Blob Fee Slippage with Preconfirmations</strong></a><strong> </strong>(September 2024)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethresear.ch/t/slot-inclusion-rates-and-blob-market-combinatorics/19817">Slot Inclusion Rates and Blob Market Combinatorics</a> (June 2024)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/preconf.eth/6lZYL62DR9U14KC7wCC4RHReVdHcBeMy5PKeHVbPq5k">Analyzing Blob Inclusion Rates and Market Strategies</a> (April 2024)</p></li></ul><ul><li><p style="text-align: start"><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://frontier.tech/measuring-reordering-slippage-in-mev">Trading in the Dark: Measuring Reordering Slippage in MEV</a> (January 2024)</p></li><li><p style="text-align: start"><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/mohammed7s/Feedlot/blob/main/report/build/report.pdf"><u>Feedlot: trading with safe, low latency price oracles</u></a> (April 2023)</p></li><li><p style="text-align: start"><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/Evan-Kim2028/tda_ohm_analysis/blob/main/Using_Topological_Data_Analysis_to_Identify_MEV_Behavior.pdf"><u>Using Topological Data Analysis to Identify Distinct MEV Behavior</u></a> (November 2022)</p></li></ul><h2 id="h-data" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Data</h2><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethresear.ch/t/economic-implications-of-a-competitive-blob-market/20931">Economic Implications of a Competitive Blob Market </a>(November 2024)</p></li></ul><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/clustering-analysis-on-zeta-lessgreater-pyth-transaction-logs">Clustering Analysis on Zeta &lt;&gt; Pyth Transaction Logs</a> (August 2024)</p></li></ul><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/conquering-solana-data-analytics-part-1-tooling">Conquering Solana Data Analytics Part 1 - Tooling</a> (August 2024)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/economic-costs-of-failed-transactions-on-solana"><strong>Economic Costs of Failed Transactions on Solana</strong></a><strong> </strong>(August 2024)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethresear.ch/t/slot-inclusion-rates-and-blob-market-combinatorics/19817">Slot Inclusion Rates and Blob Market Combinatorics</a> (June 2024)</p></li></ul><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/streaming-embedded-olap-data-pipeline-with-lancedb-duckdb-in-python">Streaming Embedded OLAP Data Pipeline with LanceDB + DuckDB in Python</a> (May 2024)</p></li></ul><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/0x70b0451b1C047ed750C4661B4624B67FD94A31c5/-gq99zeoHqQxc1ViSJV4135u2ZgaH8v6q_WacrPECSA">Fetching Uniswap Historical Swap Data With Python and Subgrounds</a> (November 2023)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/0x70b0451b1C047ed750C4661B4624B67FD94A31c5/86O5gGvnrm3q_J2ZCAkZZL0o9ernHSGVMvoX564hzlA">Free Historical Blockchain Extraction with Cryo + Merkle Reth Nodes</a> (November 2023)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/0x70b0451b1C047ed750C4661B4624B67FD94A31c5/P-ZBadPVJLENgsLKoxgA9nPGchVARK9QoD0EfaNQeh8">Streamline Subgraph Pipeline Creation With DataStreams</a> (January 2023)</p></li></ul><h2 style="text-align: start" id="h-mev" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>MEV</strong></h2><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/preconf.eth/xX2wu_3DC76qVYy1GHi1WVeOV2cXDF_rtDh7GlD6ZEU"><strong>Tackling Blob Fee Slippage with Preconfirmations</strong></a><strong> </strong>(September 2024)</p></li></ul><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@evandekim/setup-a-solana-etl-pipeline-and-calculate-jito-tips">Calculate Jito Tips in Solana</a> (July 2024)</p></li></ul><ul><li><p style="text-align: start"><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/preconf.eth/6lZYL62DR9U14KC7wCC4RHReVdHcBeMy5PKeHVbPq5k">Analyzing Blob Inclusion Rates and Market Strategies </a>(April 2024)</p></li></ul><ul><li><p style="text-align: start"><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/preconf.eth/cxUO8pPBfqnqAlzFUzoEUa6sgnr68DRmsNhBWPb2u-c">Censorship, Latency, and Preconfirmations in the Blob Market</a> (February 2024)</p></li></ul><ul><li><p style="text-align: start"><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://frontier.tech/measuring-reordering-slippage-in-mev">Trading in the Dark: Measuring Reordering Slippage in MEV </a>(January 2024)</p></li><li><p style="text-align: start"><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/0x70b0451b1C047ed750C4661B4624B67FD94A31c5/8yqC4SPzhEqeG5ZyNNIWtlP6oQxi5Nr1oO7wVrF3_w0">Cross-Chain NFT Marketplace MEV Strategy with Artemis: A Technical Commentary</a> (May 2023)</p></li></ul><ul><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/Xb8XRL7nD-ORyeX76nrG6eOiTYOMTmKfOxEfMEpBae0"><u>Using Topological Data Analysis to Identify Distinct MEV&nbsp;Behavior</u></a> (blog, November 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/Evan-Kim2028/tda_ohm_analysis/blob/main/Using_Topological_Data_Analysis_to_Identify_MEV_Behavior.pdf"><u>Using Topological Data Analysis to Identify Distinct MEV Behavior</u></a> (paper, November 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/Mc11J16dVP7Ervk1r2Sx_wkJ7dzb7Ce60Y2EpbRBlHY"><u>MEV Arbitrage on Olympus</u></a> (October 2022)</p></li></ul><h2 style="text-align: start" id="h-defi" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>DeFi</strong></h2><ul><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/mohammed7s/Feedlot/blob/main/report/build/report.pdf"><u>Feedlot: trading with safe, low latency price oracles</u></a> (April 2023)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/YPI5RbKi9CyIEgpnrFtmnyACHMtTHM9FBsMJFjdwLXI"><u>Applying Balancer’s Managed Pool Controllers to Manage Liquidity in Bonding&nbsp;Curves</u></a> (December 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/0KZf_XYMCfjgy7fng4nJL_2JOH2k1UOgb_deXoIQg_A"><u>Pari-Mutuel Market Design</u></a> (August 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/SVp-wan8A0VtZVff8YgUKTQAhJlpKa4CPXNVKqW_d5o"><u>The AMM Design Trilemma</u></a> (August 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://primitive.mirror.xyz/wXPmtmaO4yvhyra36XKqwX36gM0qmR0X3YNTOBZB5Kk"><u>RMM-01 Concentrated Liquidity Analysis</u></a> (July 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/aUULTZFwhJ9XsOZ6XIbAYl1iaSncDKpDwKazCemJlI8"><u>Replicating Market Makers and Super Hedging Portfolios</u></a> (May 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/Q2tdbKQjTyrSixC4YaElqr83zJE0gUIOIzltxFa-VIQ"><u>Liquidity Management and Supply Issuance Optimization Model</u></a> (April 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/_6voXoEEfJqy58fNAhQjDWo6K3PvDeCxubfFBC6oAes"><u>Quantitative Modeling on Rebase Tokens&nbsp;(Ohm)</u></a> (January 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/ujJJbOtc25RAGJMewlPahBUcDyozOJaBF7qznEET3ik"><u>Crypto Speculation Index (CSI)</u></a> (January 2022)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/pExa2KveQ5B3WWSyWEW_iVKi2Ka3oqeRdfkjYBxBIhE"><u>Why Decentralized Algorithmic Stablecoins Win the Stablecoin Wars (UST,&nbsp;BEAN)</u></a> (December 2021)</p></li></ul><ul><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://mirror.xyz/evandekim.eth/fuznGoiFzMmDk6iMdunDejkPiuRbJ5yqDRzg7-q2IYA"><u>Applications of Graph Theory on the Structure of a DAO</u></a> (November 2021)</p></li></ul><h2 style="text-align: start" id="h-math" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Math</strong></h2><ul><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/Evan-Kim2028/math-studies/blob/main/quadratic_reciprocity_lucas_polynomials.pdf"><u>Quadratic Reciprocity of Lucas Polynomials</u></a> (December 2021)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/Evan-Kim2028/math-studies/blob/main/MAT402_Grobner_Basis_Presentation_4.22.21.nb"><u>Groebner Basis and Automated Geometric Theorem Solver</u></a> (Mathematica, April 2021)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/Evan-Kim2028/math-studies/blob/main/independent_study_measure_theory_lebesgue_integration_notes.pdf"><u>Measure Theory - Lebesgue Integration</u></a> (May 2020)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/Evan-Kim2028/math-studies/blob/main/independent_study_abstract_algebra_notes.pdf"><u>Abstract Algebra - Groups, Kernels, Normal Subgroups</u></a> (November 2020)</p></li><li><p><a target="_blank" rel="noreferrer" class="dont-break-out" href="https://github.com/Evan-Kim2028/math-studies/blob/main/independent_study_perceptron_neural_network_notes.pdf"><u>Perceptron Neural Networks</u></a> (May 2020)</p></li></ul><br><br>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>defi</category>
            <category>mev</category>
            <category>data engineering</category>
            <category>data science</category>
            <category>web3</category>
            <category>crypto</category>
            <category>python</category>
            <category>solana</category>
            <category>sui</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/2215a709752c58f4e14c761e693a609e.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Streaming Embedded OLAP Data Pipeline with LanceDB + DuckDB in Python]]></title>
            <link>https://paragraph.com/@evandekim/streaming-embedded-olap-data-pipeline-with-lancedb-duckdb-in-python</link>
            <guid>SmUxjOipU1iyuOFkS6az</guid>
            <pubDate>Sun, 12 May 2024 20:01:49 GMT</pubDate>
            <description><![CDATA[IntroThis post goes over a simple solution to create an embedded streaming OLAP data pipeline that leverages historical blockchain data from Hypersyn...]]></description>
            <content:encoded><![CDATA[<div class="relative header-and-anchor"><h2 id="h-intro">Intro</h2></div><p>This post goes over a simple solution to create an embedded streaming OLAP data pipeline that leverages historical blockchain data from <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/enviodev/hypersync-client-python"><u>Hypersync</u></a> and mutable columnar storage format <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://lancedb.github.io/lance/"><u>Lance</u></a>. Using the new python package <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://pypi.org/project/lancedb-tables/0.1.1/">lancedb-tables</a>, it is easy to create LanceDB tables, update without complex file management systems, and stream real time data.&nbsp;</p><div class="relative header-and-anchor"><h2 id="h-python-data-ecosystem">Python Data Ecosystem&nbsp;</h2></div><p>Python has been experiencing massive leaps and bounds in open source data standards via the Apache Arrow standard. Apache Arrow plays a significant role in standardizing the Python data ecosystem by providing a unified, language-agnostic columnar memory format for data. This standardization is crucial for several reasons:</p><ul><li><p>System Interoperability: Arrow's language-agnostic format enhances data exchange across diverse systems and languages, minimizing serialization costs between different data processing tools. For example, Arrow enables seamless transition between dataframe and SQL apis.</p></li><li><p>Community and Ecosystem Growth: Wide adoption across languages fosters a robust developer community, driving continuous enhancements in Arrow and its libraries.</p></li><li><p>Future-Proofing Data Engineering: Arrow standardizes data formats and processing methods, supporting large-scale analytics and future data engineering innovations in Python.</p></li></ul><p>Apache Arrow makes it possible to have seamless integration between LanceDB, DuckDB, and Polars.</p><div class="relative header-and-anchor"><h2 id="h-data-pipeline-stack">Data Pipeline Stack</h2></div><ul><li><p>Hypersync - Query historical blockchain data</p></li><li><p>LanceDB - write data to a LanceDB table</p></li><li><p>DuckDB - OLAP Queries on the LanceDB table</p></li></ul><div class="relative header-and-anchor"><h3 id="h-hypersync">Hypersync</h3></div><p>Envio is a real-time indexing solution specifically designed for EVM-compatible blockchains. It provides developers with Hypersync, a more efficient way to sync and aggregate blockchain data compared to JSON-RPC, offering up to 20-100x faster syncing on historical data as a result.</p><p>The following example utilizes the <code>lancedb-tables</code> package to sync a sample of 50,000 blocks from Hypersync to a local LanceDB table. Example code and instructions can be found in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Evan-Kim2028/hypersync_lancedb"><u>this repository</u></a>. The Hypersync client is used to query blocks and transactions from blocks 19,825,000 to 19,850,000. Then running the back fill table example will then back fill the LanceDB table from blocks 19,800,000 to 19,825,000. </p><p>Polars is used as an analytical preprocessing API of choice to perform a bit of data cleaning before writing data into the LanceDB table.</p><div class="relative header-and-anchor"><h3 id="h-lancedb">LanceDB</h3></div><p>LanceDB contains a powerful writer that makes it very simple to read and write new Lance datasets. The key reason is that LanceDB decouples read and write operations. This is by design as all LanceDB files are read-only. New data writing operations create new fragments without affecting existing data, allowing for independent operation of read and write nodes in a distributed environment.&nbsp;</p><p>This design ensures that read operations on current data remain unaffected by concurrent writes. This makes the Lance format behave as a <strong>mutable</strong> columnar storage format. In contrast, parquet is a <strong>immutable</strong> columnar storage format and requires additional overhead and code complexity to manage streaming data.</p><div class="relative header-and-anchor"><h3 id="h-duckdb">DuckDB</h3></div><p>The example notebook <code>analytics.ipynb</code> is an easy example that shows how easy it is to add OLAP functionality by using DuckDB on top of a LanceDB table. Notably using DuckDB to count the number of unique blocks is much faster than Polars, on a magnitude of about 10x.</p><div class="relative header-and-anchor"><h3 id="h-conclusion">Conclusion</h3></div><p>This was a short example of how to leverage a modern data engineering stack to build a local embedded OLAP streaming data pipeline with ease in a local environment. Follow up writings will show how to save this pipeline in a cloud environment and add more data sources.</p><p></p>]]></content:encoded>
            <author>evandekim@newsletter.paragraph.com (Defi, Data, Degen)</author>
            <category>defi</category>
            <category>data engineering</category>
            <category>embedded olap</category>
            <category>pipeline</category>
            <category>lancedb</category>
            <category>duckdb</category>
            <category>ethereum</category>
            <category>blockchain</category>
            <category>on-chain data</category>
            <category>polars</category>
            <category>python</category>
            <category>data streaming</category>
            <category>sql</category>
            <category>hypersync</category>
        </item>
    </channel>
</rss>