<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Degen Code]]></title><description><![CDATA[The complete guide to building Ethereum trading & arbitrage bots.]]></description><link>https://www.degencode.com</link><image><url>https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png</url><title>Degen Code</title><link>https://www.degencode.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 14 Jun 2026 12:01:13 GMT</lastBuildDate><atom:link href="https://www.degencode.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[BowTiedDevil]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[degencode@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[degencode@substack.com]]></itunes:email><itunes:name><![CDATA[BowTiedDevil]]></itunes:name></itunes:owner><itunes:author><![CDATA[BowTiedDevil]]></itunes:author><googleplay:owner><![CDATA[degencode@substack.com]]></googleplay:owner><googleplay:email><![CDATA[degencode@substack.com]]></googleplay:email><googleplay:author><![CDATA[BowTiedDevil]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Optimizing Smart Contracts With Autoresearch]]></title><description><![CDATA[I&#8217;ve been using AutoResearch (AR) to improve my smart contracts, and you should too.]]></description><link>https://www.degencode.com/p/optimizing-smart-contracts-with-autoresearch</link><guid isPermaLink="false">https://www.degencode.com/p/optimizing-smart-contracts-with-autoresearch</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Tue, 09 Jun 2026 18:34:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!KH5X!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa8b359-0a8c-4e01-ab07-0253c3c62c4c_2400x1350.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve been using <a href="https://github.com/karpathy/autoresearch">AutoResearch</a> (AR) to improve smart contracts.</p><p>I don&#8217;t see AR discussed often enough, and I think it suffers from a branding problem. </p><p>The name suggests it is a tool for finding obscure info, digging into scientific papers, or summarizing dense source material. But it&#8217;s really much more practical. </p><p>At a high level, AR is a simple technique for performing a series of structured experiments, measuring the impact to some evaluation metric, and choosing to keep or discard those individual changes.</p><h2>Multi-DEX Execution</h2><p>At the end of my <a href="https://www.degencode.com/t/uniswap-v4">Uniswap V4</a> series, I introduced a gnarly &#8220;virtual machine&#8221; command-based executor contract for handling swaps between multiple DEX and pool versions.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d15fe5ca-61b6-4431-a2a3-33aff24cdd8b&quot;,&quot;caption&quot;:&quot;In Part II, I presented a set of fake contracts for testing, plus a series of tests to demonstrate the execution commands needed for arbitrarily sequenced Uniswap V3 swaps.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Uniswap V4/V3 Multi-Pool Arbitrage&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-05-22T17:38:00.329Z&quot;,&quot;cover_image&quot;:null,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/uniswap-v4-v3-multi-pool-arbitrage-part-iii-uniswap-v4-commands&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:164173508,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:5,&quot;comment_count&quot;:2,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>It was inspired by the Uniswap V4 Router design which used byte encoding of arbitrary commands across multiple contracts supported by the exchange.</p><p>Its complexity made it a real pain to build, test, and debug. So I got it working, ensured it satisfied my synthetic test cases, ran some live arbitrage to confirm it was functional, then moved on.</p><p>But there was a lot of potential left on the table!</p><p>At the end of last year, AI models got really good and CLI coding harnesses became very sophisticated. Suddenly I could dedicate a lot of idle computing resources to background refinement of techniques I had already started, like the VM executor.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;7fe99343-c9f1-4332-b26d-806a82b9ddfe&quot;,&quot;caption&quot;:&quot;I have been using AI to chew through some significant Aave V3 complexity recently.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Agentic Coding For Degens&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-02-10T23:05:46.363Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!vlNY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/agentic-coding&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:187546090,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:13,&quot;comment_count&quot;:2,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><h2>The Goal</h2><p>Ultimately I want to build a flexible executor contract that supports command-based execution of atomic swaps across any mixture of Uniswap V2, V3, and V4 pools (and all forks).</p><h2>The Method</h2><p>Building the contract will be done through a series of AR refinements to a single <a href="https://www.degencode.com/p/generalized-vyper-smart-contracts">executor smart contract</a> using Vyper version 0.4.3.</p><p>Tests will be written using <a href="https://www.degencode.com/p/testing-with-ape-framework">Ape Framework</a> version 0.8.50.</p><p>The AI harness is <a href="https://pi.dev/">Pi</a> version 0.79.0 using the <a href="https://www.npmjs.com/package/pi-autoresearch">pi-autoresearch</a> extension. The backend AI model is GLM-5.1.</p><h2>Goal: V2-Only Optimization</h2><p>It would be overwhelming to bring the full set of Uniswap versions in scope initially. So let&#8217;s start with something really simple: build a contract that can execute a chain of Uniswap V2 pool swaps, then do AR runs on it until we&#8217;ve optimized it and see what we get.</p><p>I&#8217;ve already created a set of fake contracts that I can use for simulating Uniswap V2 pools and their underlying ERC-20 tokens.</p><p><code>fake_erc20.vy</code></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;9c94a872-c9e5-4393-85bb-d79e72f6a1e8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from ethereum.ercs import IERC20
from ethereum.ercs import IERC20Detailed

implements: IERC20
implements: IERC20Detailed

# ERC20 events inherited from IERC20 &#8212; no local definitions needed.

name: public(String[32])
symbol: public(String[32])
decimals: public(uint8)

MINTER: immutable(address)

totalSupply: public(uint256)
balanceOf: public(HashMap[address, uint256])
allowance: public(HashMap[address, HashMap[address, uint256]])


@deploy
def __init__(_name: String[32], _symbol: String[32], decimals: uint8, nominal_initial_supply: uint256):
    MINTER = msg.sender
    init_supply: uint256 = nominal_initial_supply * 10 ** convert(decimals, uint256)
    self.name = _name
    self.symbol = _symbol
    self.decimals = decimals
    self.balanceOf[msg.sender] = init_supply
    self.totalSupply = init_supply


@external
def transfer(_to : address, _value : uint256) -&gt; bool:    
    self.balanceOf[msg.sender] -= _value
    self.balanceOf[_to] += _value
    log IERC20.Transfer(msg.sender, _to, _value)
    return True


@external
def transferFrom(_from : address, _to : address, _value : uint256) -&gt; bool:
    self.balanceOf[_from] -= _value
    self.balanceOf[_to] += _value
    self.allowance[_from][msg.sender] -= _value
    log IERC20.Transfer(_from, _to, _value)
    return True


@external
def approve(_spender : address, _value : uint256) -&gt; bool:    
    self.allowance[msg.sender][_spender] = _value
    log IERC20.Approval(msg.sender, _spender, _value)
    return True


@external
def mint(_to: address, _value: uint256):
    assert msg.sender == MINTER
    assert _to != empty(address)
    self.totalSupply += _value
    self.balanceOf[_to] += _value
    log IERC20.Transfer(empty(address), _to, _value)


@internal
def _burn(_to: address, _value: uint256):
    """
    @dev Internal function that burns an amount of the token of a given
         account.
    @param _to The account whose tokens will be burned.
    @param _value The amount that will be burned.
    """
    assert _to != empty(address)
    self.totalSupply -= _value
    self.balanceOf[_to] -= _value
    log IERC20.Transfer(_to, empty(address), _value)


@external
def burn(_value: uint256):
    """
    @dev Burn an amount of the token of msg.sender.
    @param _value The amount that will be burned.
    """
    self._burn(msg.sender, _value)


@external
def burnFrom(_to: address, _value: uint256):
    """
    @dev Burn an amount of the token from a given account.
    @param _to The account whose tokens will be burned.
    @param _value The amount that will be burned.
    """
    self.allowance[_to][msg.sender] -= _value
    self._burn(_to, _value)</code></pre></div><p><code>fake_weth.vy</code></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;c65a5f14-b778-46a8-8dcf-5f3f8572f446&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">import fake_erc20
from ethereum.ercs import IERC20
initializes: fake_erc20
exports: (
    fake_erc20.IERC20,
    fake_erc20.IERC20Detailed,
    fake_erc20.mint,
    fake_erc20.burn,    
)

@deploy
@payable
def __init__(
    name: String[32], symbol: String[32], decimals: uint8, supply: uint256
):
    fake_erc20.__init__(name, symbol, decimals, supply)


@external
@payable
def deposit():
    fake_erc20.totalSupply += msg.value
    fake_erc20.balanceOf[msg.sender] += msg.value
    log IERC20.Transfer(empty(address), msg.sender, msg.value)


@external
def withdraw(amount: uint256):
    fake_erc20.totalSupply -= amount
    fake_erc20.balanceOf[msg.sender] -= amount
    log IERC20.Transfer(msg.sender, empty(address), amount)
    raw_call(
        msg.sender, 
        b'',
        value=amount
    )</code></pre></div><p><code>fake_uniswap_v2_pair.vy</code></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;3d5f1633-1a78-4123-9a2a-95a40e17d8fc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">"""
Fake Uniswap V2 pair for testing.

Mimics the V2 swap interface with the same invariant enforcement as the
real UniswapV2Pair.sol:
- Reentrancy guard (matches real V2's `unlocked` flag)
- Snapshot balances BEFORE the callback (to compute input amounts via delta)
- K-invariant check after callback (constant product minus fee)
- Callback only invoked when data.length &gt; 0 (matches real V2)

The V2 swap is an optimistic transfer: output tokens are sent to `to`
BEFORE any input is received. If data is non-empty, a callback
(uniswapV2Call) is invoked on `to`, giving the caller a chance to
pay the input tokens. After the callback (or immediately if no callback),
the pair verifies the constant product invariant.

Unlike V3/V4, there is no auto-pay &#8212; the executor must transfer tokens
to the pair before or during the callback.

V2 is constant-product: two reserves, no complex tick math. The pair
enforces the K-invariant at runtime via balance deltas &#8212; no pre-
configured swap amounts are needed. Just mint liquidity tokens to the
pair and call sync() to initialize reserves, then call swap() with
any amount up to the reserves.

Liquidity setup:
  1. Mint both tokens to the pair (owner or any address)
  2. Call sync() to snapshot current balances as reserves
  3. swap() is now ready &#8212; it will enforce K-invariant

Fee parameter: swap_fee is a fraction of 10000.
  Uniswap / SushiSwap: 30 (0.3%)
  PancakeSwap:         25 (0.25%)
"""

from .interfaces.UniswapV2 import IUniswapV2Pair
from .interfaces.UniswapV2 import IUniswapV2Callee
from .interfaces.UniswapV2 import IHookCallee
from .interfaces.UniswapV2 import IPancakeCallee
from ethereum.ercs import IERC20

MAX_CALLDATA_LENGTH: constant(uint256) = 512

implements: IUniswapV2Pair

OWNER: immutable(address)
SWAP_FEE: immutable(uint256)  # Fee as fraction of 10000 (30 = 0.3%, 25 = 0.25%)

token0: public(address)
token1: public(address)

# Which callback selector to invoke: 0 = uniswapV2Call, 1 = hook, 2 = pancakeCall
callback_variant: public(uint256)

# Pre-configured swap amounts (optional &#8212; for backward compatibility).
# When non-zero, swap() verifies the exact output matches these values
# IN ADDITION to the K-invariant.
amount_in: public(uint256)
amount_out: public(uint256)

# Reentrancy guard &#8212; matches real V2's `unlocked` flag
unlocked: public(uint256)

# Simulated reserves for getReserves(), initialized by sync(). 
# Used by V2 math (getAmountOut/getAmountIn)
reserve0: public(uint112)
reserve1: public(uint112)
block_timestamp_last: public(uint32)


@external
@view
def getReserves() -&gt; (uint112, uint112, uint32):
    return (self.reserve0, self.reserve1, self.block_timestamp_last)


@deploy
def __init__(token0: address, token1: address, _callback_variant: uint256, _swap_fee: uint256):
    OWNER = msg.sender
    self.token0 = token0
    self.token1 = token1
    self.callback_variant = _callback_variant
    SWAP_FEE = _swap_fee
    self.unlocked = 1


@external
@nonpayable
def sync():
    """Update reserves to match current token balances.

    Call after minting liquidity tokens to the pair. This snapshots
    current balances as reserves so that swap() can enforce the
    K-invariant and getReserves() returns correct values for V2 math.
    """
    self.reserve0 = convert(staticcall IERC20(self.token0).balanceOf(self), uint112)
    self.reserve1 = convert(staticcall IERC20(self.token1).balanceOf(self), uint112)
    log IUniswapV2Pair.Sync(self.reserve0, self.reserve1)


@external
@nonpayable
def reset():
    """Drain all tokens and clear state &#8212; for test teardown / reuse.

    Transfers all token0 and token1 balances back to the contract owner,
    then zeroes reserves and swap config so the pair can be set up fresh.
    """
    assert msg.sender == OWNER
    bal0: uint256 = staticcall IERC20(self.token0).balanceOf(self)
    bal1: uint256 = staticcall IERC20(self.token1).balanceOf(self)
    if bal0 &gt; 0:
        extcall IERC20(self.token0).transfer(empty(address), bal0)
    if bal1 &gt; 0:
        extcall IERC20(self.token1).transfer(empty(address), bal1)
    self.reserve0 = 0
    self.reserve1 = 0
    self.amount_in = 0
    self.amount_out = 0


@external
@nonpayable
def swap(
    amount0Out: uint256,
    amount1Out: uint256,
    to: address,
    data: Bytes[MAX_CALLDATA_LENGTH],
):
    """
    Perform a V2 swap with constant-product invariant enforcement.

    This matches the real UniswapV2Pair.sol flow:
    1. Reentrancy guard
    2. Output amounts &gt; 0 and &lt;= reserves
    3. to != token0 and to != token1
    4. (Optional) Verify output matches pre-configured amount_out
    5. Optimistic transfer of output tokens
    6. Callback if data.length &gt; 0
    7. Compute input amounts via balance delta
    8. K-invariant check: balance0Adjusted * balance1Adjusted &gt;= reserve0 * reserve1 * 10000^2
    9. Update reserves to match balances
    """
    # &#9472;&#9472; Reentrancy guard (matches real V2's lock modifier) &#9472;&#9472;
    assert self.unlocked == 1, "UniswapV2: LOCKED"
    self.unlocked = 0

    assert amount0Out &gt; 0 or amount1Out &gt; 0, "UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT"

    # &#9472;&#9472; Reserve checks (matches real V2) &#9472;&#9472;
    assert amount0Out &lt;= convert(self.reserve0, uint256), "UniswapV2: INSUFFICIENT_LIQUIDITY"
    assert amount1Out &lt;= convert(self.reserve1, uint256), "UniswapV2: INSUFFICIENT_LIQUIDITY"

    # &#9472;&#9472; to != token addresses (matches real V2) &#9472;&#9472;
    assert to != self.token0 and to != self.token1, "UniswapV2: INVALID_TO"

    # Snapshot reserve values BEFORE the optimistic transfer + callback
    # (matches real V2's getReserves() before the transfer)
    _reserve0: uint112 = self.reserve0
    _reserve1: uint112 = self.reserve1

    # &#9472;&#9472; Optimistic transfer of output tokens (matches real V2) &#9472;&#9472;
    if amount0Out &gt; 0:
        extcall IERC20(self.token0).transfer(to, amount0Out)
    if amount1Out &gt; 0:
        extcall IERC20(self.token1).transfer(to, amount1Out)

    # &#9472;&#9472; Invoke callback if data is non-empty (matches real V2) &#9472;&#9472;
    if len(data) &gt; 0:
        if self.callback_variant == 0:
            extcall IUniswapV2Callee(to).uniswapV2Call(
                msg.sender, amount0Out, amount1Out, data
            )
        elif self.callback_variant == 1:
            extcall IHookCallee(to).hook(
                msg.sender, amount0Out, amount1Out, data
            )
        else:
            extcall IPancakeCallee(to).pancakeCall(
                msg.sender, amount0Out, amount1Out, data
            )

    # &#9472;&#9472; Read post-callback balances (matches real V2) &#9472;&#9472;
    balance0: uint256 = staticcall IERC20(self.token0).balanceOf(self)
    balance1: uint256 = staticcall IERC20(self.token1).balanceOf(self)

    # &#9472;&#9472; Compute input amounts via balance DELTA (matches real V2) &#9472;&#9472;
    # Real V2: amountNIn = balanceN &gt; _reserveN - amountNOut ? balanceN - (_reserveN - amountNOut) : 0
    # In Solidity, underflow wraps; in Vyper it reverts. Handle the edge case
    # where amountNOut == _reserveN (allowed by &lt;= check) by using a safe
    # formulation: if balanceN + amountNOut &gt; _reserveN, then amountNIn is
    # the surplus over the expected post-transfer balance.
    amount0In: uint256 = (balance0 + amount0Out - convert(_reserve0, uint256)) if (balance0 + amount0Out &gt; convert(_reserve0, uint256)) else 0
    amount1In: uint256 = (balance1 + amount1Out - convert(_reserve1, uint256)) if (balance1 + amount1Out &gt; convert(_reserve1, uint256)) else 0
    
    assert amount0In &gt; 0 or amount1In &gt; 0, "UniswapV2: INSUFFICIENT_INPUT_AMOUNT"

    # &#9472;&#9472; K-invariant check &#9472;&#9472;    
    balance0_adjusted: uint256 = balance0 * 10000 - amount0In * SWAP_FEE
    balance1_adjusted: uint256 = balance1 * 10000 - amount1In * SWAP_FEE
    assert balance0_adjusted * balance1_adjusted &gt;= convert(_reserve0, uint256) * convert(_reserve1, uint256) * 10000 * 10000, "UniswapV2: K"

    # &#9472;&#9472; Update reserves to match actual balances (matches real V2's _update) &#9472;&#9472;
    self.reserve0 = convert(balance0, uint112)
    self.reserve1 = convert(balance1, uint112)

    # &#9472;&#9472; Emit events (matches real V2) &#9472;&#9472;
    log IUniswapV2Pair.Sync(self.reserve0, self.reserve1)
    log IUniswapV2Pair.Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to)

    self.amount_in = 0
    self.amount_out = 0
    self.unlocked = 1


@external
@payable
def __default__():
    return</code></pre></div><h3>Initial Contract</h3><p>Ask the AI to write the initial contract:</p><blockquote><p>Write a Vyper smart contract, <code>command_executor.vy</code>, to support executing encoded byte command streams at transaction time. The contract will support up to 255 unique 1 byte commands (<code>0x00</code>-<code>0xFE</code>) plus a 1 byte separator (<code>0xFF</code>). </p><p>All encoded commands shall be structured as concatenated bytes with fixed offsets:</p><p>  [1 byte] - COMMAND<br>  [X bytes][Y bytes][Z bytes] - arguments necessary for execution of the given function<br>  [1 byte] - SEPARATOR</p><p>The contract will use immutable variables set at deployment time, starting with these: <br>  <code>OWNER</code>, holding the deployer address<br>  <code>WETH_ADDR</code>, holding the Wrapped Ether contract address</p><p>The contract shall not use durable storage. The contract may use transient storage using the <code>transient</code> keyword to pass data across call frames where needed.</p><p>Provide a fallback function <code>__default__</code> which accepts zero-calldata Ether transfers but reject all other calls.</p><p>Create a single external function called <code>execute_commands</code>. Protect the function with a top-level assertion: <code>msg.sender == self.OWNER</code>. </p><p>Provide initial support for one command: <code>CMD_V2_SWAP_EXPLICIT_AMOUNTS</code> that performs a <code>swap</code> call at a given Uniswap V2 pool.</p></blockquote><p>It generated this contract:</p><p><code>command_executor.vy</code></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;4154101e-076a-4a5a-832e-8fb34d5b5561&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">"""
Command Executor &#8212; parses and dispatches an encoded byte stream of
commands at transaction time.

Encoding (per command):
    [1 byte ]  COMMAND_ID       (0x00&#8211;0xFE valid; 0xFF is separator)
    [X bytes]  arg1
    [Y bytes]  arg2
    [Z bytes]  arg3
    [1 byte ]  SEPARATOR        (0xFF)

X, Y, Z are fixed per command type and must be known by the encoder.

No durable storage is used. Immutables are set at deploy time and
transient storage is available for cross-call-frame data passing
(e.g. reentrancy guard, flash-swap callback verification).
"""

from .interfaces.UniswapV2 import IUniswapV2Pair

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# Constants
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

COMMAND_SEPARATOR: constant(uint8) = 255

# Command IDs (0x00&#8211;0xFE; 0xFF reserved as separator)
CMD_V2_SWAP_EXPLICIT_AMOUNTS: constant(uint8) = 0

# V2 swap argument sizes (bytes)
V2_POOL_LEN: constant(uint256) = 20
V2_AMOUNT_LEN: constant(uint256) = 32
V2_RECIPIENT_LEN: constant(uint256) = 20
V2_ARGS_LEN: constant(uint256) = 20 + 32 + 32 + 20  # 104 bytes total

# Maximum number of commands per call (Vyper 0.4.x uses for/range, not while)
MAX_COMMANDS: constant(uint256) = 128

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# Immutables
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

OWNER: immutable(address)
WETH_ADDR: immutable(address)

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# Transient storage &#8212; auto-cleared at end of each transaction (EIP-1153)
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

# Reentrancy guard: 0 = unlocked, 1 = locked.
# Uses transient storage instead of durable storage so the slot is
# automatically reset between transactions, saving gas and avoiding
# the need for a persistent state variable.
_locked: transient(uint256)

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# Events
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

event CommandExecuted:
    command_id: uint8

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# Constructor
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

@deploy
def __init__(weth_addr: address):
    OWNER = msg.sender
    WETH_ADDR = weth_addr

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# Fallback &#8212; accept plain ETH transfers only
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

@payable
@external
def __default__():
    """@notice Accepts zero-calldata Ether transfers and rejects all other calls. """
    assert len(msg.data) == 0, "only plain ETH transfers"

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# External entry point
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

@external
def execute_commands(encoded_commands: Bytes[4096]) -&gt; bool:
    """
    @notice Parse and execute an encoded command stream.
    @dev Each command: [1B COMMAND_ID][N bytes args][1B SEPARATOR=0xFF].
         0x00 CMD_V2_SWAP_EXPLICIT_AMOUNTS:
           20B pool | 32B amount0Out | 32B amount1Out | 20B recipient
    @param encoded_commands Concatenated bytes of command+args+separator tuples.
    @return True when all commands have executed successfully.
    """
    # &#9472;&#9472; Access control &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
    assert msg.sender == OWNER, "unauthorized"

    # &#9472;&#9472; Reentrancy guard (transient) &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
    assert self._locked == 0, "reentrant call"
    self._locked = 1

    cursor: uint256 = 0
    end: uint256 = len(encoded_commands)

    for _: uint256 in range(MAX_COMMANDS):
        if cursor &gt;= end:
            break
        # &#9472;&#9472; Read command byte &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
        cmd: uint8 = self._read_uint8(encoded_commands, cursor)
        cursor += 1

        # &#9472;&#9472; Dispatch &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
        if cmd == CMD_V2_SWAP_EXPLICIT_AMOUNTS:
            # Args: [20B pool][32B amount0Out][32B amount1Out][20B recipient]
            pool: address        = self._read_address(encoded_commands, cursor)
            cursor += V2_POOL_LEN

            amount0_out: uint256 = self._read_uint256(encoded_commands, cursor)
            cursor += V2_AMOUNT_LEN

            amount1_out: uint256 = self._read_uint256(encoded_commands, cursor)
            cursor += V2_AMOUNT_LEN

            recipient: address   = self._read_address(encoded_commands, cursor)
            cursor += V2_RECIPIENT_LEN

            self._v2_swap(pool, amount0_out, amount1_out, recipient)

        else:
            assert False, "unknown command"

        # &#9472;&#9472; Validate separator &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
        sep: uint8 = self._read_uint8(encoded_commands, cursor)
        cursor += 1
        assert sep == COMMAND_SEPARATOR, "invalid separator"

        log CommandExecuted(command_id=cmd)

    # &#9472;&#9472; Unlock (transient storage auto-clears on revert) &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
    self._locked = 0
    return True

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# Internal &#8212; byte-parsing helpers
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

@internal
@pure
def _read_uint8(data: Bytes[4096], offset: uint256) -&gt; uint8:
    """@dev Read a single byte as uint8 at the given offset. """
    return convert(convert(convert(slice(data, offset, 1), bytes32), uint256) &gt;&gt; 248, uint8)


@internal
@pure
def _read_address(data: Bytes[4096], offset: uint256) -&gt; address:
    """Read a 20-byte address at *offset*."""
    return convert(slice(data, offset, 20), address)


@internal
@pure
def _read_uint256(data: Bytes[4096], offset: uint256) -&gt; uint256:
    """Read a 32-byte uint256 at *offset*."""
    return convert(slice(data, offset, 32), uint256)

# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
# Internal &#8212; command handlers
# &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;

@internal
def _v2_swap(
    pool: address,
    amount0_out: uint256,
    amount1_out: uint256,
    recipient: address,
):
    """
    Execute a swap() call on a Uniswap V2 pair.

    Calls pair.swap(amount0Out, amount1Out, to, "") with empty data
    (no flash-swap callback).
    """
    extcall IUniswapV2Pair(pool).swap(
        amount0_out, amount1_out, recipient, b""
    )</code></pre></div><h3>Experiment Setup</h3><p>The AR extension comes with an <code>autoresearch-create</code> skill, so I load it and have this brief interaction:</p><blockquote><p><em>I have the autoresearch skill loaded. What would you like me to optimize? I need to know:</em></p><p><em>1. Goal &#8212; What are we optimizing?<br>2. Command/Benchmark &#8212; How do we measure it?<br>3. Metric &#8212; What number are we trying to improve (and which direction)?<br>4. Constraints &#8212; Any hard rules (tests must pass, no new deps, etc.)?            </em></p><p><em>Or if you&#8217;d like me to explore the codebase first to suggest an optimization target, I can do that too.</em></p></blockquote><p>I respond:</p><blockquote><p>I want to create an autoresearch task with an optimization target of <strong>minimizing total gas use</strong> of a transaction that captures profit from a synthetic arbitrage scenario through three Uniswap V2 pools (V2-V2-V2). </p><p>Build an Ape test that generates the profit scenario using <code>@contracts/fake_erc20.vy</code>, <code>@contracts/fake_uniswap_v2_pair.vy</code>, and <code>@contracts/fake_weth.vy</code>. </p><p>Build fake pools as <code>pytest</code> fixtures, initialize them using fake tokens, assign the tokens and mint pool reserves that result in a profit after fees. The test must assert that the transaction results in a positive gross WETH profit at the executor contract, but net WETH profit (considering gas fees) may be ignored.</p></blockquote><p>The model considered this for a while, then added a new command to the contract to support ERC20 transfers between pools. I already know this is sub-optimal because V2 pairs support direct transfers between themselves, but that&#8217;s fine. The AR loop should be able to discover this!</p><p>The model then wrote an experiment log (<code>log.jsonl</code>), configuration (<code>config.json</code>), scripts for experiment measurement (<code>measure.sh</code>) and checking (<code>checks.sh</code>), and a prompt for the model (<code>prompt.md</code>).</p><p>Then it ran a baseline test for the starting V2-V2-V2 scenario: 202,464 gas.</p><h3>Run</h3><p>Then I started the AR run using the extension command <code>/autoresearch minimize V2-V2-V2 gas use</code>. </p><p>It quickly started iterating over improvements and opened an experiment result overlay in the terminal showing results of its experiments:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8YId!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8YId!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png 424w, https://substackcdn.com/image/fetch/$s_!8YId!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png 848w, https://substackcdn.com/image/fetch/$s_!8YId!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png 1272w, https://substackcdn.com/image/fetch/$s_!8YId!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8YId!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png" width="1456" height="1588" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1588,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:392876,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/201203448?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8YId!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png 424w, https://substackcdn.com/image/fetch/$s_!8YId!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png 848w, https://substackcdn.com/image/fetch/$s_!8YId!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png 1272w, https://substackcdn.com/image/fetch/$s_!8YId!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca41c9e5-41db-4a3a-9921-43ac380d8601_1990x2170.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It ultimately made 18 passes, reducing the gas use to 169,765 with a hyper-optimized contract that only includes one dedicated V2-V2-V2 function:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;ed2c339b-60ee-4c8a-8ce2-a6284b9a1f3f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">"""
Command Executor &#8212; specialized for V2-V2-V2 triangle arbitrage.
Uses direct-to-next-pool swap routing to eliminate intermediate transfers.
"""

from ethereum.ercs import IERC20
from .interfaces.UniswapV2 import IUniswapV2Pair

OWNER: immutable(address)
WETH: immutable(address)
_locked: transient(uint256)

@deploy
def __init__(weth: address):
    OWNER = msg.sender
    WETH = weth

@external
def execute_v2_v2_v2(
    pool1: address, 
    pay_amount1: uint256,
    a0out1: uint256, 
    a1out1: uint256,
    pool2: address, 
    a0out2: uint256, 
    a1out2: uint256,
    pool3: address, 
    a0out3: uint256, 
    a1out3: uint256,
) -&gt; bool:
    assert msg.sender == OWNER, "unauthorized"
    assert self._locked == 0, "reentrant call"
    self._locked = 1

    extcall IERC20(WETH).transfer(pool1, pay_amount1)
    extcall IUniswapV2Pair(pool1).swap(a0out1, a1out1, pool2, b"")
    extcall IUniswapV2Pair(pool2).swap(a0out2, a1out2, pool3, b"")
    extcall IUniswapV2Pair(pool3).swap(a0out3, a1out3, self, b"")

    return True</code></pre></div><p>I want to highlight one nice optimization that AR found: Since Uniswap V2 performs the post-swap check against reserve values held in storage, you can transfer tokens to it before calling <code>swap</code>. So you can effectively transfer the intermediate amounts directly between pools by passing pool2&#8217;s address to the <code>swap</code> call at pool1. This reduces the number of ERC-20 transfers to the theoretical minimum, <em>n_pools + 1</em>.</p><p>But you&#8217;ll also noticed that is stripped the command stream mechanism and transient storage features completely! This makes sense because the feedback mechanism only tracked the gas use of a V2-V2-V2 chained swap, so it was correct to focus on that slice and ruthlessly cut anything not supportive of that goal. Hard-coding the swaps is absolutely the right call there, but there are more optimizations still on the table.</p><p><strong>IMPORTANT LESSON: optimizations selected by the AR loop are downstream of the feedback mechanism.</strong></p><p>Close that AR loop by running <code>/autoresearch off</code>, then start another with a new more specific goal: /<code>autoresearch Optimize V2-V2-V2 arbitrage without relying on an initial balance held by the execution contract.</code></p>
      <p>
          <a href="https://www.degencode.com/p/optimizing-smart-contracts-with-autoresearch">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Numerical Optimization — Part V: Closed-Form Solutions]]></title><description><![CDATA[The Answer Hiding in Plain Sight]]></description><link>https://www.degencode.com/p/numerical-optimization-part-v-closed-form-solutions</link><guid isPermaLink="false">https://www.degencode.com/p/numerical-optimization-part-v-closed-form-solutions</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Wed, 20 May 2026 18:11:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a href="https://www.degencode.com/p/numerical-optimization-part-iv-newtonian-methods">Part IV</a>, while deriving analytical gradients for the Newtonian methods benchmark, we paused on the simple case of two V2 pools with the same token pair. The profit function <em>P</em>(<em>x</em>) and its derivative <em>dP/dx</em>&#8203; both had clean algebraic expressions. Setting the derivative to zero and solving algebraically gave us the optimal trade size in a single step:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{dP}{dx} = f^2 \\cdot \\frac{R_{ETH}^B \\cdot R_{USDC}^B}{(R_{USDC}^B + x_{eff})^2} \\cdot \\frac{R_{USDC}^A \\cdot R_{ETH}^A}{(R_{ETH}^A + ETH_{eff})^2} - 1&quot;,&quot;id&quot;:&quot;TROWVIFQOG&quot;}" data-component-name="LatexBlockToDOM"></div><p>Setting this to zero and solving numerically gave the optimal trade size: <em>x* </em>&#8776;<em> 7,011 USDC, P </em>&#8776;<em> 50 USDC</em>. </p><p>This is <strong>not</strong> itself a closed-form solution &#8212; it&#8217;s numerical root-finding on the gradient, which is faster than iterative optimization on <em>P(x)</em> but still not an explicit formula. The key observation, though, was that the gradient equation <em>dP/dx = 0</em> is an algebraic equation in <em>x</em>, not a transcendental one. That means it admits a closed-form solution, but we didn&#8217;t derive it.</p><h3><strong>Deriving the Closed-Form Solution</strong></h3><p>Let&#8217;s close that loop now. Rewriting Part IV&#8217;s derivative in our reserve notation (<em>a</em> = ETH in Pool A, <em>b</em> = USDC in Pool A, <em>c</em> = ETH in Pool B, <em>d</em> = USDC in Pool B, <em>f</em> = fee factor), and noting that <em>x_eff</em>&#8203; = <em>fx</em> and <em>ETH_eff&#8203; = f&#8901;eth</em> where <em>eth = f&#8901;c&#8901;x/(d+f&#8901;x)</em>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{dP}{dx} = f^2 \\cdot \\frac{c \\cdot d}{(d + fx)^2} \\cdot \\frac{a \\cdot b}{(a + f \\cdot \\text{eth})^2} - 1 = 0&quot;,&quot;id&quot;:&quot;GLTKMIIQTQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Move the 1 to the other side:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;f^2 \\cdot \\frac{c \\cdot d}{(d + fx)^2} \\cdot \\frac{a \\cdot b}{(a + f \\cdot \\text{eth})^2} = 1&quot;,&quot;id&quot;:&quot;BTQLIVNWIH&quot;}" data-component-name="LatexBlockToDOM"></div><p>Substitute:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;f^2 \\cdot \\frac{c \\cdot d}{(d + fx)^2} \\cdot \\frac{a \\cdot b}{\\left(a + \\frac{f^2 c x}{d + fx}\\right)^2} = 1&quot;,&quot;id&quot;:&quot;SRZPZVFOXT&quot;}" data-component-name="LatexBlockToDOM"></div><p>Clean up the nested denominator:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;a + \\frac{f^2 c x}{d + fx} = \\frac{a(d + fx) + f^2 c x}{d + fx} = \\frac{ad + afx + f^2 c x}{d + fx} = \\frac{ad + fx(a + fc)}{d + fx}&quot;,&quot;id&quot;:&quot;DULUWWMUGH&quot;}" data-component-name="LatexBlockToDOM"></div><p>Substitute back, then transform the fraction in the denominator:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{a \\cdot b}{\\left(\\frac{ad + fx(a + fc)}{d + fx}\\right)^2} = a \\cdot b \\cdot \\frac{(d + fx)^2}{(ad + fx(a + fc))^2}&quot;,&quot;id&quot;:&quot;YOMMWDWGQQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Cancel terms:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{aligned}\nf^2 \\cdot \\frac{c \\cdot d}{\\cancel{(d + fx)^2}} \\cdot a \\cdot b \\cdot \\frac{\\cancel{(d + fx)^2}}{(ad + fx(a + fc))^2} = 1\n\\\\\n\\frac{f^2 \\cdot a \\cdot b \\cdot c \\cdot d}{(ad + fx(a + fc))^2} = 1\n\\end{aligned} &quot;,&quot;id&quot;:&quot;LRHARVFMXR&quot;}" data-component-name="LatexBlockToDOM"></div><p>Take the square root of both sides, only considering the positive root of 1:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{f\\sqrt{abcd}}{ad + fx(a + fc)} = 1&quot;,&quot;id&quot;:&quot;GWQHYGGMJN&quot;}" data-component-name="LatexBlockToDOM"></div><p>Solve for <em>x</em>:&#8203;&#8203;</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{array}\nf\\sqrt{abcd} = ad + fx(a + fc)\n\\\\\nfx(a + fc) = f\\sqrt{abcd} - ad\n\\\\\nx^* = \\frac{f\\sqrt{abcd} - ad}{f(a + fc)}\n\\end{array}&quot;,&quot;id&quot;:&quot;EKKCWSWQCW&quot;}" data-component-name="LatexBlockToDOM"></div><p>This takes us from the Part IV problem setup to its closed-form solution. </p><h2><strong>Other Closed-Form Solutions</strong></h2><p>Multiple independent derivations of closed-form solutions exist for different liquidity pool designs. </p><p>I&#8217;m sure other solutions exist that I have not yet seen, so treat this section as a survey useful primarily as introduction.</p><h3><strong>Angeris et al. (2019) &#8212; Single Pool vs. Reference Market</strong></h3><p><strong>Paper</strong>: <a href="https://arxiv.org/abs/1911.03380">An Analysis of Uniswap Markets</a> (arXiv:1911.03380)</p><p><strong>Setting</strong>: A single constant product pool with reserves <em>R_&#945;</em>&#8203;, <em>R_&#946;</em>&#8203;, product <em>k </em>= <em>R_&#945;&#8901;R_&#946;</em>&#8203;, fee 1&#8722;<em>&#947;</em>, and an infinitely liquid external reference market with price <em>m_p</em>&#8203;.</p><p><strong>Result</strong>: The optimal arbitrage trade is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{aligned}\n&amp;\\Delta_\\alpha^* = \\left(R_\\alpha - \\sqrt{\\frac{k}{\\gamma \\cdot m_p}}\\right)^+\n\\\\\n&amp;\\text{where} \\ (x)^+ = \\max(x, 0)\n\\end{aligned}&quot;,&quot;id&quot;:&quot;KWESDDQBDT&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Significance</strong>: This is the foundational result. It shows the arbitrage problem is convex and admits a simple closed-form. The no-arbitrage condition <em>&#947;&#8901;m_p</em>&#8203; &#8804; <em>m_u </em>&#8203;&#8804; <em>&#947;^(</em>&#8722;1)<em>&#8901;m_p</em>&#8203; follows directly. The arbitrageur trades until the pool price equals the market price (adjusted for fees).</p><p><strong>Limitation</strong>: Only applies to single-pool arbitrage against an external market. Does not address cyclic arbitrage across multiple pools.</p><h3><strong>Angeris &amp; Chitra (2020) &#8212; CFMM Framework</strong></h3><p><strong>Paper</strong>: <a href="https://arxiv.org/abs/2003.10001">Improved Price Oracles: Constant Function Market Makers</a> (arXiv:2003.10001)</p><p><strong>Setting</strong>: General constant function market makers, including constant mean markets (Balancer) and Curve.</p><p><strong>Key results</strong>:</p><ul><li><p>The trading set of any CFMM used in practice is convex</p></li><li><p>The optimal arbitrage problem is efficiently solvable</p></li><li><p>Lower bounds on total reserve value via Fenchel conjugate</p></li></ul><p><strong>Significance</strong>: Provides the general convex optimization framework. Shows that <em>efficient</em> solutions always exist for CFMM arbitrage. The multi-asset formulation generalizes the single-pool result.</p><p><strong>Limitation</strong>: While convexity guarantees efficient numerical solutions, closed-form expressions are not available for all CFMM types (e.g., Curve reserve values).</p><h3><strong>Willetts &amp; Harrington (2024) &#8212; Multi-Token Pools</strong></h3><p><strong>Paper</strong>: <a href="https://arxiv.org/abs/2402.06731">Closed-form solutions for generic N-token AMM arbitrage</a> (arXiv:2402.06731)</p><p><strong>Setting</strong>: N-token geometric mean market maker pools (Balancer) with fees.</p><p><strong>Key result</strong>: For a pool with <em>N</em> tokens, reserves <strong>R</strong>, weights <strong>w</strong>, and fee <em>&#947;</em>, given a known trade signature <strong>s</strong> (which tokens are bought vs. sold), the optimal arbitrage trade for each active token <em>i</em> is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\Phi_i = \\gamma^{-d_i}\\left(\\breve{k}\\left(\\frac{\\breve{w}_i \\gamma^{d_i}}{m_{p,i}}\\right)^{1-\\breve{w}_i}\\prod_{j \\neq i}\\left(\\frac{m_{p,j}}{\\breve{w}_j \\gamma^{d_j}}\\right)^{\\breve{w}_j} - R_i\\right)&quot;,&quot;id&quot;:&quot;WGVQUOEPTY&quot;}" data-component-name="LatexBlockToDOM"></div><p>&#8203;&#8203;<strong>Trade signature search</strong>: For <em>N</em>-token pools, one must search over possible trade signatures. The number of valid signatures grows as roughly <em>3^N</em>, but:</p><ul><li><p>For <em>N &#8804; 5</em>, exhaustive search is faster than convex optimization</p></li><li><p>The search is embarrassingly parallel across signatures (GPU-friendly)</p></li><li><p>Heuristics can reduce the search space</p></li></ul><p><strong>Significance</strong>: First closed-form for <em>multi-asset</em> pools. In simulation, it outperforms CVXPY convex optimization and captures arbitrage opportunities sooner. In competitive dueling simulations, the closed-form arbitrageur wins even when the convex optimizer gets priority.</p><p><strong>Limitation</strong>: Requires knowing the trade signature a priori. Exhaustive signature search becomes expensive for <em>N </em>&gt; 7. Extends to amplified liquidity (Curve-style) via virtual reserves.</p><h3><strong>Hartigan (2026) &#8212; Multi-Hop Paths via M&#246;bius Transformations</strong></h3><p><strong>Article</strong>: <a href="https://medium.com/@jeffreyhartigan/the-geometry-of-arbitrage-generalizing-multi-hop-dex-paths-via-m%C3%B6bius-transformations-b56f992b01cd">&#8220;The Geometry of Arbitrage: Generalizing Multi-Hop DEX Paths via M&#246;bius Transformations&#8221;</a> (Medium: Jeffrey Hartigan)</p><p><strong>Setting</strong>: Multi-hop cyclic arbitrage through V2 (constant product) pools.</p><p><strong>Origin</strong>: &#8203;Hartigan derived the closed-form solution for the two-pool case in a different article, and assumed the result was a special case. He wrotes that multi-hop paths would &#8220;likely produce higher-degree polynomials that didn&#8217;t admit clean analytic solutions&#8221;.</p><p>Then while manually working out the input-output function for a 4-hop route, he noticed a continued-fraction structure forming. No matter how many pools he chained, the transfer function always collapsed into the same form:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{Kx}{M + Nx}&quot;,&quot;id&quot;:&quot;DGEOIRZEYV&quot;}" data-component-name="LatexBlockToDOM"></div><p>He recognized this as a <a href="https://en.wikipedia.org/wiki/M%C3%B6bius_transformation">M&#246;bius transformation</a>, which has a unique composition property that we will discuss in depth.</p><p><strong>Key result</strong>: The K/M/N recurrence and general formula:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;x^* = \\frac{\\sqrt{KM} - M}{N}&quot;,&quot;id&quot;:&quot;JTLOVBWRWY&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Performance claims</strong> (from Hartigan&#8217;s article):</p><ul><li><p>50&#8211;60x faster than binary search on CPU (single path)</p></li><li><p>Maps trivially to GPU tensor operations (promote K, M, N to batch tensors)</p></li><li><p>At ~10,000 parallel routes, GPU overtakes CPU</p></li><li><p>At 1,000,000 routes, GPU evaluates entire search space in ~50ms</p></li><li><p>Zero iterations regardless of path length</p></li></ul><p><strong>Significance</strong>: Hartigan&#8217;s article is the primary source for the M&#246;bius transformation insight and the K/M/N recurrence. It shows that the algebraic structure of constant product AMMs makes multi-hop cyclic arbitrage exactly solvable, and makes the deep connection between financial liquidity and the geometry of the complex plane &#8212; the 2&#215;2 matrices used to compose swap hops are the same mathematical objects that describe how the Riemann sphere maps onto itself and how relativistic velocities compose.</p><p><strong>Two notes on the N coefficient</strong>: </p><ol><li><p>The listed recurrence in Hartigan&#8217;s article (<em>N</em>&#8242; = <em>N</em>&#8901;<em>r_i</em>&#8203; + <em>M</em>&#8901;<em>f_i</em>&#8203;) has an error &#8212; it doesn&#8217;t match the matrix multiplication from which it derives. The matrix bottom-left entry gives <em>N</em>&#8242; = <em>f_i</em>&#8203;&#8901;<em>K </em>+ <em>r_i</em>&#8203;&#8901;<em>N</em> instead. The article&#8217;s own note that &#8220;N uses K before it is updated&#8221; directly contradicts the formula it accompanies: the formula references M, but the note references K. Since both the matrix derivation and Hartigan&#8217;s independently-verified 2-hop result confirm K, the note is correct and the formula has a likely typo (K written as M).  The listed formula yields <em>N </em>= <em>f</em>&#8901;(<em>a</em>+<em>d</em>) for the 2-hop case, while both the matrix <em>and</em> Hartigan&#8217;s independently-verified 2-hop result give <em>N </em>= <em>f</em>&#8901;(<em>b</em>&#8901;<em>f</em>+<em>d</em>). Notably, Hartigan&#8217;s own verification section is internally inconsistent with his recurrence: his 2-hop coefficients <em>(N = k</em>&#8901;<em>(b</em>&#8901;<em>k+d))</em> cannot be produced by applying his listed recurrence to the same setup.</p></li><li><p>Hartigan&#8217;s <em>N = f</em>&#8901;<em>(b</em>&#8901;<em>f + d)</em> and our <em>N = f</em>&#8901;<em>(a + f&#8901;c)</em> are both correct when considered for a given pool structure &#8212; they correspond to the same cyclic arbitrage starting from different tokens. Hartigan&#8217;s convention starts with Pool A (sell ETH, buy USDC), while ours starts with Pool B (input USDC, buy ETH). Because the matrix product <em>M_2&#8901;M_1 &#8800; M_1&#8901;M_2</em> in general, the N coefficient depends on which pool is the first hop. The K and M values are the same regardless of starting point.</p></li></ol><p><strong>Limitation</strong>: Only applies to constant product (V2) AMMs. Does not address V3 concentrated liquidity, Curve, or budget-constrained multi-path problems.</p><h3><strong>Community Derivations &#8212; Two-Pool Cyclic Formula</strong></h3><p><strong>Source</strong>: <a href="https://ethereum.stackexchange.com/questions/159056/uniswap-v2-optimal-arbitrage-amount">Ethereum StackExchange</a> </p><p><strong>Setting</strong>: Two V2 pools with the same token pair, cyclic path: input &#8594; AMM A &#8594; AMM B &#8594; output.</p><p><strong>Notation</strong>: AMM A has input reserve <em>y_a</em>&#8203;, output reserve <em>x_a</em>&#8203;; AMM B has input reserve <em>x_b</em>&#8203;, output reserve <em>y_b</em>&#8203;. Fee factor <em>f = 1&#8722;&#947;</em>.</p><p><strong>Result</strong>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{aligned}\n&amp;k = f \\cdot x_b + f^2 \\cdot x_a\n\\\\\n&amp;a_q = k^2\n\\\\\n&amp;b_q = 2k \\cdot y_a \\cdot x_b\n\\\\\n&amp;c_q = (y_a \\cdot x_b)^2 - f^2 \\cdot x_a \\cdot y_b \n\\cdot y_a \\cdot x_b\n\\\\\n&amp;r = \\frac{-b_q + \\sqrt{b_q^2 - 4a_q c_q}}{2a_q}\n\\end{aligned}&quot;,&quot;id&quot;:&quot;PERVSLDGWD&quot;}" data-component-name="LatexBlockToDOM"></div><p>&#8203;&#8203;&#8203;Note: <em>a_q</em>&#8203;, <em>b_q</em>&#8203;, <em>c_q</em>&#8203; are the <em>quadratic equation coefficients</em>, not pool reserves &#8212; a naming collision that makes this form confusing compared to the reserve-labeled version.</p><p><strong>Variable mapping to our notation</strong>: The community formula&#8217;s variables are defined by <em>direction in the path</em>, not by pool label:</p><ul><li><p><em>x_a</em>&#8203; = output reserve of AMM A (first hop) = ETH in Pool B = <em>c</em></p></li><li><p><em>y_a</em>&#8203; = input reserve of AMM A (first hop) = USDC in Pool B = <em>d</em></p></li><li><p><em>x_b</em>&#8203; = input reserve of AMM B (second hop) = ETH in Pool A = <em>a</em></p></li><li><p><em>y_b</em>&#8203; = output reserve of AMM B (second hop) = USDC in Pool A = <em>b</em></p></li></ul><p>With this mapping, <em>k </em>= <em>f</em>&#8901;<em>a</em>+<em>f^</em>2&#8901;<em>c </em>= <em>f</em>&#8901;(<em>a</em>+<em>f</em>&#8901;<em>c</em>), which is exactly the correct <em>N</em> from the M&#246;bius recurrence. This is <em>not</em> a coincidence &#8212; the community quadratic and the M&#246;bius formula solve the same <em>dP/dx = 0</em> equation, and the <em>k</em> in the community formula plays the same structural role as <em>N</em> in the M&#246;bius formula.</p><p><strong>Why it looks different from the M&#246;bius formula</strong>: This is the raw quadratic formula applied to the numerator of <em>dP</em>/<em>dx</em>=0 after clearing denominators. The numerator is a cubic that factors as (<em>d</em>+<em>fx</em>)&#215;(quadratic); the community formula solves the quadratic, while the M&#246;bius approach recognizes that the equation <em>K</em>&#8901;<em>M </em>= (<em>M</em>+<em>N</em>&#8901;<em>x</em>)^2 can be unwound via <em>K</em>&#8901;<em>M</em>&#8203; &#8212; collapsing the quadratic to a simple division. Both give the same answer. The community form requires no insight about M&#246;bius transformations &#8212; just mechanical algebra. It generalizes poorly beyond two pools (higher-degree numerators), which is why the M&#246;bius approach is preferred for multi-hop paths.</p><p><strong>Significance</strong>: This is what many practitioners implement first, because it requires no insight about M&#246;bius transformations &#8212; just mechanical algebra.</p><h3><strong>Wachter (2022) &#8212; ETH Zurich Thesis</strong></h3><p><strong>Paper</strong>: <a href="https://siwachter.com/assets/thesis.pdf">Distributed Computing: Arbitrage Opportunities on Decentralized Exchanges</a></p><p><strong>Setting</strong>: Cyclic arbitrage on Uniswap V2 and V3.</p><p><strong>Key result for V2</strong>: Setting the derivative of the profit function to zero and solving yields a closed-form expression for the optimal input. In Wachter&#8217;s notation (where <em>k_i</em>&#8203; denotes the constant product <em>Rx</em>_<em>i</em>&#8203; &#8901; <em>Ry</em>_<em>i</em>&#8203; for pool <em>i</em>), the solution is a ratio of reserve products and square roots thereof &#8212; algebraically equivalent to the other forms surveyed above, but expressed with different intermediate variables.</p><p><strong>V3 approach</strong>: For V3 pools with tick boundaries, ternary search (O(log n)) is used since closed-form is unavailable.</p><p><strong>Significance</strong>: Provides the V2 closed-form and explicitly acknowledges that V3 requires iterative methods.</p><h2><strong>Deriving the Solutions</strong></h2>
      <p>
          <a href="https://www.degencode.com/p/numerical-optimization-part-v-closed-form-solutions">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Numerical Optimization — Part IV: Newtonian Methods]]></title><description><![CDATA[Standing on the Shoulders of Giants]]></description><link>https://www.degencode.com/p/numerical-optimization-part-iv-newtonian-methods</link><guid isPermaLink="false">https://www.degencode.com/p/numerical-optimization-part-iv-newtonian-methods</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Wed, 13 May 2026 17:37:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The derivative-free methods covered in Parts <a href="https://www.degencode.com/p/numerical-optimization-part-ii-derivative-free-methods">II</a> and <a href="https://www.degencode.com/p/numerical-optimization-part-iii-derivative-free-multivariate-methods">III</a> treat the objective function as a black box, probing inputs and observing outputs without requiring knowledge of the internal structure. This flexibility is valuable, but it comes at a cost: without information about how the function changes, these methods may converge slowly, may fail to converge at all, and offer mixed guarantees about the result being globally optimal.</p><p>Part II covered the derivative as a measure of rate of change. From a physical landscape point of view, the derivative tells us the direction of steepest ascent or descent, and the second derivative reveals how quickly that direction is changing. Algorithms that exploit this information can make better choices during iteration, which can result in better performance compared to black box methods.</p><p>Derivative-free methods exhibit <strong>linear / geometric convergence</strong> &#8212; the error is linearly reduced by each iteration. Methods using derivatives can exhibit <strong>quadratic convergence</strong> &#8212; the error is exponentially reduced by each iteration.</p><p>Thus, derivative methods <em>can</em> converge faster than black-box methods.</p><h3><strong>Why &#8220;Newtonian&#8221;?</strong></h3><p>Isaac Newton <a href="https://en.wikipedia.org/wiki/Newton's_method">developed a method</a> for finding roots of equations &#8212; finding all <em>x</em> where <em>f(x) = 0</em>. The method converges on these values by repeatedly calculating a point (<em>x1</em>) adjacent to the previous point (<em>x0</em>):</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;x_{k+1} = x_k - \\frac{f(x_k)}{f'(x_k)}&quot;,&quot;id&quot;:&quot;HAOUDYYOWE&quot;}" data-component-name="LatexBlockToDOM"></div><p>The function <em>f</em> itself is arbitrary, so let&#8217;s apply a bit of mathematical bookkeeping to consider how this applies to derivatives. Define a new function <em>g</em> to represent the first derivative of <em>f</em>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;g(x) = f'(x)&quot;,&quot;id&quot;:&quot;WDLZTKVVTL&quot;}" data-component-name="LatexBlockToDOM"></div><p>Then apply Newton&#8217;s Method to <em>g(x)</em>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;g_{k+1} = g_k - \\frac{g(x_k)}{g'(x_k)}&quot;,&quot;id&quot;:&quot;IIUPQTTAMT&quot;}" data-component-name="LatexBlockToDOM"></div><p>We know from Part II that local optima of <em>f(x)</em> occur where <em>f&#8217;(x) = 0</em>. Optimizing <em>f(x)</em> is equivalent to using Newton&#8217;s Method on <em>g(x)</em>, which finds the points where <em>g(x) = f&#8217;(x) = 0</em></p><p>Many variations on Newton&#8217;s original algorithm have been developed. The term &#8220;Newtonian methods&#8221; now encompasses all optimization algorithms that use derivatives:</p><ul><li><p><strong>Newton&#8217;s Method</strong> uses the first and second derivative directly</p></li><li><p><strong>Quasi-Newton Methods</strong> approximate the second derivative by tracking previous values</p></li><li><p><strong>Gradient Descent</strong> <strong>Methods</strong> use only the first derivative</p></li><li><p><strong>Secant Methods</strong> approximate both derivatives</p></li></ul><h2>Multivariate Problems</h2><p>Newton&#8217;s Method can be applied directly as shown above to single variable optimization. But with some generalization, it can be used to find optima for multivariate functions.</p><p>To keep multivariate expressions concise, we introduce two new terms:</p><ul><li><p><strong>Gradient &#8212;</strong> a matrix of first-order partial derivatives for each variable</p></li><li><p><strong>Hessian &#8212;</strong> a matrix of second-order partial derivatives for each variable</p></li></ul><h3>The Gradient</h3><p>For a multivariate function <em>f(x)</em> where <strong>x</strong> is a vector, its gradient is the column vector of the first partial derivative of <em>f</em> for each element:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\nabla f(\\mathbf{x}) = \\begin{bmatrix} \\frac{\\partial f}{\\partial x_1} \\\\[6pt] \\frac{\\partial f}{\\partial x_2} \\\\[6pt] \\vdots \\\\[6pt] \\frac{\\partial f}{\\partial x_n} \\end{bmatrix}&quot;,&quot;id&quot;:&quot;CFFQWIZHMO&quot;}" data-component-name="LatexBlockToDOM"></div><p>The gradient points in the direction of steepest ascent. At a critical point (local maximum or minimum):</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\nabla f({\\mathbf{x}}^{*}) = \\begin{bmatrix} 0 \\\\[6pt] 0 \\\\[6pt] \\vdots \\\\[6pt] 0 \\end{bmatrix}&quot;,&quot;id&quot;:&quot;LDMUYPJSHW&quot;}" data-component-name="LatexBlockToDOM"></div><p><strong>Arbitrage Example</strong>: For a profit function <em>P(x_1, x_2)</em> over two paths, each partial derivative (&#8706;<em>P</em>/&#8706;<em>xi) </em>represents the marginal profit from increasing that path&#8217;s allocation. The gradient points toward increasing profits through allocation of additional capital in a given direction.</p><h3>The Hessian</h3><p>For a multivariate function <em>f(<strong>x</strong>)</em> where <strong>x</strong> is a vector, its Hessian is a <em>n x n</em> square matrix of the second partial derivative of <em>f</em> for each pair of elements:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H(\\mathbf{x}) = \\begin{bmatrix} \\frac{\\partial^2 f}{\\partial x_1^2} &amp; \\frac{\\partial^2 f}{\\partial x_1 \\partial x_2} &amp; \\cdots \\\\[6pt] \\frac{\\partial^2 f}{\\partial x_2 \\partial x_1} &amp; \\frac{\\partial^2 f}{\\partial x_2^2} &amp; \\cdots \\\\[6pt] \\vdots &amp; \\vdots &amp; \\ddots \\end{bmatrix}&quot;,&quot;id&quot;:&quot;OYHFXOUHZJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>From the Hessian, we can calculate a set of scalar components called <strong>eigenvalues</strong>. These &#8220;compressed&#8221; values classify the curvature of the function at the given point.</p><p>Inspecting the values tells us how a local point compares with the global:</p><ul><li><p>All positive &#8212; we have found a local minimum (movement to an adjacent region would increase <em>f</em>)</p></li><li><p>All negative &#8212; we have found a local maximum (movement to an adjacent would decrease <em>f</em>)</p></li><li><p>Mixed sign &#8212; we have found a saddle point (a movement to an adjacent region may decrease or increase <em>f</em>)</p></li><li><p>Some zero &#8212; we have found a flat region (movement to an adjacent region will not change <em>f</em>)</p></li></ul><p>The ratio of the largest to smallest eigenvalue is called the <strong>condition number</strong>, and indicates the &#8220;width&#8221; of the local region. Large condition numbers indicate a narrow valley which can be difficult to evaluate for gradient descent methods that ignore the Hessian.</p><h3>Multivariate General Form </h3><p>The steps above can be expressed for multivariate problems as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\mathbf{x}_{k+1} = \\mathbf{x}_k - \\frac{\\nabla f(\\mathbf{x}_k)}{H(\\mathbf{x}_k) }\n&quot;,&quot;id&quot;:&quot;EMWHTBTYYS&quot;}" data-component-name="LatexBlockToDOM"></div><p>which looks similar to the Newton&#8217;s Method form for <em>g(x)</em> above. You can see clearly that the gradient and Hessian represent the first and second derivative of <em>f(<strong>x</strong>)</em>.</p><h3>Strategy</h3><p>Newtonian methods share a common control flow:</p><ol><li><p>Model the function using derivatives</p></li><li><p>Predict where <em>&#8711;f = <strong>0</strong></em></p></li><li><p>Move toward that prediction</p></li><li><p>Repeat until convergence</p></li></ol><h3>Convergence</h3><p>The convergence criteria for a Newtonian method generally follow this structure:</p><ol><li><p>The Hessian must have no zero values at each iteration</p></li><li><p>The Hessian must be positive definite (all eigenvalues positive) near the optimum</p></li><li><p>The initial guess must be &#8220;sufficiently close&#8221; to the optimum</p></li></ol><p>Newtonian methods can fail to converge if they encounter a saddle point or a flat region, because the gradient will not point anywhere useful. They can also fail if the initial guess is too far from the true optimum &#8212; initial steps can overshoot the optimum and fail to refine on successive iterations. </p><p>Newtonian methods are therefore <strong>not deterministic</strong>. Several solutions have been developed to handle this:</p><ul><li><p><strong>Damped Newton Method</strong> &#8212; apply a damping factor to clamp the maximum step size, which increases the likelihood of convergence</p></li><li><p><strong>Hessian Regularization</strong> &#8212; apply a transformation to the Hessian if negative or near-zero eigenvalues are found</p></li><li><p><strong>Trust-region Methods</strong> &#8212; constrain the step size within some bound where the quadratic model (three terms representing the function, its first derivative, and its second derivative) is known to be accurate</p></li></ul><h2>Quasi-Newtonian Methods</h2><p>The quadratic model satisfies this constraint:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H \\Delta \\mathbf{x} = -\\nabla f&quot;,&quot;id&quot;:&quot;QBPSAKMWPZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>But to determine the next step for <strong>x</strong> means solving:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\Delta \\mathbf{x} = -H^{-1} \\nabla f&quot;,&quot;id&quot;:&quot;EEBHNMFYBM&quot;}" data-component-name="LatexBlockToDOM"></div><p>This can become a heavy operation since the Hessian dimension squares with the number of decision variables. A problem with a 10-dimensional decision variable requires determining a 10 x 10 Hessian and its inverse on each step.</p><p>Several methods have been proposed to approximate the Hessian and its inverse instead of calculating it directly. </p><p>The most basic form uses the secant equation to assume constant curvature between two points instead of allowing it to be fully variable. This relationship is expressed as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;H \\mathbf{s}_k = \\mathbf{y}_k&quot;,&quot;id&quot;:&quot;GMONWRKTUU&quot;}" data-component-name="LatexBlockToDOM"></div><p>where <strong>s</strong> is the step vector.</p><p>Using this relationship, the approximated Hessian still provides useful curvature feedback without the steep calculation penalty.</p><p>Thus, Quasi-Newtonian methods are all dedicated to finding some general approximation for <em>H</em>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;B_k \\approx H&quot;,&quot;id&quot;:&quot;IGXKCJANJA&quot;}" data-component-name="LatexBlockToDOM"></div><p>Several methods have been developed for this purpose:</p><ul><li><p><a href="https://docs.scipy.org/doc/scipy/reference/optimize.minimize-bfgs.html">BFGS</a> &#8212; an approximation that only accounts for the outer products of the step <strong>s</strong> and change <strong>y</strong></p></li><li><p><a href="https://docs.scipy.org/doc/scipy/reference/optimize.minimize-lbfgsb.html">L-BFGS</a> &#8212; a limited-memory variation on BFGS that does not store <em>Bk</em>, instead keeping only the last <em>m</em> step/gradient-change pairs and calculating intermediate products on demand</p></li><li><p><a href="https://docs.scipy.org/doc/scipy/reference/optimize.minimize-lbfgsb.html">L-BFGS-B</a> &#8212; an extension that allows for boundaries on variables, which can be very useful for problems where ranges of values could be nonsensical</p></li><li><p>DFP &#8212; a method that tracks the Hessian&#8217;s inverse directly, instead of calculating the Hessian and inverting it after</p></li></ul><h2>Gradient Descent Methods</h2><p>These methods improve calculation speed by ignoring the Hessian completely. There are several ways to proceed from a given point, knowing only the gradient:</p><ul><li><p>Steepest Descent &#8212; at each point, move in the direction of steepest descent, backtracking as needed until a convergence value is satisfied</p></li><li><p>Conjugate Gradient &#8212; at each step, apply constraints such that the next direction is orthogonal (e.g. at a right angle in two dimensions) to the previous step</p></li><li><p>Momentum &#8212; accelerate gradient descent by accumulating a &#8220;velocity&#8221; term across multiple steps</p></li></ul><p>Steepest descent methods are prone to slow convergence or overshooting errors (zig-zagging) if the step size is not optimized. Conjugate gradient methods solve that issue by moving at &#8220;right angles&#8221; through the search space, which minimize backtracking over multiple steps. These methods are particularly appealing for large problems with memory limitations. Momentum methods are good for problems with noisy functions with many flat or saddle regions, since they tend to move quickly through them in the direction of global extrema.</p><h2>Secant Methods</h2><p>These methods bridge the gap between the derivative-free and Newtonian approaches. They approximate derivatives from function values, which allows them to gain some of Newton's convergence speed without requiring explicit gradient computation.</p><p>Several methods are developed using this approach:</p><ul><li><p>Secant Approximation (Univariate) &#8212; assume a linear gradient, similar to the quasi-Newtonian Hessian approximation discussed above</p></li><li><p>Broyden&#8217;s Method &#8212; a multivariate generalization of the secant approximation that uses a rank-1 modification to determine the approximate Hessian across steps</p></li></ul><p>Broyden&#8217;s Method has two variants: one approximates H, the other approximates its inverse.</p><h2>Profit Function Derivatives</h2><p>All this theory is neat, but how do we actually express the arbitrage function and take its derivatives?</p><h3>Simple Case</h3><p>Start with the most basic example &#8212; Uniswap V2 arbitrage between two equivalent pairs (WETH-USDC) at some fee (gamma):</p><p>The structure of the arbitrage is that we sell WETH at Pool A and buy it at Pool B. We maximize profit in USDC by choosing the USDC forward amount <em>x</em>:</p>
      <p>
          <a href="https://www.degencode.com/p/numerical-optimization-part-iv-newtonian-methods">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Numerical Optimization — Part III: Derivative-Free Methods (Multivariate)]]></title><description><![CDATA[More Pools (Maybe) More Profits]]></description><link>https://www.degencode.com/p/numerical-optimization-part-iii-derivative-free-multivariate-methods</link><guid isPermaLink="false">https://www.degencode.com/p/numerical-optimization-part-iii-derivative-free-multivariate-methods</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Thu, 30 Apr 2026 20:37:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.degencode.com/p/numerical-optimization-part-ii-derivative-free-methods">Part II</a> covered derivative-free methods for the simple arbitrage problem: two pools holding the same token pair, optimizing for maximum profit from a single trade.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;5f6ae44f-93f0-4e63-af12-99bfd922b38f&quot;,&quot;caption&quot;:&quot;Part I outlined the structure of our optimization effort before concluding with a simple benchmark of SciPy&#8217;s minimize_scalar methods.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Numerical Optimization &#8212; Part II: Derivative-Free Methods&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-24T20:00:57.712Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!Trtf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7581bf0-17a0-4264-8090-9f123b975a2b_800x800.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/numerical-optimization-part-ii-derivative-free-methods&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:195079609,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:0,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Now we&#8217;ll turn our attention to a complex arbitrage problem: multiple pools holding an arbitrary set of token pairs, optimizing for a set of trades to maximize profit.</p><h2>Scalar vs. Vector</h2><p>In any of these optimization problems, we are concerned with determining a value for the <strong>decision variable</strong> that maximizes or minimizes some constraint.</p><p>For the simple arbitrage problem, the decision variable is the optimum starting trade (which is then chained through the fixed pool path) to maximize net profit. This one-dimensional value is also called a <strong>scalar</strong>. The SciPy function is named <code>minimize_scalar</code>, which gives us a strong hint about what kinds of problems it can solve.</p><p>For the complex arbitrage problem, the decision variable is a multi-dimensional <strong>vector</strong> that represents inputs to different pool paths.</p><p>These problems cannot be evaluated by the scalar methods, so we must use an appropriate multivariate method.</p><h2>The Parallel Arbitrage Problem</h2><p>Consider that a searcher has a set of arbitrage paths and wants to maximize profitable trades through them. </p><p>The decision variable <em><strong>x</strong></em> is a vector of amounts allocated to each path:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\mathbf{x} = \\left[ x_1, x_2, \\dots , x_n \\right]&quot;,&quot;id&quot;:&quot;MOCNSFUGNI&quot;}" data-component-name="LatexBlockToDOM"></div><p>The net profit is the sum of the net change in x across each path:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;P(\\mathbf{x}) = \\sum_{i=1}^{n} \\left( \\Delta x_i \\right)&quot;,&quot;id&quot;:&quot;OZBQUZOJHQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>We can impose additional constraints to clamp the solution to feasible values:</p><ul><li><p>The net profit must be positive</p></li><li><p>The maximum total trade is bounded by some value</p></li><li><p>The paths are independent (each pool is used only once)</p></li><li><p>Each trade satisfies the pool/protocol-specific constraints</p></li></ul><p>But since we&#8217;re using the &#8220;black box&#8221; method of calling calculation functions on our offline simulator, we can defer enforcement of the pool/protocol-specific constraints.</p><p>So the major constraints are setting the maximum size and constructing a set of independent paths.</p><h2>Methods</h2><p>For simplicity, we&#8217;ll evaluate a subset of methods built into <a href="https://scipy.org/">SciPy</a> version 1.17.1. </p><p>Using <a href="https://huggingface.co/moonshotai/Kimi-K2.6">Kimi K2.6</a> as the AI model, I started with a request to prepare a comparison of the available methods and their suitability for our complex arbitrage problem:</p><blockquote><h3><a href="https://docs.scipy.org/doc/scipy/reference/optimize.minimize-neldermead.html">Nelder-Mead</a></h3><p><strong>Mechanism:</strong> Maintains a simplex of <em>n</em>+1 points in the search space. Iteratively reflects, expands, contracts, and shrinks the simplex based on relative function values at the vertices.</p><p><strong>Properties:</strong></p><ul><li><p>Purely derivative-free; no gradient estimation.</p></li><li><p>Supports box bounds.</p></li><li><p>Does <strong>not</strong> support general inequality or equality constraints.</p></li><li><p>Deterministic given a starting point.</p></li><li><p>Well-suited to low-dimensional problems (<em>n </em>&#8804; 20) and noisy objectives.</p></li><li><p>Known to stall on problems with narrow curving valleys.</p></li></ul><p><strong>Constraint handling for capital budget:</strong> The sum constraint cannot be enforced natively. Possible workarounds:</p><ol><li><p><strong>Repair method:</strong> After each simplex vertex update, project onto the budget hyperplane by proportional scaling.</p></li><li><p><strong>Penalty method:</strong> Add &#955;&#8901;max&#8289;(0,&#8721;xi&#8722;C)^2 to the objective.</p></li></ol><p><strong>Expected behavior on arbitrage problems:</strong> Good for small <em>n</em> (2&#8211;5 paths) where the landscape is relatively smooth. Will struggle if paths have strongly differing scales (e.g., USDC-denominated inputs from 10^ to 10^9).</p><p></p><h3><a href="https://docs.scipy.org/doc/scipy/reference/optimize.minimize-powell.html">Powell</a></h3><p><strong>Mechanism:</strong> A conjugate-direction method. Performs sequential 1-D minimizations along a set of search directions, then updates the direction set based on the progress made. Avoids explicit gradients.</p><p><strong>Properties:</strong></p><ul><li><p>Derivative-free; no gradient or Hessian required.</p></li><li><p>Supports box bounds; evaluates outside bounds during the first iteration if the initial guess is outside, but respects bounds thereafter when provided.</p></li><li><p>Does <strong>not</strong> support general constraints.</p></li><li><p>Often faster than Nelder-Mead on smooth or approximately quadratic objectives.</p></li><li><p>Can accelerate along ridge-like structures in the objective surface.</p></li></ul><p><strong>Constraint handling for capital budget:</strong> Same as Nelder-Mead &#8212; repair or penalty required.</p><p><strong>Expected behavior on arbitrage problems:</strong> Likely more efficient than Nelder-Mead because the arbitrage profit landscape tends to be smooth (swap output is a continuous function of input). The conjugate-direction updates should exploit the near-quadratic structure around the peak.</p><p></p><h3><a href="https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyqa.html">COBYQA</a></h3><p><strong>Mechanism:</strong> Constrained Optimization BY Quadratic Approximations. A derivative-free trust-region SQP method. At each iteration, it builds quadratic models of the objective and each constraint from function evaluations, then solves a trust-region subproblem. </p><p><strong>Properties:</strong></p><ul><li><p>Derivative-free; constructs quadratic surrogate models.</p></li><li><p>Supports box bounds (treated as unrelaxable &#8212; always respected).</p></li><li><p>Supports general nonlinear inequality and equality constraints (also treated as unrelaxable).</p></li><li><p>Trust-region radius adapts automatically.</p></li><li><p>Stronger convergence guarantees than Nelder-Mead or Powell for smooth objectives.</p></li></ul><p><strong>Constraint handling for capital budget:</strong> The linear budget constraint &#8721;<em>xi</em>&#8203; &#8804; <em>C </em>can be passed directly as a <code>NonlinearConstraint</code> or <code>LinearConstraint</code>. This is a significant advantage &#8212; no repair or penalty tuning needed.</p><p><strong>Expected behavior on arbitrage problems:</strong> Best suited when the objective is smooth and constraints must be respected exactly. The overhead of quadratic model building is justified if evaluations are cheap (which they are for offline pool simulation). Should outperform Nelder-Mead and Powell on constrained problems with <em>n </em>&#8805; 3.</p><p></p><h3><a href="https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html">COBYLA</a></h3><p><strong>Mechanism:</strong> Constrained Optimization BY Linear Approximation. Uses linear (affine) models of the objective and constraints, updated iteratively. </p><p><strong>Properties:</strong></p><ul><li><p>Derivative-free; linear models are cheaper to build than quadratic.</p></li><li><p>Supports general nonlinear inequality constraints (equality constraints are internally converted to pairs of inequalities).</p></li><li><p>Supports box bounds via inequality constraints.</p></li><li><p>Simpler than COBYQA but less accurate for highly curved objectives.</p></li></ul><p><strong>Constraint handling for capital budget:</strong> Pass as a linear inequality constraint directly.</p><p><strong>Expected behavior on arbitrage problems:</strong> A lighter-weight alternative to COBYQA. May be faster per iteration but require more iterations if the profit landscape has significant curvature (which it does, due to AMM slippage).</p><p></p><h3><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html">Differential Evolution</a></h3><p><strong>Mechanism:</strong> A stochastic, population-based evolutionary algorithm. Maintains a population of candidate vectors. Each generation, every candidate is mutated (by adding scaled differences of randomly selected population members) and recombined with the original to form a trial vector. The better of the two survives.</p><p><strong>Properties:</strong></p><ul><li><p>Global optimizer; designed to escape local minima.</p></li><li><p>Derivative-free; uses only function value rankings.</p></li><li><p>Requires bounds for all variables (mandatory).</p></li><li><p>Supports linear and nonlinear constraints (inequality and equality) via the Lampinen approach.</p></li><li><p>Supports parallel evaluation via <code>workers=-1</code> or a custom map-like callable.</p></li><li><p>Supports vectorized evaluation via <code>vectorized=True</code> (passes all trial vectors at once).</p></li><li><p>Stochastic; requires a random seed for reproducibility.</p></li></ul><p><strong>Key parameters:</strong></p><ul><li><p><code>popsize</code>: Multiplier for population size (pop=popsize&#215;<em>n</em>).</p></li><li><p><code>mutation</code> (F): Differential weight, typically [0.5,1].</p></li><li><p><code>recombination</code> (CR): Crossover probability, typically 0.7.</p></li><li><p><code>strategy</code>: <code>'best1bin'</code> is the default; <code>'randtobest1bin'</code> often explores more aggressively.</p></li><li><p><code>polish</code>: If <code>True</code>, runs L-BFGS-B from the best DE solution at the end. Can be disabled for pure derivative-free results.</p></li></ul><p><strong>Constraint handling for capital budget:</strong> Pass as a <code>LinearConstraint</code> or <code>NonlinearConstraint</code>. The Lampinen constraint handling compares constraint violation along with objective value during selection.</p><p><strong>Expected behavior on arbitrage problems:</strong> Because evaluations are cheap, DE&#8217;s large population size is not a bottleneck. The <code>vectorized=True</code> option is particularly attractive: we can evaluate all trial vectors in a single batch call to our pool simulation infrastructure. DE should find the global optimum reliably but may converge more slowly than local methods once near the peak. Best used when <em>n</em> is moderate (<em>n </em>&#8804; 20) and there is concern about local optima (e.g., noisy or multi-modal profit landscapes from interacting pools).</p></blockquote><h2>Test &amp; Benchmark</h2><p>Then I asked the model to create a test harness to compare the methods, solve them and compare accuracy. </p><p>It created four scenarios and described them:</p>
      <p>
          <a href="https://www.degencode.com/p/numerical-optimization-part-iii-derivative-free-multivariate-methods">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Numerical Optimization — Part II: Derivative-Free Methods]]></title><description><![CDATA[Black Box Optimization]]></description><link>https://www.degencode.com/p/numerical-optimization-part-ii-derivative-free-methods</link><guid isPermaLink="false">https://www.degencode.com/p/numerical-optimization-part-ii-derivative-free-methods</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Fri, 24 Apr 2026 20:00:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Trtf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7581bf0-17a0-4264-8090-9f123b975a2b_800x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.degencode.com/p/numerical-optimization-part-i-introduction">Part I</a> outlined the structure of our optimization effort before concluding with a simple benchmark of SciPy&#8217;s <code>minimize_scalar</code> methods.</p><p>To understand how I landed on Brent as the default solver in <a href="https://github.com/BowTiedDevil/degenbot">degenbot</a>, we need to understand the major categories that classify optimization methods.</p><p><a href="https://en.wikipedia.org/wiki/Calculus">Calculus</a> is the branch of mathematics concerning rates of change. The most familiar operation in calculus is the <strong>derivative</strong>, which expresses the rate of change of some function with respect to its input. </p><p>I studied mechanical engineering so I often think of calculus operations with respect to a mapped physical system. </p><p>Let&#8217;s consider a function that maps the displacement of some object from its origin over time:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;x(t) = C \\cdot t&quot;,&quot;id&quot;:&quot;OZZZQVHAHA&quot;}" data-component-name="LatexBlockToDOM"></div><p>This is a simple linear displacement function whereby the object is displaced at a constant rate.</p><p>The velocity of the object over time is modeled by the first derivative of the original function:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;v(t) = \\frac{d}{dt} \\left( x(t) \\right)\n&quot;,&quot;id&quot;:&quot;IGAQVGXCPM&quot;}" data-component-name="LatexBlockToDOM"></div><p>The d/dt notation tells us that the derivative of the enclosed function <strong>f</strong> is taken with respect to the variable <strong>t</strong>. The &#8220;with respect to&#8221; clause is somewhat redundant for a function with only one variable. The distinction becomes important when evaluating systems with multiple variables.</p><p>The mechanics of taking derivatives are not relevant here, but the expression above resolves to:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;v(t) = C&quot;,&quot;id&quot;:&quot;FHZXOBQAUX&quot;}" data-component-name="LatexBlockToDOM"></div><p>Or more plainly, the velocity of the object is a constant value.</p><p>The second derivative can expressed over the original function or its first derivative:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\frac{d}{dt}(v(t)) = \\frac{d}{dt}\\left( \\frac{d}{dt} \\left( x(t) \\right) \\right)&quot;,&quot;id&quot;:&quot;ZPLQLHJJPQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>The second derivative of displacement represents acceleration. We know that an object at constant velocity is not accelerating, so that value is zero.</p><p>There are names for higher-order derivatives, but the first and second are usually all that you need to work through most problems.</p><p>These concepts will appear later when we study Newtonian methods.</p><h2>Fermat&#8217;s Theorem</h2><p>The study of the first derivative allows us to determine some of the system&#8217;s behavior. For systems that exhibit variable behavior, we can identify certain interesting points where the change becomes zero. This may indicate that the system has ceased to change (an object falls from a height and lands without bouncing), or that it has momentarily changed direction (that same object falls from a height, then bounces in the reverse direction).</p><p>In either case, the first derivative becomes zero are one or more points called local extrema. These indicates that the function has been maximized or minimized within some range. These local extrema may also represent global extrema, but not necessarily.</p><h2>Black Box Functions</h2><p>The example above is intentionally simplistic. For functions like these, we can solve for the minimum or maximum directly by taking the first derivative and solving that equation for all inputs that result in zero. Then we can inspect the original function at those values and easily identify the global extrema.</p><p>But life is not always easy, and we often want to optimize a system that can only be observed from the outside. These systems are &#8220;black boxes&#8221; because we have visibility on the inputs and outputs, but incomplete visibility on how they work. We can measure how the system changes empirically and approximate the first derivative, but the absence of a formal output function for that first derivative makes the direct solution infeasible.</p><h2>Derivative-Free Methods</h2><p>Several methods have been developed to handle the task of optimizing black box functions where the derivative cannot be calculated, or where that calculation is unacceptably costly.</p><p>These are commonly known as derivative-free algorithms, and they operate iteratively &#8212; varying the output and studying the change in output until converging on an optimum within some tolerance.</p><p>These methods are generally well understood and documented, which implies they are likely part of the AI training data used to build popular models. </p><p>Using Kimi K2.5, I described the test setup and asked it to summarize derivative-free methods available in SciPy and how they could be used to solve the problem. </p><p>This is the result:</p><blockquote><h3>Nelder-Mead Simplex Method</h3><p>Nelder-Mead maintains a geometric simplex of n+1 vertices (a triangle in 2D, tetrahedron in 3D) where each vertex represents a candidate solution vector of trade amounts across pools. At each iteration, it evaluates the profit at all vertices by delegating to pool classes, identifies the worst-performing vertex, and attempts to improve it by reflecting it through the centroid of the remaining vertices. Depending on how the reflected point compares to the current best and second-worst values, the method may expand (take a larger step), contract (take a smaller step toward the centroid), or shrink the entire simplex toward the best vertex. This approach requires only objective comparisons&#8212;no gradients or smoothness assumptions&#8212;making it robust to the piecewise-constant regions and sudden jumps created by trading fees and liquidity limits in AMM pools.</p><h3>Powell&#8217;s Method (Conjugate Direction Method)</h3><p>Powell&#8217;s method performs sequential line minimizations along a set of directions that are iteratively updated to become conjugate (non-interfering) based on the observed improvement patterns. Starting with coordinate axes as initial search directions, it minimizes the profit function along each direction in turn by finding the optimal step size, then constructs a new conjugate direction from the overall displacement achieved during the cycle. The method replaces the direction that contributed least to the total improvement with this new conjugate direction, gradually learning the local curvature of the objective without ever computing derivatives. While efficient for smooth, locally quadratic objectives, Powell&#8217;s method can stall when encountering flat regions or discontinuities common in arbitrage problems where profits remain constant until crossing a fee threshold or liquidity boundary.</p><h3>Differential Evolution (DE)</h3><p>Differential Evolution is a population-based evolutionary algorithm that maintains a fixed-size swarm of candidate solution vectors, each representing a complete set of trade amounts across pools. For each generation, every individual in the population creates a mutant vector by adding the weighted difference of two randomly selected population members to a third base individual, effectively exploring directions suggested by the population&#8217;s diversity. The mutant then undergoes crossover with the original individual to create a trial vector, which is evaluated against the current individual by delegating both to pool classes for profit calculation; the better performer survives to the next generation. This stochastic, parallel search naturally explores multimodal landscapes with multiple local profit peaks (arising from different pool path combinations), handles discontinuities without issue, and can be embarrassingly parallelized across available compute resources.</p><h3>Covariance Matrix Adaptation Evolution Strategy (CMA-ES)</h3><p>CMA-ES maintains a multivariate Gaussian distribution over the search space, where the mean represents the current best estimate of optimal trade amounts and the covariance matrix captures the pairwise correlations between amounts across different pools. At each iteration, it samples a population of candidate solutions from this distribution, evaluates their profit by delegating to pool classes, and updates the distribution parameters by re-weighting samples based on their ranked performance (not absolute values). The covariance matrix adaptation learns the local structure of the objective&#8212;such as the inherent coupling between trade amounts in multi-hop paths dictated by constant product formulas&#8212;allowing the algorithm to make elongated steps along valleys and navigate ill-conditioned problems efficiently. While highly sample-efficient and self-adaptive regarding step sizes, CMA-ES assumes local quadratic behavior and may stagnate if the profit landscape contains large truly flat regions where all sampled points yield identical values.</p><h3>Constrained Optimization by Linear Approximation (COBYLA)</h3><p>COBYLA is a trust-region algorithm designed for constrained black-box optimization that maintains both the current iterate and a set of &#8220;trust region&#8221; points used to construct linear approximations of the objective and constraint functions. At each iteration, it solves a linear programming subproblem using these approximations to find a step direction that improves profit while respecting constraints (such as slippage limits, available liquidity, or gas bounds), then evaluates the true objective and constraints at the proposed point by delegating to pool classes. The approximations are updated by replacing the oldest trust region point or adjusting the trust region radius based on how well the linear model predicted the actual function values. Unlike penalty-based methods, COBYLA maintains feasibility throughout the optimization (when started from a feasible point) and handles hard constraints natively without requiring derivative information, making it ideal for arbitrage scenarios where constraints like maximum slippage are non-negotiable safety limits.</p><h3>Bayesian Optimization (Gaussian Process-Based)</h3><p>Bayesian optimization builds a probabilistic surrogate model&#8212;typically a Gaussian Process&#8212;over the profit function, treating each evaluation of the black-box pool delegation as an observation that updates the posterior belief about the objective landscape. The Gaussian Process provides both a mean prediction of profit and an uncertainty estimate at any untested point, enabling the algorithm to use an acquisition function (such as Expected Improvement or Upper Confidence Bound) to intelligently select the next trade amount vector that optimally balances exploitation (high predicted profit) against exploration (high uncertainty). After each pool evaluation, the surrogate model is refit and the acquisition function optimized to suggest the next candidate, creating a highly sample-efficient search that minimizes expensive calls to slow or rate-limited pool simulations. While exceptionally efficient when evaluations are costly, the computational overhead of fitting Gaussian Processes scales cubically with the number of observations, making this approach suitable for offline path planning or high-stakes arbitrage validation rather than millisecond-latency production trading.</p><h3>Hooke-Jeeves Pattern Search</h3><p>Hooke-Jeeves performs a two-phase iteration consisting of exploratory moves and pattern moves: the exploratory phase samples points along each coordinate direction from the current base point by taking fixed-size steps, accepting any move that improves profit (even marginally), and uses the resulting point as the new base if any improvement was found. The pattern move then attempts to exploit the discovered trend by jumping from the original base point in the direction of the net displacement achieved during exploration, essentially accelerating along promising directions. If pattern moves continue to succeed, the step size is maintained or increased; if exploration fails to find improvement at a given resolution, the step size is reduced for finer local search. This deterministic, direction-based approach requires no derivatives and minimal bookkeeping, but its reliance on fixed exploratory step sizes and coordinate-aligned searches can lead to slow convergence on rotated or highly correlated objective landscapes like those arising from coupled pool interactions.</p><h3>Basin-Hopping with Local Refinement</h3><p>Basin-hopping is a two-stage global optimization strategy that combines random perturbations with local optimization to escape local profit maxima and find the global optimum across disconnected feasible regions. The algorithm starts from an initial trade amount vector and applies a local optimizer (such as Nelder-Mead or Powell&#8217;s method) to find the nearest local maximum, then randomly perturbs this solution by adding noise scaled to the problem dimensionality and accepts or rejects the new starting point based on a Metropolis criterion or strict improvement requirement. This process iterates, effectively allowing the solver to &#8220;hop&#8221; between basins of attraction in the profit landscape, which is essential for arbitrage problems where different pool paths create multiple distinct local optima. The method is particularly effective when combined with aggressive local optimizers that quickly refine each basin to its peak, trading off the number of expensive pool evaluations per hop against the probability of discovering the globally optimal arbitrage path.</p><h3>Simulated / Dual Annealing</h3><p>Simulated annealing is a probabilistic meta-heuristic inspired by the metallurgical annealing process, where the algorithm starts with high &#8220;temperature&#8221; allowing large random steps and gradually cools, reducing both step sizes and the probability of accepting worsening moves according to a Boltzmann-like acceptance criterion. At high temperatures, the solver explores broadly across the trade amount space, delegating to pools and accepting occasional profit decreases to escape local optima; as temperature decreases, it increasingly rejects inferior solutions and converges toward the global maximum. Dual annealing extends this with an adaptive strategy that combines classical annealing with local search visits and uses a fast annealing schedule for global exploration. While theoretically guaranteed to find the global optimum given infinite time and logarithmic cooling, simulated annealing typically requires many more objective evaluations than population-based methods to achieve comparable convergence, making it less suitable for time-sensitive arbitrage scenarios unless profit evaluations are extremely cheap.</p></blockquote><h2>Just Put My Benchmark in the Bag Bro</h2><p>That&#8217;s nice and all, but what we really care about it speed and accuracy. So let&#8217;s ask the model to implement and evaluate them:</p>
      <p>
          <a href="https://www.degencode.com/p/numerical-optimization-part-ii-derivative-free-methods">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Numerical Optimization — Part I: Introduction]]></title><description><![CDATA[Who Optimizes the Optimizers?]]></description><link>https://www.degencode.com/p/numerical-optimization-part-i-introduction</link><guid isPermaLink="false">https://www.degencode.com/p/numerical-optimization-part-i-introduction</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Tue, 21 Apr 2026 18:32:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>MEV searching boils down to a set of critical elements: pathfinding, numerical optimization, execution.</p><p>Numerical optimization is a broad category concerning the question &#8220;what is the optimal input that satisfies the constraints of my problem?&#8221;</p><p>This question is highly relevant to us. After identifying a price mismatch across some asset market, we must determine the most profitable route to capture value and how to size the associated trades.</p><p>I first covered numerical optimization with a lesson covering the Brent solver implemented in <a href="https://scipy.org/">SciPy</a>.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;f3c30410-d8fd-49c3-b1de-659f91c4d04e&quot;,&quot;caption&quot;:&quot;The trickiest part of a two-pool arbitrage is determining the optimal borrow amount.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Smart Contract Arbitrage &#8212; Optimizing the Input&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2022-03-16T04:52:36.958Z&quot;,&quot;cover_image&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/smart-contract-arbitrage-optimizing&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:50429039,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:23,&quot;comment_count&quot;:4,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Later I focused on convex optimization:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a82615b2-4962-4b2d-980f-a5d86e62fe96&quot;,&quot;caption&quot;:&quot;If you spend enough time on Twitter and follow smart people, you&#8217;ll see &#8220;convex optimization&#8221; mentioned.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Convex Optimization &#8212; Introduction&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-07-24T23:12:27.707Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4cc59c1a-da34-40ab-939d-ac79082a5e0b_860x809.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/convex-optimization-part-i-introduction&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:146861328,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:4,&quot;comment_count&quot;:0,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><h2>Playing To Your Strengths</h2><p>It&#8217;s important to understand your strengths and weaknesses, and even more important to choose efforts that are helped by your strengths, not harmed by your weaknesses, or ideally both.</p><p>I&#8217;m above average at abstract thinking, verbal reasoning, working memory, and synthesis (applying techniques from multiple domains). But I&#8217;m below average at mental processing speed, spatial reasoning, and numeric intuition. </p><p>This sets me up for trouble when I&#8217;m faced with certain classes of problems, and it compounds if that problem has multiple non-obvious solutions.</p><p>When I encounter some numerical optimization problem, I often have little intuition about how to attack it, falling back on brute-force methods and taking the first solution that works without spending much time on it.</p><p>Luckily, I now have access to highly capable AI tools that can fill in these gaps. </p><p>I already showed how to use AI to boost feature development and debugging.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;ab3fe280-8897-4693-8a2b-1cb9f352f3ad&quot;,&quot;caption&quot;:&quot;I have been using AI to chew through some significant Aave V3 complexity recently.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Agentic Coding For Degens&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-02-10T23:05:46.363Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!vlNY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/agentic-coding&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:187546090,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:13,&quot;comment_count&quot;:2,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Then I showed how to use it to quickly iterate on developing smart contracts.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d9c47046-08c0-4b66-abde-00cd3d3ca5fb&quot;,&quot;caption&quot;:&quot;With user data ready, we can turn our concentration to executing the liquidations.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Aave &#8212; Part IX: Execution Contract&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-04-10T20:59:11.280Z&quot;,&quot;cover_image&quot;:null,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/aave-part-ix-execution-contract&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:193502349,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:3,&quot;comment_count&quot;:0,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Now I&#8217;m going to demonstrate how I&#8217;m using it to refine parts of my code base with methods that would be tedious to apply by hand.</p><p>This will be a multi-part series because the scope is too broad for a single entry. Even after I reach a reasonable stopping point, I may continue adding new entries as I discover and implement new techniques.</p><h2>Problem &amp; Test Setup</h2><p>We need a way to compare methods across runs, so we need to build a reproducible set of inputs that model a relevant problem in our domain. </p><p>Since the majority of our work concerns liquidity pools from Uniswap and its various forks, we need a way to generate synthetic pool states associated with a given arbitrage scenario. I gave this to an agent, which ultimately proposed this architecture:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;markdown&quot;,&quot;nodeId&quot;:&quot;aecbf77b-8358-4aca-9275-7497cb8afc77&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-markdown">   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474;                         FixtureFactory                              &#9474;
   &#9474;  &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;  &#9474;
   &#9474;  &#9474;                    PoolStateGenerator                         &#9474;  &#9474;
   &#9474;  &#9474;  &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;  &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;  &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;&#9474;  &#9474;
   &#9474;  &#9474;  &#9474;  V2 Pools   &#9474;  &#9474;  V3 Pools   &#9474;  &#9474;  V4 Pools               &#9474;&#9474;  &#9474;
   &#9474;  &#9474;  &#9474;  (reserves) &#9474;  &#9474;(tick-based) &#9474;  &#9474;(pool ID + tick-based)   &#9474;&#9474;  &#9474;
   &#9474;  &#9474;  &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;  &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;  &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;&#9474;  &#9474;
   &#9474;  &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;  &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
                                 &#9474;
                                 &#9660;
                      &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
                      &#9474; ArbitrageCycleFixture&#9474;
                      &#9474; (frozen dataclass)   &#9474;
                      &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre></div><p>I&#8217;m omitting considerable detail on how this works under the hood, but the takeaway is that we can use this setup to mix and match arbitrage scenarios across the different pool types. </p><h3>Scenario: Basic Two-Pool</h3><p>Generate a basic scenario using two Uniswap V2 pools with a fixed price ratio and a target liquidity:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;bc82d4fb-b888-4b38-b882-25515a6161d7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def simple_v2_arb_profitable(self) -&gt; ArbitrageCycleFixture:
    """
    Two V2 pools with 2% price difference.

    Simple profit: arbitrage from higher to lower price pool.
    Pool A: 1 ETH = 2000 USDC (price = 2000)
    Pool B: 1 ETH = 1960 USDC (price = 1960, 2% lower)

    Arbitrage: Buy ETH in pool B (cheaper), sell in pool A (more expensive).
    """
    pool_a_address: ChecksumAddress = cast(
        "ChecksumAddress", "0x0000000000000000000000000000000000000001"
    )
    pool_b_address: ChecksumAddress = cast(
        "ChecksumAddress", "0x0000000000000000000000000000000000000002"
    )
    input_token_address: ChecksumAddress = cast(
        "ChecksumAddress", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
    )  # USDC

    # Generate pools with 2% price difference
    pool_a, pool_b = self._generator.generate_profitable_v2_pair(
        pool_a_address=pool_a_address,
        pool_b_address=pool_b_address,
        fee_a=Fraction(3, 1000),
        fee_b=Fraction(3, 1000),
        price_ratio=1.02,
        liquidity_base=10**21,  # ~1000 ETH equivalent
    )

    return ArbitrageCycleFixture(
        id="simple_v2_arb_profitable",
        cycle_type="v2_v2",
        pool_states={pool_a_address: pool_a, pool_b_address: pool_b},
        input_token_address=input_token_address,
        expected_optimal_input=0,  # Calculated by solver
        expected_profit=0,  # Calculated by solver
    )</code></pre></div><h2>SciPy Benchmark</h2><p>Now let&#8217;s compare the three methods implemented by SciPy&#8217;s <a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize_scalar.html">minimize_scalar</a> solver (Bounded, Brent, and Golden):</p>
      <p>
          <a href="https://www.degencode.com/p/numerical-optimization-part-i-introduction">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Aave — Part IX: Execution Contract]]></title><description><![CDATA[Getting AI To Do The Heavy Lifting]]></description><link>https://www.degencode.com/p/aave-part-ix-execution-contract</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-ix-execution-contract</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Fri, 10 Apr 2026 20:59:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With user data ready, we can turn our concentration to executing the liquidations.</p><p>I have built many execution contracts before for various projects (search the <a href="https://www.degencode.com/archive">archives</a> if you&#8217;d like a refresher on them).</p><p>They all have a similar structure:</p><ul><li><p>Validate that the caller is authorized, then process their inputs</p></li><li><p>Record the starting balance</p></li><li><p>Perform a sequence of validated actions across a set of smart contracts</p></li><li><p>Record the final balance, reverting if it has decreased</p></li></ul><p>This is a well-understood pattern with plenty of examples, which is an ideal setup for agentic AI. My last entry on agentic coding was well-received, so I expect this will be useful too.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;419a89a6-909b-4152-aea1-8dbb4805a806&quot;,&quot;caption&quot;:&quot;I have been using AI to chew through some significant Aave V3 complexity recently.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Agentic Coding For Degens&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:100}],&quot;post_date&quot;:&quot;2026-02-10T23:05:46.363Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!vlNY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/agentic-coding&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:187546090,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:12,&quot;comment_count&quot;:2,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><h2>Agent Setup</h2><p>For this effort, I created a new project in directory <code>~/code/aave_v3_liquidator</code> and created a minimal Python 3.12 virtual environment using <code>uv</code> with these packages:</p><ul><li><p><code>eth-ape</code> with the <code>recommended-plugins</code> extra</p></li><li><p><code>degenbot</code> 0.6.0a2</p></li><li><p><code>vyper</code> 0.4.3</p></li></ul><p>I used <a href="https://pi.dev/">Pi</a> version 0.66.1, calling <a href="https://huggingface.co/moonshotai/Kimi-K2.5">Kimi K2.5</a> through <a href="https://synthetic.new/?referral=zL07O85jYXTPOcZ">Synthetic</a>.</p><h2>Session Transcript</h2><p>You can review the <a href="https://pi.dev/session/#41f7fcca7c1a200148f24d0ddde6b06c">complete transcript</a> if you&#8217;re curious.</p><p>Let&#8217;s break down the session with some context to help read between the lines:</p><h2>The Task</h2><p>I started the session with a task definition:</p><blockquote><p>Help me prepare a plan for this project: create a Vyper smart contract that can execute an atomic Aave V3 liquidation against a given user. It will use the Uniswap V4 PoolManager as its "bank" and "market", i.e. it should perform an <code>unlock</code>, then within the V4 callback, execute a <code>liquidationCall</code> at the Aave V3 Pool and a <code>swap</code> at the V4 PoolManager to handle these operations: a withdrawal of the debt token (sent to the Aave V3 Pool), a deposit of the collateral token (received from the Aave liquidation), and optionally another swap to consolidate profit in a given token. There are some subtleties regarding execution order that we will have to study, but I can retrieve contract source code as needed.</p></blockquote><p>Then it thought for a bit, proposed a design, and gave a summary:</p><blockquote><p><strong>Next steps:</strong></p><ul><li><p>I can retrieve the Aave V3 Pool source code to verify <code>liquidationCall</code> return values</p></li><li><p>I can retrieve Uniswap V4 PoolManager interface to confirm <code>unlockCallback</code> signature</p></li><li><p>Do you have a specific network in mind (Ethereum mainnet, testnet)?</p></li></ul><p>Would you like me to start implementing the contract, or should we fetch and analyze the dependency contracts first to nail down the exact interfaces?</p></blockquote><p>So I gave it some more context:</p><blockquote><p>We are working on Ethereum mainnet. Retrieve the Aave V3 Pool source code by using <code>cast</code>. First retrieve the implementation address from the proxy contract at <strong>0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2</strong>, then get its source. The Uniswap V4 PoolManager contract is not a proxy, so retrieve its source directly at <strong>0x000000000004444c5dc75cB358380D2e3dE08A90</strong></p></blockquote><p>It read the <code>cast</code> skill to learn how to use Foundry correctly, then downloaded the contract source code for the Aave V3 Pool implementation contract at <a href="https://etherscan.io/address/0x8147b99df7672a21809c9093e6f6ce1a60f119bd">0x8147b99df7672a21809c9093e6f6ce1a60f119bd</a> and the Uniswap V4 PoolManager. It read through them to discover the execution flow for the key functions, and then summarized the operations:</p><blockquote><p>I&#8217;ve retrieved both contracts. Here are the key findings:</p><p><strong>Summary of Retrieved Contracts</strong></p><p></p><p><strong>Aave V3 Pool (Proxy: </strong><code>0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2</code><strong>)</strong></p><ul><li><p><strong>Implementation</strong>: <code>0x8147b99df7672a21809c9093e6f6ce1a60f119bd</code></p></li><li><p><strong>Key Function</strong>:</p></li></ul><pre><code><code>function liquidationCall(
    address collateralAsset,
    address debtAsset,
    address borrower,
    uint256 debtToCover,
    bool receiveAToken  // false = receive underlying collateral
) external;</code></code></pre><ul><li><p><strong>No return value</strong> - collateral is transferred directly to <code>msg.sender</code></p></li></ul><p></p><p><strong>Uniswap V4 PoolManager (</strong><code>0x000000000004444c5dc75cB358380D2e3dE08A90</code><strong>)</strong></p><ul><li><p><strong>Key Interface</strong>:</p></li></ul><pre><code><code>function unlock(bytes calldata data) external returns (bytes memory);

// Callback that MUST be implemented
interface IUnlockCallback {
    function unlockCallback(bytes calldata data) external returns (bytes memory);
}

function take(Currency currency, address to, uint256 amount) external;
function settle() external payable returns (uint256 paid);
function settleFor(address recipient) external payable returns (uint256 paid);

function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
    external returns (BalanceDelta swapDelta);

struct SwapParams {
    bool zeroForOne;
    int256 amountSpecified;  // negative = exact input, positive = exact output
    uint160 sqrtPriceLimitX96;
}</code></code></pre><p></p><p><strong>Updated Execution Plan</strong></p><p>Based on the source code, here&#8217;s the refined execution flow:</p><pre><code><code>&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;  unlock(data)    &#9474;
&#9474;   (caller = us)  &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
         &#8595;
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;     take(debtAsset, amount)       &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474; unlockCallback() &#9474;  &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#8594; &#9474;  PoolManager&#9474;
&#9474;   (us via PM)    &#9474;                                   &#9474;             &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;                                   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
         &#8595;
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;approve(debtAsset)&#9474;  &#8594;  liquidationCall(...)  &#8594;  receive collateral
&#9474;  (to Aave Pool)  &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
         &#8595;
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;     Optional: swap collateral&#8594;profit
&#9474;  Optional swap   &#9474;  &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#8594;
&#9474; (if consolidate) &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
         &#8595;
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;     swap/debt operations to acquire debtAsset
&#9474;  Acquire debt    &#9474;  &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#8594;
&#9474;  for repayment   &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
         &#8595;
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;     settle() - repay flash borrow
&#9474;     settle()     &#9474;  &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#8594;
&#9474;  (debtAsset)     &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
         &#8595;
    Validation: All deltas must be zero
         &#8595;
    Return profit to caller</code></code></pre><p></p><p><strong>Key Implementation Details</strong></p><p><strong>1. Execution Order (Critical)</strong></p><pre><code><code># Inside unlockCallback:
1. take(debtAsset, self, debt_amount)    # Create liability
2. approve(debtAsset, aave_pool, debt_amount)
3. liquidationCall(...)                   # Receive collateral
4. [Optional] swap(collateral&#8594;profit)    # Consolidate
5. swap(profit&#8594;debtAsset) or take        # Acquire repayment funds
6. settle(debtAsset)                      # Clear liability</code></code></pre><p><strong>2. Vyper Interface Definitions</strong></p><pre><code><code># Aave V3 Pool
interface IPool:
    def liquidationCall(
        collateralAsset: address,
        debtAsset: address,
        borrower: address,
        debtToCover: uint256,
        receiveAToken: bool
    ): nonpayable

# Uniswap V4 PoolManager
interface IPoolManager:
    def unlock(data: Bytes[1024]) -&gt; Bytes[1024]: nonpayable
    def take(currency: address, to: address, amount: uint256): nonpayable
    def settle() -&gt; uint256: payable
    def swap(
        key: PoolKey,
        params: SwapParams,
        hookData: Bytes[256]
    ) -&gt; BalanceDelta: nonpayable

# Must implement
interface IUnlockCallback:
    def unlockCallback(data: Bytes[1024]) -&gt; Bytes[1024]: nonpayable</code></code></pre><p><strong>3. Critical Subtleties</strong></p><ul><li><p><strong>Callback validation: </strong>Check <code>msg.sender == pool_manager</code> inside <code>unlockCallback</code></p></li><li><p><strong>PoolKey ordering: </strong>V4 requires <code>currency0 &lt; currency1</code>; need sorting helper</p></li><li><p><strong>BalanceDelta: </strong>Track swap returns - negative means received, positive means owed</p></li><li><p><strong>Token approvals: </strong>Infinite approve Aave Pool and PoolManager in constructor</p></li><li><p><strong>Native ETH: </strong>Use WETH for ETH positions; PoolManager handles via Currency type</p></li></ul><p><strong>4. Profit Flow</strong></p><ol><li><p>Debt tokens are <code>taken</code> from PoolManager (creates liability)</p></li><li><p>Liquidation repays debt, receives collateral (+ liquidation bonus)</p></li><li><p>Optional: Swap collateral for preferred profit token</p></li><li><p>Acquire debt tokens to <code>settle</code> the liability</p></li><li><p>Remaining tokens = profit &#8594; transfer to caller</p></li></ol><p>Would you like me to proceed with implementing the Vyper contract now, or do you need any clarification on the execution flow?</p></blockquote><p>Most models are trained to be helpful and maintain velocity, so you&#8217;ll notice that it &#8220;wants&#8221; to guide us down the route of progress: either begin implementing the contract immediately, or offer explanation of its proposed design.</p><p>Here is the point where things could go off the rails. There are a lot of assumptions baked into the proposed design, and I like to bring them to the surface.</p><p>So I choose a third route:</p>
      <p>
          <a href="https://www.degencode.com/p/aave-part-ix-execution-contract">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Aave — Part VIII]]></title><description><![CDATA[Price Oracles]]></description><link>https://www.degencode.com/p/aave-part-viii-price-oracles</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-viii-price-oracles</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Fri, 03 Apr 2026 18:33:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0deg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1fbc8b82-8e1b-4d93-8906-b81ca5b574e5_1616x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We now have access to the complete set of Aave user positions, so we can analyze them and begin hunting. </p><p>To properly evaluate the health of a particular user, we need to know all of their collateral and debt positions. In <a href="https://www.degencode.com/p/aave-part-v-liquidation-valuation-user-asset-discovery">Part V</a>, we did this by hand using mentally mapped asset prices.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a198a6ca-e72d-4e93-9acb-b80bea05d37a&quot;,&quot;caption&quot;:&quot;We discovered in Part IV that liquidatable positions are frequently present on Aave. But it&#8217;s not enough to find all the positions below the liquidation threshold and clear them out &#8212; position size and gas fees factor into the overall profit.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Aave &#8212; Part V&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-12-18T00:10:55.849Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!dPPk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84c245f6-f959-49b3-a48d-d60ca0565576_1962x1104.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/aave-part-v-liquidation-valuation-user-asset-discovery&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:181452218,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Now we want to replace that process (with its assumptions and simplifications) with one that scales across all of Aave&#8217;s assets. To do this, we need to look closely at how asset prices are determined so that we can track them in the same way.</p><h2>Price Oracles</h2><p>I&#8217;ve discussed price oracles before in a limited way, focusing on the price of a single asset.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d2d14fc3-c8d5-461a-bf95-356c7085da1f&quot;,&quot;caption&quot;:&quot;So far I&#8217;ve concerned myself with two types of token pools:&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Chainlink Price Feeds, Calculating Trade EV, Automatically Scaling Gas Fees&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2022-01-17T23:38:18.360Z&quot;,&quot;cover_image&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/chainlink-price-feeds-calculating&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:47230301,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:28,&quot;comment_count&quot;:8,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Aave uses an <a href="https://etherscan.io/address/0x54586be62e3c3580375ae3723c145253060ca0c2">oracle contract</a> that acts as a bridge between its internal operations and the external market. The current oracle is available at the <a href="https://etherscan.io/address/0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e#readContract">PoolAddressProvider</a> contract via the <code>getPriceOracle</code> function. The oracle was set shortly after the mainnet market deployment and has not changed since, but that is not a guarantee that it will remain that way. It can be changed at any time by governance.</p><h2>Asset Sources</h2><p>The oracle maintains a single source for each of the registered assets. Whenever this source is updated, the oracle emits an <code>AssetSourceUpdated</code> event. The Aave updater in <code>degenbot</code> tracks these events and uses them to update the database.</p><h2>Asset Prices</h2><p>The oracle&#8217;s price discovery mechanism is the <code>getAssetPrice</code> function, which retrieves the source for a given asset, then calls <code>source.latestAnswer()</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;9c386607-b3be-48d9-9e17-c4805340836b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">/// @inheritdoc IPriceOracleGetter
function getAssetPrice(address asset) public view override returns (uint256) {
  AggregatorInterface source = assetsSources[asset];

  if (asset == BASE_CURRENCY) {
    return BASE_CURRENCY_UNIT;
  } else if (address(source) == address(0)) {
    return _fallbackOracle.getAssetPrice(asset);
  } else {
    int256 price = source.latestAnswer();
    if (price &gt; 0) {
      return uint256(price);
    } else {
      return _fallbackOracle.getAssetPrice(asset);
    }
  }
}</code></pre></div><p><code>latestAnswer()</code> should be familiar if you&#8217;ve looked at a Chainlink contract. Note that the oracle does not have to be a Chainlink contract, but most of them are. The only requirement of the oracle is that is provides the <code>latestAnswer</code> interface. </p><p>So all we need is the source for each asset, and a way to observe the price when it changes.</p><p>Let&#8217;s consider Aave&#8217;s Ether/WETH asset. The oracle tells us that the price source is contract <a href="https://etherscan.io/address/0x5424384B256154046E9667dDFaaa5e550145215e">0x5424384B256154046E9667dDFaaa5e550145215e</a>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R_Rb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R_Rb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png 424w, https://substackcdn.com/image/fetch/$s_!R_Rb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png 848w, https://substackcdn.com/image/fetch/$s_!R_Rb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png 1272w, https://substackcdn.com/image/fetch/$s_!R_Rb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R_Rb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png" width="1456" height="547" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:547,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71078,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/192900860?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R_Rb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png 424w, https://substackcdn.com/image/fetch/$s_!R_Rb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png 848w, https://substackcdn.com/image/fetch/$s_!R_Rb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png 1272w, https://substackcdn.com/image/fetch/$s_!R_Rb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d5b2b9a-d9d9-4fee-90e9-f555f8cf809c_1614x606.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And that the asset&#8217;s current price is 205429135979 ($2,054):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9dMX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9dMX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png 424w, https://substackcdn.com/image/fetch/$s_!9dMX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png 848w, https://substackcdn.com/image/fetch/$s_!9dMX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png 1272w, https://substackcdn.com/image/fetch/$s_!9dMX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9dMX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png" width="1456" height="548" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:548,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63779,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/192900860?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9dMX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png 424w, https://substackcdn.com/image/fetch/$s_!9dMX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png 848w, https://substackcdn.com/image/fetch/$s_!9dMX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png 1272w, https://substackcdn.com/image/fetch/$s_!9dMX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11d7cc36-3d8f-4b39-b2a2-30ba7d390903_1616x608.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We can also inspect <code>latestAnswer()</code> directly at the source contract:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!r2XN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!r2XN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png 424w, https://substackcdn.com/image/fetch/$s_!r2XN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png 848w, https://substackcdn.com/image/fetch/$s_!r2XN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png 1272w, https://substackcdn.com/image/fetch/$s_!r2XN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!r2XN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png" width="1456" height="283" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:283,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:65986,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/192900860?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!r2XN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png 424w, https://substackcdn.com/image/fetch/$s_!r2XN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png 848w, https://substackcdn.com/image/fetch/$s_!r2XN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png 1272w, https://substackcdn.com/image/fetch/$s_!r2XN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7af7f044-c5da-4b88-a7f2-1f06518736ff_1606x312.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The values match, which we expected.</p><h2>Chainlink SVR</h2><p>It&#8217;s not obvious from reading the contract source, but this is a special kind of feed contract labeled as a Chainlink Smart Value Recapture (SVR) feed also specific to the Aave protocol.</p>
      <p>
          <a href="https://www.degencode.com/p/aave-part-viii-price-oracles">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Degenbot v0.6.0 (alpha 1) Release]]></title><description><![CDATA[Aave V3 Testers Wanted]]></description><link>https://www.degencode.com/p/degenbot-v060-alpha1-release-</link><guid isPermaLink="false">https://www.degencode.com/p/degenbot-v060-alpha1-release-</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Mon, 30 Mar 2026 23:14:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://aave.com/blog/aave-v4-live-ethereum">Aave&#8217;s V4 market</a> went live on Mainnet this morning, congratulations to them! </p><p>Coincidentally I&#8217;ve published a <a href="https://pypi.org/project/degenbot/0.6.0a1.post1/">new degenbot release</a> with Aave V3 position scraping enabled. If you&#8217;re interested in testing it, you can install it using <code>pip install degenbot==0.6.0a1.post1. </code></p><p>You can populate your database with positions using these commands:</p><ul><li><p>Upgrade the database using <code>degenbot database upgrade</code></p></li><li><p>Activate the market using <code>degenbot aave activate ethereum_aave_v3 </code></p></li><li><p>Scrape the positions using <code>degenbot aave update</code></p></li></ul><p>In addition to Aave V3, this release is notable because it&#8217;s the first one distributed with integrated Rust components via <a href="https://pyo3.rs/">PyO3</a>. </p><p>I will be integrating more Rust into the package, so please report any bugs that you encounter on these 0.6 releases. </p><p>The installation should work cleanly on Linux (x86_64, aarch64), MacOS (x86_64 and ARM64), and Windows (x86_64). If you have exotic hardware and want to help me test the package against those, please let me know!</p>]]></content:encoded></item><item><title><![CDATA[Aave — Part VII]]></title><description><![CDATA[Unraveling Complexity]]></description><link>https://www.degencode.com/p/aave-part-vii-unraveling-complexity</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-vii-unraveling-complexity</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Tue, 24 Mar 2026 19:46:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.degencode.com/p/aave-part-vi-adding-db-positions">Part VI</a> closed with a simple demonstration of building collateral positions using a simple process: look for Supply events, then decode them to inspect the user address, the reserve address, and the amount supplied.</p><p>But that&#8217;s a naive approach that ignores a lot of detail. Critically, it scales poorly &#8212; every successive update would have to scan for new events since the last block, discover new users, add them to the previous users, then do a full re-scrape of all of the previous plus new positions.</p><p>So instead of rely on that structure, I built the updater from the ground up. Along the way, I dug through many layers of unexpected complexity. It required a lot of untangling, but yielded valuable information about how Aave handles position accounting, upgrades, and changing incentives.</p><p>I will cover several areas where position tracking required additional layers of decoding and tracking. This will be particularly useful if you need to understand how the Aave contracts are wired together across a particular market.</p>
      <p>
          <a href="https://www.degencode.com/p/aave-part-vii-unraveling-complexity">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Agentic Coding For Degens]]></title><description><![CDATA[How To Automate The Boring Stuff]]></description><link>https://www.degencode.com/p/agentic-coding</link><guid isPermaLink="false">https://www.degencode.com/p/agentic-coding</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Tue, 10 Feb 2026 23:05:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!vlNY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been using AI to chew through some significant Aave V3 complexity recently. </p><p>I&#8217;ve learned a lot, and will certainly refer to these techniques again. So instead of folding it into an Aave specific write-up where it could be missed, I&#8217;m giving it proper treatment with its own introduction.</p><h2>AI Fatigue</h2><p>Every week I&#8217;m being told that it&#8217;s all over: a script will take my job, the robots are coming to kill me, RAM will never be affordable again, I should shut up and get in the pod.</p><p>It&#8217;s neither as bad nor as good as they say. Relentless media treatment has made &#8220;AI&#8221; a container word &#8212; people hear it, fill in ideas and things they&#8217;ve heard about it, and then stop thinking.</p><p>You can respond to AI any way you like. Here are my very important opinions on it:</p><ul><li><p>Seeing fake AI avatars everywhere is annoying, but I like generating coloring pages for my kids and diagrams for technical presentations.</p></li><li><p>AI bots clogging up the replies on X is infuriating, but I like to get a summary of long documents.</p></li><li><p>The chat bot on my car dealership&#8217;s website is stupid, but the one integrated into open-source tool documentation is very useful.</p></li></ul><h2>Coding Tools</h2><p>AI has been around for many decades, and its sophistication and usefulness has been trending upwards. It crossed the usefulness threshold in recent years, and appears to be here to stay. </p><p>I only started paying attention to this a few years ago.</p><h3>Autocomplete</h3><p>I saw folks talking about GitHub Copilot, so tried it out. After a few days I turned it off &#8212; I didn&#8217;t find the auto-completion particularly useful, and was frequently distracted by the phantom text appearing when I stopped typing.</p><h3>Web Chat</h3><p>Then Claude launched with the familiar web interface. I saw more people talking about it, saying that Opus 4 was a good coding model, so I signed up for a few months. </p><p>I found it was very good at generating small scale code snippets that I could copy and paste into my project. But the experience fell apart quickly when the code didn&#8217;t work as expected &#8212; I had to dump long tracebacks into the chat and ask it for fixes, then perform another copy-paste cycle. I felt limited by the browser interface and frustrated by how poorly it performed as the session grew in length. I cancelled.</p><h3>Agentic</h3><p>Then I started seeing chatter about agentic coding techniques. The promise was that agents could be spawned which would operate independently towards some goal.</p><p>I was specifically interested in the coding agents. A coding agent is simply a tool that tracks user inputs, calls an AI service, interprets responses, executes tools on behalf of the AI and manages I/O. </p><h4>Editor Based</h4><p>The first coding agents tools I saw were hooked to a graphical editor via a plug-in or natively: Cursor, Windsurf, Cline, Zed, Roo. </p><p>I tried them and enjoyed the ability to easily focus the tool on a section of code or an error message in the terminal. I continued to get nice results on small-scale tasks, but large refactoring efforts felt quite clumsy and hard to track if they needed multiple phases. I had the feeling I had traded writing code (which I enjoy very much) for micromanaging a capable but easily distracted person (which I dislike very much).</p><h4>Command Line Based</h4><p>Then Claude Code dropped in the middle of last year and everything changed!</p><p>It&#8217;s difficult to separate the improvement in coding tools from the improvement in coding-focused AI models, so I won&#8217;t try. But I will assert that console tools like Claude Code are a huge improvement over both web tools and editor tools. </p><p>The web comparison is easy to demonstrate. Imagine using Claude Opus 4.5 through the web interface. Still very capable, but having to copy and paste is cumbersome and not scalable.</p><p>The IDE tool comparison is harder, but the weakness shared with the web tool is the mode of user interaction. You still enter prompts with a keyboard and approve actions with a mouse. Very compatible with the typical way people work, but still limited by user attention and availability.</p><h2>Code Is Now Cheap</h2><p>In a post-AI world, code has become cheap so you should write less of it! </p><p>Your time is now too expensive to spend writing your own loops. But what should you do instead?</p><h3>You Can Just (Not) Do Things</h3><p>Appearing busy is glamorous: you look important and get the warm feeling that you&#8217;ve maximized your productivity. </p><p>But the result of your efforts can vary wildly depending on the tasks you select.</p><p>For coders, this shows up as a ratio between features and products shipped vs. time spent creating it. </p><p>In terms of processing speed and I/O, humans are the bottleneck in any process involving computers. So learning how to extract yourself from the &#8220;hot&#8221; loop is critical.</p><p>Several approaches have come from this realization.</p><h3>Vibe Coding</h3><p>Andrej Karpathy&#8217;s <a href="https://x.com/karpathy/status/1886192184808149383">famous &#8220;vibe coding&#8221; tweet</a> has pushed many people towards giving the AI more control. Instead of trying to act as the brain and letting the AI be your fingers, vibe coding encourages stepping away and reviewing the output later, then nudging the result to taste.</p><p>The tweet literally says it&#8217;s &#8220;not too bad for throwaway weekend projects&#8221;, so it&#8217;s obviously not a good technique for writing production software.</p><p>Once you see the spaghetti generated from a long vibe coding session, you&#8217;ll realize the problem. AIs are good at generating code &#8212; the more you use it the more you get. </p><p>The usual AI solution to handling complex inputs tends towards branching and special-case fixes. Ask it to consolidate the code using DRY techniques and it will just extract a bunch of helper functions or hide the branching in some new method, without resolving the architectural issue. It can quickly spiral away when not properly constrained.</p><h3>Ralph Wiggum</h3><p>I recommend watching this primer on the Ralph Wiggum loop by its creator, Geoffrey Huntley:</p><div id="youtube2-4Nna09dG_c0" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;4Nna09dG_c0&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/4Nna09dG_c0?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Here is the basic Ralph technique:</p><ul><li><p>Generate a highly detailed plan and specifications for what you want to build</p></li><li><p>Run a coding agent in a loop with these instructions:</p><ul><li><p>Read the plan</p></li><li><p>Read the specifications</p></li><li><p>Select the most important single task from the plan</p></li><li><p>Complete it</p></li><li><p>Run the tests</p></li><li><p>Commit</p></li><li><p>Mark the task as complete</p></li><li><p>Add important lessons learned to a file</p></li><li><p>If all tasks are complete, take some action that terminates the loop</p></li></ul></li></ul><p>In this way, each iteration of the loop starts fresh with a context window focused on important items and a simple request: implement one task. </p><p>Each loop starts the agent with its context reset, so that it can load only what it needs instead of holding on to many irrelevant instructions that degrade accuracy.</p><h2>Automation</h2><p>I&#8217;m a huge fan of using command line interface (CLI) tools. They are flexible, high performance, and portable. But the killer feature is they can be automated!</p><p>This whole newsletter is about building autonomous stuff, so this shouldn&#8217;t surprise you. If I can eliminate a click or button press, or make something run on a schedule, I will. I&#8217;m willing to spend a lot of time converting a manual process to automatic.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vlNY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vlNY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vlNY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vlNY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vlNY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vlNY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg" width="564" height="500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:564,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:84335,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/187546090?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!vlNY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vlNY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vlNY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vlNY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e18f2ff-cb99-49c9-bd2f-f73e81b7bc93_564x500.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So naturally when I code, I do it with agentic tools on the CLI that call AI models specifically trained for coding.</p><p>I have a strong bias towards open source, so I prefer using an open source agent calling open weight models through a neutral provider. Currently I use <a href="https://opencode.ai/">OpenCode</a> with <a href="https://www.kimi.com/ai-models/kimi-k2-5">Kimi K2.5</a> via <a href="https://synthetic.new/?referral=zL07O85jYXTPOcZ">Synthetic</a> (referral link, we&#8217;ll both get a promotional credit if you sign up).</p><h3>Automatic Plan Summary</h3><p>My first automation was simple: generate a daily summary of the open plans.</p><p>OpenCode allows you to define a custom command via a Markdown file. So I wrote one with instructions on how to do that:</p><pre><code>---
description: Review project plans and provide a summary
agent: plan
model: synthetic/hf:moonshotai/Kimi-K2.5
---

Use the Glob tool to find work plans organized in `.opencode/plans`. Simple plans in the root directory are saved as individual files. Complex plans spanning multiple files are saved in a subdirectory.

For each plan, delegate the following actions:
- Review the progress described in the plan versus the referenced code
- Provide a summary of actual progress made
- Recommend updates to the plan to synchronize it with actual progress
- Recommend improvements to the plan

Then:
- Summarize the state of each plan and action items
- Prioritize the pending work
- Recommend one action item to make progress on each plan</code></pre><p>This command can be executed by calling OpenCode with the appropriate option. Here you can see that it looks for the plans using the Glob tool (an abstraction over <code>find</code>) in the working directory, then delegates review of each plan to a subagent that starts fresh and can concentrate on the one plan it was given: </p><p><code>summarize.md</code></p><pre><code>btd@dev:~/code/degenbot$ opencode run --command summarize

&gt; plan &#183; hf:moonshotai/Kimi-K2.5

&#10033; Glob ".opencode/plans/**/*" 7 matches
&#8594; Read .opencode/plans/hybrid_configuration/implementation_plan.md
&#8594; Read .opencode/plans/aave_transaction_refactor.md
&#8594; Read .opencode/plans/uniswap_v3_pool_refactoring/plan.md
&#8594; Read .opencode/plans/uniswap_v3_pool_refactoring/architecture_diagram.md
&#8594; Read .opencode/plans/work-plan-aave-todos.md
&#8594; Read .opencode/plans/organization_improvement.md
&#8594; Read .opencode/plans/bootstrap_system.md
&#8226; Review hybrid configuration plan Explore Agent
&#8226; Review Aave transaction refactor plan Explore Agent
&#8226; Review Uniswap V3 pool refactoring plan Explore Agent
&#8226; Review Aave TODO work plan Explore Agent
&#8226; Review organization improvement plan Explore Agent
&#8226; Review bootstrap system plan Explore Agent

Now I'll delegate review tasks for each plan to assess progress against the referenced code and provide recommendations.

&#10003; Review hybrid configuration plan Explore Agent
&#10003; Review Aave transaction refactor plan Explore Agent

[...]</code></pre><p>The result is a nice summary that I can review each morning before starting work. This can be easily triggered by a systemd script or a cron job:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9xWm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9xWm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!9xWm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!9xWm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!9xWm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9xWm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png" width="1456" height="2035" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2035,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:281269,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/187546090?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9xWm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!9xWm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!9xWm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!9xWm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F668e8a12-f940-4376-836c-59d2c1659366_1564x2186.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Transaction Inspection</h3><p>My next automation was more complex. I found that during debugging, I was often repeating instructions for the same set of actions: inspect an Etherscan page, tell me what happened during a particular transaction, tell me which contracts were used.</p><p>So I defined a read-only single task <a href="https://opencode.ai/docs/agents#subagents">subagent</a> that would do these things:</p><p><code>evm-investigator.md</code></p><pre><code>---
description: Analyzes Ethereum or EVM-compatible blockchain transactions, accounts, and smart contracts.
mode: subagent
tools:
  write: false
  edit: false
  task: false
---

You are in Ethereum Virtual Machine (EVM) transaction investigation mode. Focus on:
- Smart contract source code
- Inspecting transactions using a block explorer (Etherscan or similar)
- User calldata
- Event logs
- Storage states
- Transaction tracing
- Function call arguments and return values</code></pre><p>Within the terminal user interface (TUI), trigger its use by mentioning it with the @ symbol:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!krtH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!krtH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!krtH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!krtH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!krtH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!krtH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png" width="1456" height="2035" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2035,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:269142,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/187546090?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!krtH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!krtH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!krtH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!krtH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1be8ea04-c8f5-4d3d-802b-13290b7dc976_1564x2186.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Pretty cool!</p><h3>Automating Debugging</h3><p>It&#8217;s also simple to write a command that glues the output of something run in a terminal to a subagent. This command runs my Aave updater after a failure, instructs the model to delegate investigating the transaction that caused it, then start a hands-off debugging effort that records lessons learned to a log:</p><p><code>aave_debug.md</code></p><pre><code>---
description: Debug Aave update failures
agent: build
model: synthetic/hf:moonshotai/Kimi-K2.5
---

!`uv run degenbot aave update --no-progress-bar --one-chunk`

## DIRECTION: Investigate and debug this failed Aave update command

## PROCESS:
### 1. Gather Information
- Parse the output to identify the transaction hash associated with the event triggering the processing error
- Inspect the transaction using @evm-investigator

### 2. Investigate Code
- Review `aave_update` and determine the execution path leading to the error
- Check for related issues involving this error or a common execution path
- Generate a failure hypothesis

### 3. Validate Execution Path and Failure Hypotheses
- If the execution path is unclear, apply the `log_function_call` decorator to confirm function calls, e.g.,
    ```python
    @log_function_call
    def some_func(...): ...
    ```
- Determine if a debugging env var is useful:
    - `DEGENBOT_VERBOSE_USER=0x123...,0x456...`
    - `DEGENBOT_VERBOSE_TX=0xabc...,0xdef...`
    - `DEGENBOT_VERBOSE_ALL=1`
    - `DEGENBOT_DEBUG=1`
    - `DEGENBOT_DEBUG_FUNCTION_CALLS=1`
- Run with any verbosity flags prepended, e.g., `DEGENBOT_DEBUG=1 uv run degenbot aave update --no-progress-bar --one-chunk`

### 4. Fix &amp; Validate
- If a hypothesis is validated and the root cause is clear, implement a fix and run the update again

### 5. Document Findings
Append to @aave_debug_progress.md. Follow this format:
- **Issue:** Brief title
- **Date:** Current date
- **Symptom:** Error message verbatim
- **Root Cause:** Technical explanation
- **Transaction Details:** Hash, block, type, user, asset
- **Fix:** Code location and changes
- **Key Insight:** Lesson learned for future debugging
- **Refactoring:** Concise summary of proposed improvements to code that processes these transactions</code></pre><p>It&#8217;s now available in the TUI as slash command:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zQUx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zQUx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!zQUx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!zQUx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!zQUx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zQUx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png" width="1456" height="2035" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2035,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81001,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/187546090?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zQUx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!zQUx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!zQUx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!zQUx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd1326ee-562c-43db-9242-3ed45d58fb32_1564x2186.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>After running for a minute, it narrowed down and identified the bug:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lBpA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lBpA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!lBpA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!lBpA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!lBpA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lBpA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png" width="1456" height="2035" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2035,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:272684,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/187546090?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lBpA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!lBpA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!lBpA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!lBpA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb10bcef8-e1e4-4dd1-9106-3e5cdc0e8819_1564x2186.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And then gives me a nice little summary of what it did:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SdQE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SdQE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!SdQE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!SdQE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!SdQE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SdQE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png" width="1456" height="2035" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2035,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:331444,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/187546090?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SdQE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png 424w, https://substackcdn.com/image/fetch/$s_!SdQE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png 848w, https://substackcdn.com/image/fetch/$s_!SdQE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png 1272w, https://substackcdn.com/image/fetch/$s_!SdQE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85aecc8f-6001-4f4c-a79c-121f9050f4ed_1564x2186.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Unattended Ralph Loop</h3><p>Here&#8217;s where we max the technique out, setting up an unattended development loop where your agent can develop new features, make fixes, do code review, and run experiments while you sleep.</p>
      <p>
          <a href="https://www.degencode.com/p/agentic-coding">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Aave — Part VI]]></title><description><![CDATA[Adding Positions to the Database]]></description><link>https://www.degencode.com/p/aave-part-vi-adding-db-positions</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-vi-adding-db-positions</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Wed, 31 Dec 2025 23:47:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!VdaR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d418852-256e-4ecc-bce9-2065ed7047d8_2730x1890.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Previous entries in the <a href="https://www.degencode.com/t/aave">Aave Series</a> focused on understanding the Aave platform and the necessary contract calls to identify users, inspect their positions, and perform liquidations.</p><p>Now let&#8217;s zoom out and build a means to monitor these positions over the long term.</p><p>We will focus on developing a database structure for Aave V3 using <a href="https://www.sqlalchemy.org/">SQLAlchemy</a>, writing an extractor function to pull the necessary events and update the database, then run some limited tests to verify the data behaves as expected.</p><p>This entry is heavily influenced by ideas from <a href="https://en.wikipedia.org/wiki/Domain-driven_design">Domain-Driven Design</a>. If you find it useful, consider reading the books &#8220;Domain-Driven Design: Tackling Complexity in the Heart of Software&#8221; by Eric Evans and &#8220;Implementing Domain-Driven Design&#8221; by Vernon Vaughan.</p>
      <p>
          <a href="https://www.degencode.com/p/aave-part-vi-adding-db-positions">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Aave — Part V]]></title><description><![CDATA[Liquidation Valuation & User Asset Discovery]]></description><link>https://www.degencode.com/p/aave-part-v-liquidation-valuation-user-asset-discovery</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-v-liquidation-valuation-user-asset-discovery</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Thu, 18 Dec 2025 00:10:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!dPPk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84c245f6-f959-49b3-a48d-d60ca0565576_1962x1104.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We discovered in <a href="https://www.degencode.com/p/aave-part-iv-borrower-discovery-position-inspection">Part IV</a> that liquidatable positions are frequently present on Aave. But it&#8217;s not enough to find all the positions below the liquidation threshold and clear them out &#8212; position size and gas fees factor into the overall profit. </p><p>An Aave user can have multiple collateral deposits and borrows across the platform. But Aave tracks only that user&#8217;s overall health factor. We saw this when calling the <code>getUserAccountData</code> function on the <a href="https://www.degencode.com/p/aave-part-ii-pool-contract">Pool contract</a>. </p><p>The collateral, debt, and available borrowing capacity values reported by that function are expressed in terms of a common base currency. This base currency is not defined by Aave, it is largely a function of the oracles used for each reserve asset. For example, to determine the collateral value of WETH against the debt value of USDC, an intermediate base currency is used. The Mainnet market uses USD as this intermediate.</p><p>So we must always be aware that Aave measures user health against this base currency. Aave reserve assets move against USD and against each other, which makes accounting more complex.</p><p>Example: if you&#8217;re considering a liquidation of a WBTC loan backed by WETH, you must be aware of at least <strong>three</strong> prices:</p>
      <p>
          <a href="https://www.degencode.com/p/aave-part-v-liquidation-valuation-user-asset-discovery">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Aave — Part IV]]></title><description><![CDATA[Borrower Discovery, Position Inspection]]></description><link>https://www.degencode.com/p/aave-part-iv-borrower-discovery-position-inspection</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-iv-borrower-discovery-position-inspection</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Wed, 03 Dec 2025 14:31:25 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Loan positions on Aave can be easily inspected, provided that you know who has them.</p><p>There is no onchain view that exposes a simple list of active loan positions or their users, so we&#8217;ll need to dig through transaction logs to discover the borrowers and look up their positions.</p><p>Each new loan operation emits a <code>Borrow</code> event, which is defined in the <a href="https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/interfaces/IPool.sol">IPool.sol</a> interface:</p><pre><code>event Borrow(
    address indexed reserve,
    address user,
    address indexed onBehalfOf,
    uint256 amount,
    DataTypes.InterestRateMode interestRateMode,
    uint256 borrowRate,
    uint16 indexed referralCode
);</code></pre><p>Using Ape, connect to Ethereum mainnet and query for <code>Borrow</code> events starting at block 23,000,000:</p><pre><code>btd@dev:~$ ape console --network :mainnet
INFO:     Connecting to existing Reth node at http://localhost:8545.

In [1]: pool = Contract('0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2')

In [2]: borrows = pool.Borrow.query('*', start_block=23_000_000)

In [3]: borrows
Out[3]: 
0       0x807c5ad511680ecb97cc3a9f9caaacb6f55c98799596... 
1       0xa40948b206cde4493e8772bb9cf2dd38ef020151a453... 
2       0x8ed9da1f0a1c0daedd0b1c7a98811078cb09945076e0... 
3       0xab6b82ec1d7df25fcf8826d9410013d7813b9c5fb514... 
4       0x6f8dd67ab48078d522144126929d194d12b49b89196c... 
...
...
191965  0xc24c257bbdfd99a3693912169190f863874c134e2ec0... 
191966  0xf906e5d7b5926444dabd3750a510df36e13658433383... 
191967  0x8c0a4488312fda581f4a5d01e7bab4446d8aa9412628... 
191968  0x893e30290be406185abc59cabfef540335f53428a80d... 
191969  0xcd5c580418e3cb18cd5b2e730f671459dd90cf03e63e... 

[191970 rows x 8 columns]</code></pre><p>Calls to <code>query()</code> return a <a href="https://pandas.pydata.org/">Pandas</a> DataFrame with columns corresponding to the event&#8217;s position in the block, its transaction, and the data in the event itself. You can view these via the <code>columns</code> attribute on the DataFrame:</p><pre><code>In [4]: borrows.columns
Out[4]: 
Index(
    [
        &#8217;block_hash&#8217;, 
        &#8216;block_number&#8217;, 
        &#8216;contract_address&#8217;, 
        &#8216;event_arguments&#8217;,
        &#8216;event_name&#8217;, 
        &#8216;log_index&#8217;, 
        &#8216;transaction_hash&#8217;, 
        &#8216;transaction_index&#8217;
    ],
    dtype=&#8217;object&#8217;
)</code></pre>
      <p>
          <a href="https://www.degencode.com/p/aave-part-iv-borrower-discovery-position-inspection">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Aave — Part III]]></title><description><![CDATA[Supply, Withdraw, Borrow, Repay, and Liquidate]]></description><link>https://www.degencode.com/p/aave-part-iii-supply-withdraw-borrow-repay-and-liquidate</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-iii-supply-withdraw-borrow-repay-and-liquidate</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Sat, 29 Nov 2025 00:29:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!oCHN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb229c5be-f292-4d9f-9864-b704b3c7202f_1491x1181.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The market is volatile and someone has to stand guard against bad debt. Will you answer the call, dear reader?</p><p>Continuing the <a href="https://www.degencode.com/t/aave">Aave Series</a>, let&#8217;s build to the ultimate goal of liquidating unhealthy debt positions.</p><p>We&#8217;ll cover the five primary functions of the Pool contract, ordered to build layered understanding until we reach our target: <code>supply</code>, <code>withdraw</code>, <code>borrow</code>, <code>repay</code>, and <code>liquidationCall</code>.</p><p>The lesson is structured as a series of tests using Ape Framework that interacts with each of the five contract functions, plus some accessory calls to other contracts that will support our efforts.</p><p>This entry assumes you are familiar with the Aave concepts and Pool contract covered in <a href="https://www.degencode.com/p/aave-part-ii-pool-contract">Part II</a>, how to set up Ape Framework and how to use it for executing tests against a forked chain.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;af932678-29d6-4215-a188-1a121a74b134&quot;,&quot;caption&quot;:&quot;The set of Aave contracts deployed for a given chain is known as a &#8220;market&#8221;. Each chain typically has a main market for users, though other markets may be deployed on that chain with different features.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Aave&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:100}],&quot;post_date&quot;:&quot;2025-11-25T15:31:20.472Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!1psL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/aave-part-ii-pool-contract&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:179837891,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;df24f386-43d5-4a9f-894d-aab1b03fa7b8&quot;,&quot;caption&quot;:&quot;An effective way to expand your skills is taking what you already know and attempting to do it with new tools.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Introduction to Ape Framework&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:100}],&quot;post_date&quot;:&quot;2023-10-08T23:50:40.436Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5ebe2e7c-a38a-4614-bdb4-af5b523e84c4_200x200.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/introduction-to-ape-framework&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137754996,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:13,&quot;comment_count&quot;:6,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;483efc3a-4501-4e19-84b6-214f12eb2282&quot;,&quot;caption&quot;:&quot;I started out cowboy coding: write some new code, run it, see what happens. Nothing immediately failed? Assume it was good, write the next section of code.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Testing With Ape Framework&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:100}],&quot;post_date&quot;:&quot;2024-02-28T14:31:07.957Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/testing-with-ape-framework&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:142089052,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><h2>Project Setup</h2><p>I will be working from a new Ape project in <code>~/code/aave</code>.</p><p>Create a virtual environment:</p><pre><code>btd@dev:~/code/aave$ uv venv --python 3.13
Using CPython 3.13.8
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate</code></pre><p>Install Ape with the set of recommended plugins:</p><pre><code>btd@dev:~/code/aave$ uv pip install eth-ape[recommended-plugins]
...
Installed 133 packages in 420ms
...</code></pre><p>Create the Ape project using <code>uv run ape init</code> and edit the configuration so that the default network used by tests is a local fork from Ethereum mainnet using Foundry:</p><p><code>ape-config.yaml</code></p><pre><code>name: Aave Pool

default_ecosystem: ethereum

ethereum:
  default_network: mainnet-fork
  mainnet:
    default_provider: node
  mainnet_fork:
    default_provider: foundry

node:  
  ethereum:
    mainnet:
      uri: http://localhost:8545 # EDIT THIS

plugins:
  - foundry
  - etherscan</code></pre><p>Create a connection test to confirm that <code>ape test</code> launches the fork from the correct chain:</p><p><code>tests/test_aave_pool.py</code></p><pre><code>from ape.managers.networks import NetworkManager
from ape_foundry.provider import FoundryForkProvider

def test_fork_is_launched(networks: NetworkManager):
    assert isinstance(networks.provider, FoundryForkProvider)
    assert networks.provider.chain_id == 1</code></pre><p>Execute using <code>uv run ape test</code> and confirm that test passes. Now we can be confident that tests will run against the fork.</p><h2>supply</h2><p>The <code>supply</code> function from <a href="https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/pool/Pool.sol">Pool.sol</a>:</p><pre><code>function supply(
    address asset,
    uint256 amount,
    address onBehalfOf,
    uint16 referralCode
) public virtual override {
    SupplyLogic.executeSupply(
        _reserves,
        _reservesList,
        _usersConfig[onBehalfOf],
        DataTypes.ExecuteSupplyParams({
        user: _msgSender(),
        asset: asset,
        interestRateStrategyAddress: RESERVE_INTEREST_RATE_STRATEGY,
        amount: amount,
        onBehalfOf: onBehalfOf,
        referralCode: referralCode
        })
    );
}</code></pre><p>We need to provide four inputs:</p><ul><li><p>asset &#8212; the supplied asset address</p></li><li><p>amount &#8212; how much we will supply</p></li><li><p>onBehalfOf &#8212; the user address to credit the supply</p></li><li><p>referralCode &#8212; a unique referral code (set to zero, the referral program is currently inactive)</p></li></ul><p>We will be using WETH in various tests, so let&#8217;s make a fixture that creates a contract instance for use in other tests:</p><pre><code>WETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"

@pytest.fixture
def weth():
    return Contract(WETH_ADDRESS)</code></pre><p>And we&#8217;ll need a tester account with a balance:</p><pre><code>@pytest.fixture
def tester_account(accounts):
    return accounts[0]</code></pre><p>Create a test that asserts that <code>tester_account</code> has a balance, and that wrapping at the WETH contract works as expected:</p><pre><code>def test_wrap_ether(weth: Contract, tester_account):
    starting_ether_balance = tester_account.balance
    starting_weth_balance = weth.balanceOf(tester_account)

    deposit_amount = 100 * 10**18
    assert starting_ether_balance &gt; deposit_amount

    weth.deposit(value=deposit_amount, sender=tester_account)
    assert weth.balanceOf(tester_account) == (
        deposit_amount + starting_weth_balance
    )
    assert tester_account.balance &lt; starting_ether_balance</code></pre><p>Now we can write a test that wraps 100 Ether, approves it for use by the Pool contract, and supplies it:</p><pre><code>AAVE_V3_POOL = &#8220;0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2&#8221;

@pytest.fixture
def aave_pool():
    return Contract(AAVE_V3_POOL)


def test_supply(aave_pool: Contract, weth: Contract, tester_account):
    deposit_amount = 100 * 10**18
    weth.deposit(value=deposit_amount, sender=tester_account)
    weth.approve(
        aave_pool.address, 
        deposit_amount, 
        sender=tester_account
    )

    starting_weth_balance = weth.balanceOf(tester_account)
    tx = aave_pool.supply(
        weth.address, 
        deposit_amount, 
        tester_account.address, 
        0, 
        sender=tester_account
    )
    assert weth.balanceOf(tester_account) == (
        starting_weth_balance - deposit_amount
    )</code></pre><p>The test works as expected, but doesn&#8217;t tell us much. Add a simple <code>tx.show_trace()</code> at the end and run the test with <code>-s</code> to see the output:</p>
      <p>
          <a href="https://www.degencode.com/p/aave-part-iii-supply-withdraw-borrow-repay-and-liquidate">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Aave — Part II]]></title><description><![CDATA[Pool Contract]]></description><link>https://www.degencode.com/p/aave-part-ii-pool-contract</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-ii-pool-contract</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Tue, 25 Nov 2025 15:31:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1psL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The set of Aave contracts deployed for a given chain is known as a &#8220;market&#8221;. Each chain typically has a main market for users, though other markets may be deployed on that chain with different features.</p><p>The largest market is on Ethereum mainnet (chain ID 1), accessible via the <a href="https://app.aave.com/">Aave front-end</a> by going to the <a href="https://app.aave.com/markets/">Markets</a> tab and choosing &#8220;Core&#8221; from the drop-down menu.</p><p>Many assets are enabled for this market, including the familiar <a href="https://app.aave.com/reserve-overview/?underlyingAsset=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&amp;marketName=proto_mainnet_v3">Wrapped ETH</a>, <a href="https://app.aave.com/reserve-overview/?underlyingAsset=0xdac17f958d2ee523a2206206994597c13d831ec7&amp;marketName=proto_mainnet_v3">Tether USD</a>, and <a href="https://app.aave.com/reserve-overview/?underlyingAsset=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&amp;marketName=proto_mainnet_v3">USD Coin</a> tokens.</p><p>We will use this market for the remainder of our study. Be sure you connect to an appropriate RPC and use <a href="https://etherscan.io/">Etherscan</a> (or equivalent) as your block explorer.</p><p>It&#8217;s nice to see some data through the front-end, but our primary focus is on the contracts.</p><h2>Concepts</h2><p>Aave has several domain-specific terms and concepts. Not all of these concepts are relevant to us, but some merit a brief overview.</p><h3>Liquidity Pool</h3><p>Uniswap has the concept of individual liquidity pools holding a pair of assets. Each pool has a distinct contract address that holds the balance of each assets.</p><p>Aave refers to an instance of a market (and all of its related contracts) as a liquidity pool. Assets are not held by a single contract, rather they are managed by several distinct ones.</p><h3>Reserve</h3><p>A reserve is a generic name for a token held by an Aave liquidity pool. Individual reserve assets are approved and enabled by the protocol for use. The characteristics of each asset vary, so reserves have several parameters to control how they can be used by the protocol:</p><ul><li><p>Loan-to-Value (LTV) &#8212; a percentage specifying the maximum size of the debt that can be created against the reserve</p></li><li><p>Liquidation Threshold &#8212; a percentage specifying the LTV where a position is considered undercollateralized and eligible for liquidation</p></li><li><p>Borrowing Enabled &#8212; true/false</p></li><li><p>Caps &#8212; upper limits on both the supplied and borrowed amounts</p></li><li><p>Interest Rate Model &#8212; parameters to set the shape of the interest rate curve</p></li></ul><h3>aToken</h3><p>An <a href="https://aave.com/docs/aave-v3/smart-contracts/tokenization#atoken">aToken</a> is an ERC-20 that represents a claim on some Aave reserve asset. When a liquidity provider supplies an asset, an amount of some aToken is minted to them. To withdraw that asset, the corresponding aToken is burned and the asset is returned.</p><p>The aToken contract is standalone and holds its underlying asset. The asset can be withdrawn only by the Pool contract calling <code>burn</code> on behalf of a user.</p><h3>vToken</h3><p>A <a href="https://aave.com/docs/aave-v3/smart-contracts/tokenization#variabledebttoken">vToken</a> is an ERC-20 that represents a variable rate debt position. It is non-transferable and used to track debts for by a particular user. </p><h3>Interest Rate</h3><p>The interest rate is a percentage of the loan amount that must be repaid by borrowers to close their debt position. There are two interest rates: one for borrowing; one for supplying. The rate for borrowing is higher than the rate for supplying, and the difference is kept as a fee to the protocol.</p><p>Each interest rate curve is upward sloping with respect to usage ratio, the fraction of all open debt positions against the collateral. The interest rate has two slopes. The higher of the two is used after the LTV for the market crosses an optimal point.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1psL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1psL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png 424w, https://substackcdn.com/image/fetch/$s_!1psL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png 848w, https://substackcdn.com/image/fetch/$s_!1psL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png 1272w, https://substackcdn.com/image/fetch/$s_!1psL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1psL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png" width="1060" height="688" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:688,&quot;width&quot;:1060,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50256,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/179837891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1psL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png 424w, https://substackcdn.com/image/fetch/$s_!1psL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png 848w, https://substackcdn.com/image/fetch/$s_!1psL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png 1272w, https://substackcdn.com/image/fetch/$s_!1psL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa47e1f53-654a-4d49-aa46-9b71361f74a6_1060x688.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The net effect of this dual-slope strategy is that loan creation is encouraged by low borrowing rates when LTV is low, and discouraged by high borrowing rates when LTV is high. </p><p>Since interest rates are continuously variable, the total interest paid to a liquidity provider is calculated instantaneously at withdrawal time.</p><p>Liquidity providers are paid their accumulated interest through a scaling mechanism whereby their share of the reserve asset is multiplied by a ratio corresponding to the current liquidity index which is dynamically adjusted. </p><h3>Utilization Rate</h3><p>Each asset has a target utilization rate set by protocol governance. The protocol-wide LTV for a particular asset is rolled into this value. The transition between the two  slopes on the interest rate curve occurs at this target optimal utilization rate.</p><p>This value is shown on the front-end, and can also be read from the asset&#8217;s holding contract.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IrLr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IrLr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png 424w, https://substackcdn.com/image/fetch/$s_!IrLr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png 848w, https://substackcdn.com/image/fetch/$s_!IrLr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png 1272w, https://substackcdn.com/image/fetch/$s_!IrLr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IrLr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png" width="1456" height="601" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:601,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44584,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/179837891?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IrLr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png 424w, https://substackcdn.com/image/fetch/$s_!IrLr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png 848w, https://substackcdn.com/image/fetch/$s_!IrLr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png 1272w, https://substackcdn.com/image/fetch/$s_!IrLr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7978fd24-9890-4f33-b2ce-371ffcf97949_1475x609.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Interest rate curve for Wrapped ETH</figcaption></figure></div><h3>Efficiency Mode (E-Mode)</h3><p>For correlated assets like stablecoins or ETH derivates, Aave provides a special mode for borrowing that allows for higher LTVs.</p><h3>Isolation Mode</h3><p>The protocol is at maximum risk when a volatile asset backs a loan for another volatile asset.</p><p>Nevertheless, some new assets may be desirable, so Aave allows them to be used as collateral against less volatile assets only. A typical use case is allowing a new volatile asset to be supplied as collateral for loans of stablecoins.</p><h2>Console Exploration</h2><p>Throughout the series, console code will be provided. The underlying tool is Ape Framework, most often connected to an Ethereum mainnet RPC. </p>
      <p>
          <a href="https://www.degencode.com/p/aave-part-ii-pool-contract">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Aave — Part I]]></title><description><![CDATA[Introduction to Lending Protocols]]></description><link>https://www.degencode.com/p/aave-part-i-introduction-to-lending-protocols</link><guid isPermaLink="false">https://www.degencode.com/p/aave-part-i-introduction-to-lending-protocols</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Sat, 22 Nov 2025 00:02:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MZ5a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A lending protocol is a set of smart contracts that facilitate loans against a pool of user-supplied deposits. </p><p>The individual mechanisms are simple:</p><ul><li><p>A depositor can supply an asset to the protocol</p></li><li><p>A depositor can withdraw their previously supplied asset, plus accumulated interest, up to the unborrowed amount currently held by the protocol</p></li><li><p>A depositor can also be a borrower, and can create a loan against some other assets</p></li><li><p>Borrowers must repay the full amount of their loan, plus accumulated interest, to close their loan</p></li></ul><p>The value of a deposit must exceed the loan amount at the time of generation, a practice known as &#8220;overcollaterilization&#8221;. This provides a safety margin so the protocol and its users can react appropriately to fluctuating collateral values.</p><h2>Bad Debt</h2><p>Since the protocol distributes assets deposited by its users, there is a risk of insolvency if loans are not repaid.</p><p>For a given loan, if the value of its collateral assets falls below the value of the loaned assets, it is unlikely that the borrower will repay it &#8212; a rational borrower would prefer to hold the loaned assets instead of returning it for their devalued deposit. Loans like this are labeled &#8220;bad debt&#8221; because they create unsatisfied obligations to two groups of depositors &#8212; one supplying the collateral asset, and one supplying the loaned asset.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MZ5a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MZ5a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!MZ5a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!MZ5a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!MZ5a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MZ5a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg" width="1408" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1408,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:759046,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.degencode.com/i/179575540?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MZ5a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!MZ5a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!MZ5a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!MZ5a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F795ecba0-a9a2-4590-976e-bd5a37930383_1408x768.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bad debt hurts both groups because a deposit asset can only be withdrawn up to the unborrowed amount &#8212; that is, only the amount of an asset held by the pool and not currently given to a borrower. </p><p>In the case of excessive bad debt, a lending protocol can experience a &#8220;bank run&#8221; event where only a fraction of users can withdraw unborrowed assets, and everyone after is unable to withdraw.</p><h2>Liquidations</h2><p>Since bad debt is dangerous, the protocol needs safeguards and mechanisms to reduce or remove bad debt. </p><p>Some protocols manage a separate asset reserve that can make depositors whole following a liquidity event. This is outside the scope of our study, so we won&#8217;t concern ourselves with this.</p><p>The first defense against accumulating bad debt is to incentivize third parties to liquidate (repay) unhealthy loans. As the asset value ratio of collateral vs. borrowed falls, liquidators are incentivized to repay a portion of the loan in exchange for an inflated share of the collateral. </p><p>The primary effect is reducing the outstanding obligation to the loaned asset&#8217;s depositors. But the secondary effect is <em>increasing</em> the outstanding obligation to the collateral asset&#8217;s depositors.</p><p>These effects seemingly antagonize the two groups &#8212; the loaned asset&#8217;s depositors are being made whole at the cost of the collateral asset&#8217;s depositors!</p><p>However, remember that every loan is overcollateralized. Thus each collateral asset across the protocol should have a net positive amount of pooled liquidity. This extra balance is what pays for the liquidation incentive, and lending protocols often implement variable interest rates to encourage users to shift their behavior towards either deposits or borrowing.</p><h2>Aave</h2><p>According to <a href="https://defillama.com/protocols/lending">DefiLlama</a>, <a href="https://aave.com/">Aave</a> is the largest lending protocol by total volume locked (TVL).</p><p>There are three major versions of Aave, presented below with a link to the white paper, date of first deployment to Ethereum mainnet, and a snapshot of TVL:</p><ul><li><p><a href="https://github.com/aave/aave-protocol/blob/master/docs/Aave_Protocol_Whitepaper_v1_0.pdf">V1</a> &#8212; Jan 2020, <a href="https://defillama.com/protocol/aave-v1">$10.3 million</a></p></li><li><p><a href="https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf">V2</a> &#8212; Dec 2020, <a href="https://defillama.com/protocol/aave-v2">$187.8 million</a></p></li><li><p><a href="https://github.com/aave-dao/aave-v3-origin/blob/main/docs/Aave_V3_Technical_Paper.pdf">V3</a> &#8212; March 2022, <a href="https://defillama.com/protocol/aave-v3">$30.3 billion</a></p></li></ul><p>Aave V3 manages a majority of current lending activity, so we will focus on it. V3 is deployed to several chains, so the strategies we develop here will be portable.</p><h2>Moving Forward</h2><p>We are here to find profitable opportunities, and the primary way to do this on Aave is to act as a liquidator.</p><p>But before we can develop a liquidation strategy we must study: how deposits are managed; how loans are created, repaid, and closed; and how loan health is tracked.</p><p>The next entry in the series will cover the most important smart contract on Aave &#8212; <a href="https://aave.com/docs/aave-v3/smart-contracts/pool">Pool</a>.</p>]]></content:encoded></item><item><title><![CDATA[Release: 0.5.0]]></title><description><![CDATA[One More Note About Pathfinding]]></description><link>https://www.degencode.com/p/release-050</link><guid isPermaLink="false">https://www.degencode.com/p/release-050</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Thu, 20 Nov 2025 22:01:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The path finders are integrated into <a href="https://pypi.org/project/degenbot/">degenbot</a> and I will be publishing 0.5.0 shortly &#8212; hooray! It&#8217;s been a long road but I&#8217;m pleased with the database integration, pathfinding improvements, removing the soft dependency on Cryo for chain data extraction, and needing to statically generate JSON pool data and arbitrage paths.</p><p>You should see it hit PyPi today once I finalize some release checks and final tests. As always, install it with <code>pip</code> or <code>uv</code>.</p><h2>Multiple Routes</h2><p>In the <a href="https://www.degencode.com/p/new-features-uniswap-v4-database-integration-and-path-finding">previous post</a> I mentioned the ability to provide a mapping between equivalent tokens. However as I continued scaling the path finder to work for arbitrary depth path searches, I realized it was clunky and refactored it.</p><p>I ultimately refactored the equivalent tokens map with the more general ability to specify a list of start and end tokens. This accomplishes the same goal while simplifying the control flow.</p><p>Here&#8217;s a sample function call taken from a test:</p><pre><code>def test_generic_algo_multiple_tokens():
    depth = 2

    # UniswapV4 pools hold both native and WETH pairs, so paths to 
    # and from both tokens can be found using this single type
    pool_types: list[type] = [UniswapV4PoolTable]

    generic_paths_weth_to_weth = list(
        find_paths(
            chain_id=BASE_CHAIN_ID,
            start_tokens=[WETH_BASE_ADDRESS],
            end_tokens=[WETH_BASE_ADDRESS],
            max_depth=depth,
            pool_types=pool_types,
        )
    )

    generic_paths_weth_to_native = list(
        find_paths(
            chain_id=BASE_CHAIN_ID,
            start_tokens=[WETH_BASE_ADDRESS],
            end_tokens=[ZERO_ADDRESS],
            max_depth=depth,
            pool_types=pool_types,
        )
    )

    generic_paths_weth_to_weth_or_native = list(
        find_paths(
            chain_id=BASE_CHAIN_ID,
            start_tokens=[WETH_BASE_ADDRESS],
            end_tokens=[WETH_BASE_ADDRESS, ZERO_ADDRESS],
            max_depth=depth,
            pool_types=pool_types,
        )
    )

    assert len(
        generic_paths_weth_to_weth_or_native
    ) == len(
        generic_paths_weth_to_weth
    ) + len(
        generic_paths_weth_to_native
    )</code></pre><p>In this test, three distinct path finding searches are performed through the set of known Uniswap V4 pools on Base mainnet: </p><ul><li><p>WETH &#8594; WETH</p></li><li><p>WETH &#8594; Ether</p></li><li><p>WETH &#8594; WETH | Ether (a hybrid search with multiple destinations)</p></li></ul><p>At the end, the number of paths found by the hybrid search is confirmed to match the sum of the paths found by the two single-token searches. </p><p>I also included a more complex assertion after that that confirms that all of the paths match exactly, but that required some sorting and transformation, so it&#8217;s not relevant here.</p><p>You can also run a multiple start / multiple end search:</p><pre><code>generic_paths_weth_or_native_to_weth_or_native = list(
    find_paths(
        chain_id=BASE_CHAIN_ID,
        start_tokens=[WETH_BASE_ADDRESS, ZERO_ADDRESS],
        end_tokens=[WETH_BASE_ADDRESS, ZERO_ADDRESS],
        pool_types=pool_types,
    )
)</code></pre><p>Ultimately this searches four routes, returning results as the <a href="https://en.wikipedia.org/wiki/Cartesian_product">Cartesian product</a> of the inputs:</p><ul><li><p>WETH &#8594; WETH</p></li><li><p>WETH &#8594; Ether</p></li><li><p>Native Ether &#8594; WETH</p></li><li><p>Native Ether &#8594; Ether</p></li></ul><h2>Variable Depth</h2><p>The default argument to <code>find_paths</code> will yield all paths with minimum depth equal to 2. A path&#8217;s depth is measured by the number of pools it contains. </p><p>There is an optional maximum depth argument, which defaults to <code>None</code>! Beware of leaving this at the default, because the search will continue &#8220;forever&#8221; if allowed. </p><p>There is a finite limit on the number of routes through a graph since an edge (pool) can only be used once, but for practical purposes you should always set the maximum depth to a value between 2-4. The number of possible routes increases exponentially as you increase depth.</p><pre><code><code># Find 2-pool &amp; 3-pool paths
find_paths(
    chain_id=BASE_CHAIN_ID,
    start_tokens=[WETH_BASE_ADDRESS],
    end_tokens=[WETH_BASE_ADDRESS],
    min_depth=2,
    max_depth=3,
)</code></code></pre><p>To perform a fixed length search, set the minimum and maximum depth equal:</p><pre><code># Find only 2-pool paths
find_paths(
    chain_id=BASE_CHAIN_ID,
    start_tokens=[WETH_BASE_ADDRESS],
    end_tokens=[WETH_BASE_ADDRESS],
    min_depth=2,
    max_depth=2,
)

# Find only 3-pool paths
find_paths(
    chain_id=BASE_CHAIN_ID,
    start_tokens=[WETH_BASE_ADDRESS],
    end_tokens=[WETH_BASE_ADDRESS],
    min_depth=3,
    max_depth=3,
)</code></pre><p>As a curiosity, Uniswap V4 allows for a WETH-Ether pair, which technically satisfies a mixed asset 1-pool path. Since this pool is a special case and a bit of a nuisance (deposits and withdrawals through the Wrapped Ether contract are always 1:1), I implicitly exclude it by setting the default depth to 2.</p><p>It&#8217;s usually not what you want, but in the interest of flexibility you can set the minimum depth to 1.</p><h2>Moving Forward</h2><p>With all this wrapped I&#8217;m putting two related topics on the roadmap: liquidations and lending protocols, beginning with Aave.</p>]]></content:encoded></item><item><title><![CDATA[New Features: Uniswap V4 Database Integration & Path Finding]]></title><description><![CDATA[Getting Ready for 0.5.0]]></description><link>https://www.degencode.com/p/new-features-uniswap-v4-database-integration-and-path-finding</link><guid isPermaLink="false">https://www.degencode.com/p/new-features-uniswap-v4-database-integration-and-path-finding</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Thu, 13 Nov 2025 19:53:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have published a new beta of degenbot, <a href="https://pypi.org/project/degenbot/0.5.0b4/">0.5.0b4</a> which adds two key features.</p><h2>Uniswap V4</h2><p>The <a href="https://github.com/BowTiedDevil/degenbot/blob/main/src/degenbot/uniswap/v4_liquidity_pool.py">UniswapV4Pool</a> class now performs a lookup from the database at construction time. If you have activated and updated either of the built-in exchanges (<code>base_uniswap_v4</code> or <code>ethereum_uniswap_v4</code>), the class can skip many onchain calls needed to look up tokens, fee, tick spacing, etc.</p><p>Consequently many of the inputs to the construction are now optional. If you build a V4 pool that exists in the database, you only need to provide its pool manager address and pool ID. If you aren&#8217;t using the database, you can continue passing the values in as before.</p><h2>Path Finding</h2><p>I have also included an arbitrage path finding function <code>find_paths()</code>. It uses the database to discover pools and their tokens, then works through them to find pool paths in increasing depth. </p><p>This is a key unlock because it means that paths can be built during runtime, instead of needing to prebuild them with a separate script.</p><p>Here is an example of the simplest case, a two-pool cycle using some arbitrary profit token:</p><pre><code>for arb_path in find_paths(
    chain_id=8453,
    start_token=WETH_ADDRESS,
    end_token=WETH_ADDRESS,
    max_depth=2,
):
    for pool in arb_path:
        # [create pools and build the arbitrage helper here]</code></pre><p>The path finding function also supports limiting the pools to a subset of types that identify the database types:</p><pre><code><code>for arb_path in find_paths(
    chain_id=8453,
    start_token=WETH_ADDRESS,
    end_token=WETH_ADDRESS,
    max_depth=2,
    pool_types=[
        UniswapV2PoolTable,
        UniswapV4PoolTable,
    ],
):
    for pool in arb_path:
        # [create pools and build the arbitrage helper here]</code></code></pre><p>This is very useful if you have a specialized contract that is specific to V4-V2 arbs like this one I shared:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;6d1b2412-9b16-4489-9d09-92c2f3c4799e&quot;,&quot;caption&quot;:&quot;I deployed some rough V4/V2 arbitrage bots to Base for observation and testing early this week. They landed a few hundred tiny arbs &#8212; couch cushion money to be fair &#8212; but watching them helped me squish some bugs, and the system is functioning as expected. So let&#8217;s dig in!&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Project: Uniswap V4/V2 Two-Pool Arbitrage Bot&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:33970100,&quot;name&quot;:&quot;BowTiedDevil&quot;,&quot;bio&quot;:&quot;Self-taught coder, engineer, blockchain enthusiast, technical writer. &quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/55abe25f-1bc7-46e5-94d4-7d109d5d3a3d_1620x1620.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:100}],&quot;post_date&quot;:&quot;2025-03-28T22:44:31.308Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!c9Qs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F267588e7-4bee-40c0-a0af-d6033452e21e_1024x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.degencode.com/p/project-uniswap-v4-v2-two-pool-arbitrage-bot&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:159928545,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:14,&quot;comment_count&quot;:0,&quot;publication_id&quot;:607913,&quot;publication_name&quot;:&quot;Degen Code&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!G6XT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>It also accepts a list of equivalent tokens, for instance Ether &amp; WETH. This is very useful for cases where an execution contract can perform wrapping and unwrapping in between pools. This example would find all paths that take the forms WETH-X &#8594; Ether&#8594;X and Ether-X &#8594; WETH-X:</p><pre><code><code>for arb_path in find_paths(
    chain_id=8453,
    start_token=WETH_ADDRESS,
    end_token=V4_NATIVE_ADDRESS,
    max_depth=2,
    equivalent_tokens=[(WETH_ADDRESS, V4_NATIVE_ADDRESS)],
):
    for pool in arb_path:
        # [create pools and build the arbitrage helper here]</code></code></pre><p>In the case of two equivalent profit tokens, the ordering of the inputs is arbitrary. Reversing the start and end works as expected and yields the same pools.</p><p>Currently the only supported paths are two-pool, but I am working on pools of arbitrary length and will include that in the coming stable release.</p><h3>Talkin' 'bout My Generator</h3><p>The function is a <a href="https://docs.python.org/3/glossary.html#term-generator">generator</a> which must be consumed.</p><p>You can either loop through it using <code>for</code> or <code>next</code>, or capture all of the paths in a container (<code>tuple</code>, <code>list</code>, <code>deque</code>, etc). It&#8217;s simple enough once you&#8217;ve seen it, but don&#8217;t panic when a familiar operation like <code>len(find_paths(&#8230;))</code> or <code>find_paths(&#8230;)[0]</code> fails.</p><p>The generator approach has two key benefits:</p><ul><li><p>Path finding can start immediately &#8212; improving time-to-first-calculation</p></li><li><p>Memory use is minimized &#8212; each path is built and given directly to the consuming function instead of being held until the path search is complete</p></li></ul><h2>Final Release Soon</h2><p>I&#8217;m pleased with the new features in testing, and have already retired my Cryo extraction and two-pool path finding scripts. After I convert the remaining path finding scripts for 3 or more tokens, it should be much simpler to manage and consolidate all of this stuff.</p><p>This <em>should</em> be the final beta release ahead of 0.5.0, so please report bugs if you find them.</p>]]></content:encoded></item><item><title><![CDATA[New Feature: Mainnet Exchanges & Pool Updaters]]></title><description><![CDATA[Getting Ready for 0.5.0]]></description><link>https://www.degencode.com/p/new-feature-mainnet-exchanges-and</link><guid isPermaLink="false">https://www.degencode.com/p/new-feature-mainnet-exchanges-and</guid><dc:creator><![CDATA[BowTiedDevil]]></dc:creator><pubDate>Thu, 30 Oct 2025 20:36:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6XT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F7268965e-ca71-4fa1-aaee-6387622ce3ca_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hot on the heels of degenbot <a href="https://pypi.org/project/degenbot/0.5.0b2/">0.5.0b2</a>, I have published <a href="https://pypi.org/project/degenbot/0.5.0b3/">0.5.0b3</a> which adds a set of exchanges for Ethereum mainnet:</p><ul><li><p>Pancakeswap &#8212; V2, V3</p></li><li><p>Sushiswap &#8212; V2, V3</p></li><li><p>Uniswap &#8212; V2, V3, V4</p></li></ul><p>These can be activated and updated like the Base mainnet exchanges featured in the previous beta releases.</p><p>I am targeting two more features before the official 0.5.0 release: pathfinding directly from the database, and integrating database lookups into the Uniswap V4 pool class.</p>]]></content:encoded></item></channel></rss>