<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Break Into DeFi</title>
        <link>https://paragraph.com/@break-into-defi</link>
        <description>Breaking down the complex and advanced mechanics used in DeFi protocols.</description>
        <lastBuildDate>Fri, 26 Jun 2026 09:32:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Break Into DeFi</title>
            <url>https://storage.googleapis.com/papyrus_images/9e151e5dbae9c8a52b5c89d8b87e36a9ad98d4910b5a737f62cb687df77714d9.jpg</url>
            <link>https://paragraph.com/@break-into-defi</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[The Mechanics Behind Swap Execution and Liquidity-Driven Outcomes]]></title>
            <link>https://paragraph.com/@break-into-defi/mechanics-of-uniswap-v3-liquidity-distribution</link>
            <guid>sTn1XhEM1jJnFWW3Gr10</guid>
            <pubDate>Thu, 02 Oct 2025 20:46:56 GMT</pubDate>
            <description><![CDATA[Every Uniswap V3 swap looks simple from the outside, you send one token in and receive another but; under the hood, it’s a staircase of micro-operations. According to Uniswap’s architecture, each swap transaction climbs through discrete steps that correspond to tick intervals. You can think of it as a trader walking up a staircase made of ticks, where every step represents a price range with its own liquidity depth. The engine moves one step, computes how much input is consumed and output rec...]]></description>
            <content:encoded><![CDATA[<p><strong>Every Uniswap V3 swap looks simple from the outside, you send one token in and receive another but; under the hood, it’s a staircase of micro-operations.</strong> According to Uniswap’s architecture, each swap transaction climbs through discrete steps that correspond to tick intervals. You can think of it as a trader walking up a staircase made of ticks, where every step represents a price range with its own liquidity depth. The engine moves one step, computes how much input is consumed and output received, updates prices, then moves to the next until the trade completes.</p><p>Inside the pool contract, three key data structures orchestrate this process:</p><p><strong>swapCache</strong> — a set of static data cached in memory for efficiency. It contains immutable parameters relevant to the swap (starting liquidity, protocol fee settings, and block timestamp). These values remain constant throughout the transaction.</p><p><strong>swapState</strong> — a dynamic cache tracking live progress: the current tick, the current square-root price (sqrtPriceX96), remaining input/output amounts, and cumulative output so far. As the algorithm iterates through ticks, swapState evolves with every step.</p><p><strong>stepComputations</strong> — the ephemeral micro-cache used for each individual tick step. It stores transient values: the next tick index, the amount of token in or out for that step, the liquidity at the tick boundary, the next sqrtPriceX96, and a flag indicating whether the next tick is initialized. When a tick is initialized, a “jump” occurs and liquidity instantly changes as the algorithm crosses a range boundary.</p><p>Although stepComputations lives only for the duration of each tick iteration, the data it produces is extremely rich. By tracing these internal calculations during swap execution, one can reconstruct micro-level dynamics: how much slippage occurred per tick, how rapidly active liquidity was consumed, and how many tick crossings (and therefore gas-heavy jumps) were required. These traces provide an empirical window into execution quality, liquidity utilization, and the root causes of slippage.</p><h3 id="h-liquidity-as-the-determinant-of-price-impact" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Liquidity as the Determinant of Price Impact</h3><p>Uniswap V3’s fundamental insight is that price is a derivative of available liquidity. Within any active tick range, swaps obey the constant-product formula just like Uniswap V2, but the size of that mini-pool — its local liquidity — determines how much the price moves per unit traded. Thin liquidity amplifies price movement; dense liquidity dampens it.</p><p><strong>Mathematically:</strong></p><p>Price Impact = 1 / Active Liquidity</p><p>Hence:</p><p>More liquidity → smaller price change → lower slippage<br>Less liquidity → larger price change → higher slippage</p><p>Active liquidity is the liquidity available at the range of the current price or tick where trading happens. Only this active slice participates in a swap at any given moment. The inactive ranges remain dormant until the market price migrates into them. Consequently, swap outcomes depend not only on total liquidity but on where that liquidity resides.</p><h3 id="h-from-lp-incentives-to-liquidity-distribution" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">From LP Incentives to Liquidity Distribution</h3><p>Capital efficiency in Uniswap V3 comes from allowing LPs to “concentrate their capital within custom price ranges, achieving higher capital efficiency than V2” (<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.uniswap.org/whitepaper-v3.pdf">Uniswap Whitepaper, 2021</a>). But capital efficiency means little unless it also yields profit. Since LPs earn fees only when trades occur inside their chosen range, their pursuit of profit dictates how liquidity is distributed. The result is not random; it’s behavioral economics embedded in code.</p><p>Gauntlet’s 2022 study found that LPs “tend to concentrate around the current price tick, leading to high depth near spot but thinner tails.” Delchain Research described this as a “clustering” effect where “execution for small trades is excellent, but larger orders face gaps.” <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://panoptic.xyz/research/how-concentrated-is-uniswap-concentrated-liquidity?utm_source=chatgpt.com">Panoptic’s</a> 2023 data went further: fewer than 8% of LPs provide more than 80% of total liquidity, and in some pools, just 1% of LPs control the majority — revealing both concentration and centralization.</p><p>This hierarchy of participation produces distinct execution regimes depending on how liquidity is distributed across ticks.</p><h3 id="h-a-dense-active-tick-range" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">(a) Dense Active Tick Range</h3><p>When the active tick range is dense, it means multiple LPs have concentrated liquidity tightly around the current tick. This situation is common in stable pairs like USDC/DAI or ETH/USDC, where volatility is low and LPs can confidently expect the price to remain close to its current level for long periods.</p><p>For small trades, this is ideal: because the stepComputations within the swap function find abundant liquidity at the current sqrtPriceX96, the price impact per token swapped is tiny. Each step of the swap consumes liquidity smoothly, and the swapState updates gently with minimal slippage. The trader experiences deep liquidity, low price movement, and excellent execution quality.</p><p>For large trades, the same density offers partial protection: while the beginning of the trade executes within dense liquidity, as the amount grows, the swap gradually consumes all active liquidity and begins to step into less populated ticks. Once that happens, execution quality drops — not because liquidity disappears, but because each additional step (each new tick crossed) holds progressively less liquidity than the dense band near spot. Thus, even dense active liquidity only supports low-slippage execution up to a limit proportional to its total size. After that limit, the trade suffers incremental slippage as stepComputations cross into thinner zones.</p><p>From the LP’s perspective, dense active liquidity represents a competitive zone: many LPs fighting for a share of the same trading volume. Fees per LP may be lower in absolute terms (since they’re divided among many participants), but the fee flow is steady, and capital efficiency remains high because their positions are almost always active.</p><h3 id="h-b-thin-active-tick-range" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">(b) Thin Active Tick Range</h3><p>A thin active tick range — where few LPs provide liquidity near the current tick — represents the opposite extreme. This can occur when the market is volatile, or when many LPs have withdrawn or repositioned to safer, wider ranges away from the spot price.</p><p>For small trades, the consequences are immediate: the trade begins consuming the limited active liquidity, and the price impact rises sharply even before crossing into new ticks. The swapState’s sqrtPriceX96 moves more steeply per unit of trade, indicating high slippage.</p><p>For large trades, the situation worsens drastically. Each step computation must quickly move from one tick to another, finding little liquidity in each range, and triggering multiple jumps. Gas costs rise due to frequent tick crossing, and the resulting price may deviate significantly from the starting price.</p><p>In this state, LPs who remain in the active tick range benefit disproportionately. Because all trades must flow through their limited liquidity, they earn very high fees relative to capital deployed — but the user experience for traders deteriorates due to slippage. Thin active ranges thus amplify the asymmetry between LP income and trade quality.</p><h3 id="h-c-dense-far-away-tick-ranges" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">(c) Dense Far-Away Tick Ranges</h3><p>Sometimes, the current active range is relatively thin, but liquidity exists densely farther away, either above or below the current tick. This can happen when LPs expect a price migration (for instance, anticipating ETH appreciation or a stablecoin depeg). These dense far-away zones represent “liquidity walls” waiting to activate.</p><p>For small trades, these distant dense bands are irrelevant, as execution never reaches them. But for large trades that push the price far enough, these zones act as buffers — once stepComputations reach them, slippage slows down because each step now consumes abundant liquidity again. The execution curve flattens temporarily.</p><p>However, from a system perspective, this means execution quality across the price range becomes nonlinear — smooth in dense areas, jagged in thin gaps. For LPs in these far-away dense zones, profitability is intermittent: they earn nothing until price migrates into their range, then suddenly capture high fees during active use, before becoming inactive again once the price moves away.</p><h3 id="h-d-thin-far-away-tick-ranges" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">(d) Thin Far-Away Tick Ranges</h3><p>Finally, the most fragile scenario occurs when both the active tick range and far-away ticks are thin. This patchy distribution often appears after large liquidity withdrawals or in less-traded pools. Both small and large trades then suffer: even modest swaps can move the price meaningfully because every tick holds little liquidity. Execution becomes jumpy, with stepComputations leaping across multiple ticks per trade. Gas usage increases, price discovery worsens, and the pool exhibits low resilience to trading shocks.</p><p>From an LP standpoint, this environment is unattractive. Thin far-away ticks earn no fees until price volatility triggers large movements — which may or may not happen. It represents unproductive, idle capital. But because LPs are fee-driven, few are willing to populate these zones unless compensated by volatility or high trading activity.</p><h3 id="h-summary-of-effects" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Summary of Effects</h3><table style="min-width: 100px"><colgroup><col><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Liquidity Scenario</p></th><th colspan="1" rowspan="1"><p>Small Trade Outcome</p></th><th colspan="1" rowspan="1"><p>Large Trade Outcome</p></th><th colspan="1" rowspan="1"><p>LP Incentive &amp; Result</p></th></tr><tr><td colspan="1" rowspan="1"><p>Dense active range</p></td><td colspan="1" rowspan="1"><p>Excellent execution, near-zero slippage</p></td><td colspan="1" rowspan="1"><p>Good until active liquidity depletes; mild slippage beyond</p></td><td colspan="1" rowspan="1"><p>High efficiency, low per-LP share due to competition</p></td></tr><tr><td colspan="1" rowspan="1"><p>Thin active range</p></td><td colspan="1" rowspan="1"><p>Noticeable slippage, unstable price</p></td><td colspan="1" rowspan="1"><p>Severe slippage, frequent tick crossing, high gas</p></td><td colspan="1" rowspan="1"><p>High per-trade fees, but high inactivity risk</p></td></tr><tr><td colspan="1" rowspan="1"><p>Dense far-away ranges</p></td><td colspan="1" rowspan="1"><p>No immediate effect</p></td><td colspan="1" rowspan="1"><p>Acts as slippage buffer once reached</p></td><td colspan="1" rowspan="1"><p>Intermittent profits when range activates</p></td></tr><tr><td colspan="1" rowspan="1"><p>Thin far-away ranges</p></td><td colspan="1" rowspan="1"><p>Little effect for small trades</p></td><td colspan="1" rowspan="1"><p>Poor execution and instability</p></td><td colspan="1" rowspan="1"><p>Idle capital, minimal earnings</p></td></tr></tbody></table><h3 id="h-behavioral-feedback-loop" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Behavioral Feedback Loop</h3><p>The loop completes itself: LPs chase fees → they cluster liquidity near expected trading zones → that distribution determines execution quality → traders gravitate toward pools with better execution → which further rewards the clustered LPs.</p><p>Meanwhile, distant LPs adapt by widening or repositioning ranges to capture volatility events, perpetuating a dynamic equilibrium of dense centers and thin tails. Over time, this behavior concentrates both liquidity and fee income in a small subset of addresses — Gauntlet’s Pareto finding quantified exactly that.</p><p>Thus, the entire swap experience; slippage, gas cost, and execution smoothness is an emergent property of LP fee incentives interacting with Uniswap V3’s tick-based architecture.</p><h3 id="h-conclusion" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h3><p>Tracing Uniswap V3’s internal swap variables (swapCache, swapState, stepComputations) exposes how liquidity distribution directly shapes swap quality. The staircase metaphor is literal: every step corresponds to a tick, and the height between steps is the price impact. Dense liquidity flattens the steps; thin liquidity makes them steep.</p><p>Because LPs earn only when trades use their range, their rational pursuit of fees sculpts the liquidity landscape itself. This self-organizing pattern where dense clusters near spot and thin or speculative tails, explains why small trades experience seamless execution while large trades sometimes face slippage cliffs. In Uniswap V3, the geometry of liquidity is the geometry of price movement, and understanding it requires tracing not just what the swap returns, but how it walks through its steps.</p>]]></content:encoded>
            <author>break-into-defi@newsletter.paragraph.com (paul elisha)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e8f5c568a2a9ecd0f4f1387e6f25ea15b6a5403d2b337d0988f0cfa6cb49f91c.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Understanding EIP-712: A Beginner's Guide to Typed Data Signing in Ethereum.]]></title>
            <link>https://paragraph.com/@break-into-defi/understanding-eip-712-a-beginners-guide-to-typed-data-signing-in-ethereum</link>
            <guid>kurhKZUVcQ5ShscTM0V8</guid>
            <pubDate>Wed, 16 Apr 2025 14:44:18 GMT</pubDate>
            <description><![CDATA[Oftentimes, UX and security issues often arise when signing transactions securely. For example, there needs to be an easier way to read transaction data when signing a transaction. To understand signature creation, verification and preventing replay attacks work, we will take a dive into the workings of two Ethereum Improvement Proposals - EIP 191 and EIP 712. In Ethereum, proof of ownership can be achieved using public and private key pairs to generate signatures. Imagine, appending your sig...]]></description>
            <content:encoded><![CDATA[<p>Oftentimes, UX and security issues often arise when signing transactions securely. For example, there needs to be an easier way to read transaction data when signing a transaction. To understand signature creation, verification and preventing replay attacks work, we will take a dive into the workings of two Ethereum Improvement Proposals - EIP 191 and EIP 712.<br><br>In Ethereum, proof of ownership can be achieved using public and private key pairs to generate signatures. Imagine, appending your signatures on a paper document, it grants ownership of the paper to the signer. This is what digital signatures aim to achieve. These public and private key pairs define Ethereum externally owned accounts (EOAs) and provide a means of interacting with the blockchain by signing and sending transactions, without others being able to access the account they do not own.<br><br>Signatures provide a means of cryptographic authentication in the blockchain technology, serving as a unique fingerprint forming the backbone of blockchain transactions. They are used to validate computation performed off-chain and authorize transactions on behalf of a signer.</p><h2 id="h-digital-signature-creation-process" class="text-3xl font-header"><strong>Digital Signature creation process.</strong></h2><ul><li><p>Creating signatures involves hashing the message and then combining this hash (digest) with the private key using ECDSA algorithm; this process is referred to as signing a message.</p></li><li><p>Upon signing a message, a signature is created (digital signature) is generated, serving as a means to verify that the signer is indeed the intended account.&nbsp;</p></li><li><p>Each distinct message produces a unique hash called digest.</p></li></ul><p><br>This is important when considering replay attacks. To prevent replay attack, each signature has to be unique. This is achieved by including a nonce, a number used once, as part of the message. Most times the creation of digital signatures is abstracted away from the user. However, when implementing signatures into a smart contract, extra data including nonce and chain ID should be added to prevent replay attacks.</p><h1 id="h-simple-signature" class="text-4xl font-header"><strong>Simple Signature.</strong></h1><pre data-type="codeBlock" text="function getSignerSimple(
		uint256 message,
&nbsp;&nbsp;&nbsp;&nbsp;) public pure returns (address) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes32 digest = bytes32(message);
		(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);	// digital signature
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;address signer = ecrecover(hashedMessage, v, r, s);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return signer;
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp; &nbsp; function verifySignerSimple(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uint8 _v,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes32 _r,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes32 _s,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;address signer,
		uint256 message,
&nbsp;&nbsp;&nbsp;&nbsp;) public pure returns (bool) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;address actualSigner = getSignerSimple(message, v, r, _s);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;require(signer == actualSigner);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return true;
&nbsp;&nbsp;&nbsp;&nbsp;}"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSignerSimple</span>(<span class="hljs-params">
		<span class="hljs-keyword">uint256</span> message,
&nbsp;&nbsp;&nbsp;&nbsp;</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> digest <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes32</span>(message);
		(<span class="hljs-keyword">uint8</span> v, <span class="hljs-keyword">bytes32</span> r, <span class="hljs-keyword">bytes32</span> s) <span class="hljs-operator">=</span> vm.sign(privateKey, digest);	<span class="hljs-comment">// digital signature</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> signer <span class="hljs-operator">=</span> <span class="hljs-built_in">ecrecover</span>(hashedMessage, v, r, s);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">return</span> signer;
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp; &nbsp; <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifySignerSimple</span>(<span class="hljs-params">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">uint8</span> _v,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> _r,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> _s,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> signer,
		<span class="hljs-keyword">uint256</span> message,
&nbsp;&nbsp;&nbsp;&nbsp;</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> actualSigner <span class="hljs-operator">=</span> getSignerSimple(message, v, r, _s);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in">require</span>(signer <span class="hljs-operator">=</span><span class="hljs-operator">=</span> actualSigner);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
&nbsp;&nbsp;&nbsp;&nbsp;}</code></pre><p><br>This is how signatures used to be constructed for cryptographic authentication purposes. But it comes with a flaw, which is the way it display the data to sign in bytes as it comes with security risk.<br><br></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8289868a9c989368152a6803c9b2a16e.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAgCAIAAACZ9R/cAAAACXBIWXMAABYlAAAWJQFJUiTwAAAGP0lEQVR4nJ2VXVAb1xmGz10vkotOM5NepTOpO53pRe48k2QaN5PG6cSTuP4rdlEokqCWJSyBs9KCZAkY9APYEmShkEiVDAIBjiWzataEAGsJI6MBgmQrJHL4CUKSV1qtVizWiqWdKTPuSAs0dpxe9JlzsbvnO+/ueff9dsEbP3/p9Csvni47BI7+ouaCtOZizalTp8A+L/zshYPj5557/uzZszKZ7IJEUi2ulkguyGQyiUQC3Na/jzk/xn3DNsw5e3c2GAxOTU61mlpbTa0tLXqr1drZ2Wk2m41Gk9lsxjAs8s292eDd+1+HQ/cW8RJgk81ncszkpG8C+wJFUdfgIIZh8/PzGIbhOD7q8fj9/jvT0xj2j0gkkk6RoqMXb/Z7K9+V9bTaU0SKpmkQj8dj6+uBwIzP7w8Gg36/PxAIrK6uRiKRLxcWQqHQV0tLDx48CIVCkUhkbXXNeMk8+RmukbU015kWgosEQYBkIpFMJMhUispkqEwmRRAFljXo9Qq53Ga1fiAQNOp0Br2+Uaf7KhJJEUS+8CibpXb+uV3YZkmSLEqkKSrHslmGyTJMJpfL5HJbhW3n8LAcgtwY1mQ0Nur1jXr9hRr5SjzOsIX1te+SieRGbCMejxMlgM/lQkTiayqVS9fs1Gh7pdLRDmQMQbzt7UNqzWdXzbftDszcOWW19kGq0Y5OhuOIJwF3R73Kw68ilULTyTPd4ip73SUHpPxIVGU4cfL8y79ueONN+4dQj7TmE7nCIqjolcoWJqbisdgTEjNud9vps6aTZ9pO/6m/vsGmUNgUcku5wAEpTSfPIKIqV1PTqNliOH6i57zUXH7O8N6JeGwjTVHJRCLNexEcu9VeVmYRVBiOn2h4/YhL18yvv2FqtQgqEKG457zEASkHdTrDe390arQOCLIpai2CiobXj8x6sUwuB+bGPkcqhddUqm6hWHn4VQekvKZSIaIqm6K2Q/CXfri+8ffv2BQKRCi+UnYOEYqQSmGXqBp3DkQXw7GV1eJTeBFEf+z9XqmsVyr9pKam57ykW1xlEVTYFLVXys6N2+1DTc1IpdCmqO0SVSsPv9Yl+mu3uCrs8+Xyj/a8uBcIWMoFvFX99Q0WQcWAprFbXOVubUeEoi5R9ZWycx0C4bjdcU2l6m9QI8KqXqksuhhOk2QykSx54fXqj71vKf+zRVAx6UBQs+xWlwof7Jiwt0zYW/yuq9jf6u982pWmsmmSTFMUP554I8uRyKLPF5oN3wtGlr+ORkPBpTn/ylL42/sL0fDcWvR+NDz37f2FeCzGt0IykYiX+K9EKp1iCjtzmPxjGMSWxqlNlspmqSxNZXP0JlO8J5lNU9ncZo4ukc1SJEnSNJ0iUnsSOZqZnPocAPBTAI7+BuR3/jWGYRq1uqmpsba2NhgM5vOP7kxPK+SKurq66upqJQyjKGrQG5CuLpLMFCWoTGZpde3gu/L48WOXy6XVao1GIwzXzwaDOZpeWVm+fdvHN/HI8LDZbNZqtXa7fa/NqExmembGJBb96uVDg+5RJpebmJjo7Ozs6+uzWq2hUIivYxiGpul8Pj8/P2+324eGhnAc39sITdO3sFt916+PfzGO3ry5vLLCb5WHIAjevPV9CILgp0iS5KcAQRA9PT2wUqVWq9va2gosW2BZjtvOl+A4bof7Ptsct11g2dLUNsdx+Xx+XwKG5XLFpQ8hna75slanhGGt9rJarYZhGIIgGFZp1GoIglRwvfqyViargSBIo9Y0NzcbjcaihN/vR0uMOB03XH0jTofH40ZRFMMwj8fj/tTt8XhGPUWG+h0jTvvIkIu/gmGY1+stSmRIcmd394ax/C0Ajv8EvAXA1cpf7uzu5uhsPp/fYra2GHpndzcw0v4OAH8A4INDgN3mth5tMSWKEhux9U1up/fib48A8C4AvwMAfhvQLBdbX+MNi62v0SyHflT7JgBvA3AMgIcP92zes5MnGd9YXlqMLgaWl77ciBWd/wEPV6PhbxZn1lcfPNEj3z9JF7NMpyma2A/vU6TJLJll0qVQPluCIB7ujx/jGQVPSRD/B8W/2Q85yOIzeaq4GHCGYfgsHiTy3/+Tg0q+cQCO4yiKloJTBEVRq9UKw7D6xxkYGLheAkVRHMf/A3/muZDR1LKrAAAAAElFTkSuQmCC" nextheight="1046" nextwidth="718" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><h1 id="h-eip-191-standardizing-signatures" class="text-4xl font-header"><strong>EIP-191: Standardizing signatures.</strong></h1><p>The EIP-191, is a signed data standard that proposes the format for signed data. Unlike the signature creation mechanism explained earlier, that only does arbitrary message hashing. It introduces a standard for signing data.<br><br>&lt;prefix&gt; | &lt;1 byte version&gt; | &lt;version specific data&gt; | &lt;data to sign&gt;<br><br>Prefix (0x19) - This signifies that the data is a signature.&nbsp;</p><p>1 byte version - The version associated with the sign data.</p><ul><li><p>0x00 is the version with an intended validator.</p></li><li><p>0x01 is structured data.</p></li></ul><p><br>EIP-191 uses 0x00 which is the version with the intended validator.<br><br>Data to sign - This is the application specific data or message to sign. </p><pre data-type="codeBlock" text="function getSigner191 (
		uint256 message, 
) public view returns (address) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Arguments when calculating hash to validate
		// 0x19&nbsp; < 1 byte version>&nbsp; < version specific data>&nbsp; < data to sign>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 1: byte(0x19) - the initial 0x19 byte
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 2: byte(0) - the version byte
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 3: version specific data, for version 0, it's the intended validator address
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 4-6 : Application specific data

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes1 prefix = bytes1(0x19);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes1 eip191Version = bytes1(0);	// 0x00
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;address indendedValidatorAddress = address(this);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes32 hashMessage = bytes32(message);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes32 digest =
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;keccak256(abi.encodePacked(prefix, eip191Version, indendedValidatorAddress, applicationSpecificData));	
		(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;address signer = ecrecover(hashedMessage, v, r, s);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return signer;
&nbsp;&nbsp;&nbsp;&nbsp;}


function verifySigner191(
		uint256 message,
		uint8 _v,
		bytes32 _r,
		bytes32 _s,
		address signer
)
public view returns (bool) {
		address actualSigner = getSigner191(message, v, r, _s);
		require(signer == actualSigner);
		return true;
}"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSigner191</span> (<span class="hljs-params">
		<span class="hljs-keyword">uint256</span> message, 
</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-comment">// Arguments when calculating hash to validate</span>
		<span class="hljs-comment">// 0x19&nbsp; &lt; 1 byte version&gt;&nbsp; &lt; version specific data&gt;&nbsp; &lt; data to sign&gt;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-comment">// 1: byte(0x19) - the initial 0x19 byte</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-comment">// 2: byte(0) - the version byte</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-comment">// 3: version specific data, for version 0, it's the intended validator address</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-comment">// 4-6 : Application specific data</span>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes1</span> prefix <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x19</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes1</span> eip191Version <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0</span>);	<span class="hljs-comment">// 0x00</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> indendedValidatorAddress <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> hashMessage <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes32</span>(message);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> digest <span class="hljs-operator">=</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(prefix, eip191Version, indendedValidatorAddress, applicationSpecificData));	
		(<span class="hljs-keyword">uint8</span> v, <span class="hljs-keyword">bytes32</span> r, <span class="hljs-keyword">bytes32</span> s) <span class="hljs-operator">=</span> vm.sign(privateKey, digest);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> signer <span class="hljs-operator">=</span> <span class="hljs-built_in">ecrecover</span>(hashedMessage, v, r, s);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">return</span> signer;
&nbsp;&nbsp;&nbsp;&nbsp;}


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifySigner191</span>(<span class="hljs-params">
		<span class="hljs-keyword">uint256</span> message,
		<span class="hljs-keyword">uint8</span> _v,
		<span class="hljs-keyword">bytes32</span> _r,
		<span class="hljs-keyword">bytes32</span> _s,
		<span class="hljs-keyword">address</span> signer
</span>)
<span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
		<span class="hljs-keyword">address</span> actualSigner <span class="hljs-operator">=</span> getSigner191(message, v, r, _s);
		<span class="hljs-built_in">require</span>(signer <span class="hljs-operator">=</span><span class="hljs-operator">=</span> actualSigner);
		<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}</code></pre><p>However if the data to sign is complex, there should be a way to format the data in a readable way, EIP-712.</p><h1 id="h-eip-712" class="text-4xl font-header"><strong>EIP-712.</strong></h1><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c0c3e0b6b374dadfee41d02013b83672.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAgCAIAAACZ9R/cAAAACXBIWXMAABYlAAAWJQFJUiTwAAAFi0lEQVR4nI2VT0zbVhzH32XHajvsslXqpJZ0pdUuu++8wzRVu+zSadXUdmsnxoSKlq5rRbUSDIGooWnWSO4CzN0yWWGhKeZPAWPBC8bgYHAJRBhMnThxeeSF0EAKXVRNsSG0dJ32kQ9O9Px939/vfX82OAxABQCHDwHwJqi02Y5VVBw6eBC8hiOH3jthO3bC9v6xI0eO245+UFl53HYUdJJkb/utIZbydreN8/zE5OQwxzkcjian0+Fw3PJ6m1tabrhbm5xOgmjs6e2R56YjYxEpJk1EJ0ZGR7u7u8FKJrOSyY7zImRHBwYehMP3BgYGZVkeHBjkeZ7juLGxCM/zEQhnY7PacuLqmYb+4GDdeYImQxjjWCwGllU1mUiMjUUGh4bGBYHjOAjh3PzcVDQqiuKUJE1Fo7FYTDZR5hWi5kZ/aKiuqtF5yTM7PTc7OwM0TTMMA+PMKlrBOIMxzufzAwODDQTR0fFbg8NBkiRFUW63G0KYy+Xym0/wGt76u5DffJJdy0qSZEmk08YKym6kDUPXdYQQxw2fPXvG7XY7nc6rJqdPnw6FSs6X1WVd15OJ5LK6jBCyJB5l8JqhTE71t6bSKcN4jBAqFArFYnF7+9n2LsVisVAo5E1yuZxlNpfLlSR0PaWqi9+dBF/bQODa23i9EI1OBgKB3+/epSgqFosZJdJ9fT3lP0OhkCAIFEUpilJqZwYhOMGXT/558TnDMPX115udzrq6a6IoohIr98Nhj4nXhKKony5fnp6eLkkkEwk1lf7yHRsA4FzTrWdPn3aFQg1Eg9vdShBENBpFCOm6nl1by+fzhUIhl8vRNO3z+ZpbWmRZLkmsINTDdP/RHR5hh7pDnUtLS4ZhJHUdm1hlm0aQYTZb13VN03RdN88RW73QY7HYQH//MMsmzeOwloZCob6+vmAwyPO8oiiSJMXjcYyxsYtublOSQAiFw2GCIGiajsfjZQmiuen8hQtVVVUul8tn4vf7IYRWjvZLMAxD0zTGuOwQITTycOJibe31n0vY7Xan01ldXV1TUyNJEkLIWokxFkURGIbBcZzH43G5XBRF7eyQTpHdf7bevNlqNrWjo8Pj8fh8PqfTSZKkz+fzer17LrRdVBOrYUldjy8uWA23BqR8EzdRFEXTtJ106rouyzLDMLIsq6qq7LKoKC/+fBVLaMeFJEkej0eSpPKxlfJoVoT+E1VVS71QVVXTNAhhIBCgafrOr3cEQcjn81ai9mG8jKqqJRdltxRF0UF6nOc9rTfb2tsiEBaLRWuuMMaFQsES3Sex48LqDUmSvl9uT8xE69wNP166VF9f73K56uvrvV4vwzAEQQiCgDF+UeUlCcMwIITjPD+/pHQO3g91/tUV7gqYBINBhmECgYAsy/uM7EmoqsqybDAYpGma4zjLvzUgFta9FdzXFqIoirVnZIy/crWOZdnt7W1rNK1Xy//txW2fb/7h1PUrF3+w2xsbG6urq2tra91ut9/vt9vtEML/6gVCiOd5CCOJpfgMP0RRd9vb26xQkyRJ07TL5RJF8VWJnUPVNI3jOL/f397eJgiCo4FwuVwQQq/XSxBE6a1tVoQxfmwY5WsvWpqmxeNxURQ5jhsZhTOzsZFRCCG0vkMsy1pDUR6NBWVx4dWAW8l7bBhGKqkuzqcSaiqVsqzuy2jaMB4tq8lEMm0Ye2OmaVpKT66ub07cJz8B4Iu3wKcA2D8Cq+ub5sSWKi8tWNuI9lMnAfjqXfAZAHUfA5TdWEUrO5O6ilbWN7fmYdc3ttLD31aC2+cqNreeZbNZKxdZnNncerYwdu/UAfD9h+DUAdD8+RtrG0+z2awsy0AURUmSBEGYFMXp2XhUejg2PtX7YJj5F3p6H7AMw0TGJyaj0+M8L0kSy7L/AC79OWgPHp/RAAAAAElFTkSuQmCC" nextheight="1024" nextwidth="710" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Since EIP-191 wasn’t specific enough, the data to sign needed to be easier read and displayed. This EIP proposes a standard for hashing, structuring and signing typed data. Improving ux interaction, readability and specificity to certain contracts. The primary goal is to provide a way to prevent users from misusing signatures, allowing developers to define a clear structure for the data to sign. Due to the inefficiencies of EIP-191, there needed to be a way to send transactions using premade signatures: sponsored transactions.</p><p>This standard has a format for signed data:<br><br>Prefix 0x19<br>Version 0x01<br>DomainSeparator - the hash of the data associated with the version, the domain struct defining the domain of the message being signed.</p><pre data-type="codeBlock" text="struct EIP712Domain {
	string name;
	string version;
	uint256 chainId;
	address verifyingContract;
	// bytes32 salt; not required
}

EIP712Domain eip_712_domain_separator_struct = EIP712Domain({
name: &quot;SignatureVerifier&quot;, // this can be anything
version: &quot;1&quot;, // this can be anything
chainId: 1, // ideally the chainId
verifyingContract: address(this) // ideally, set this as &quot;this&quot;, but can be any contract to verify signatures
});"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">EIP712Domain</span> {
	<span class="hljs-keyword">string</span> name;
	<span class="hljs-keyword">string</span> version;
	<span class="hljs-keyword">uint256</span> chainId;
	<span class="hljs-keyword">address</span> verifyingContract;
	<span class="hljs-comment">// bytes32 salt; not required</span>
}

EIP712Domain eip_712_domain_separator_struct <span class="hljs-operator">=</span> EIP712Domain({
name: <span class="hljs-string">"SignatureVerifier"</span>, <span class="hljs-comment">// this can be anything</span>
version: <span class="hljs-string">"1"</span>, <span class="hljs-comment">// this can be anything</span>
chainId: <span class="hljs-number">1</span>, <span class="hljs-comment">// ideally the chainId</span>
verifyingContract: <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>) <span class="hljs-comment">// ideally, set this as "this", but can be any contract to verify signatures</span>
});</code></pre><br><pre data-type="codeBlock" text="function getSignerEIP712(
		uint256 message, 
) public view returns (address) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Prepare data for hashing
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes1 prefix = bytes1(0x19);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes1 eip712Version = bytes1(0x01); // EIP-712 is version 1 of EIP-191
		
		struct Message {
			uint256 number;
			uint256 nonce;	// inserting nonce into every signature prevents replay attacks
		}

		bytes32 public constant MESSAGE_TYPEHASH = keccak256(&quot;Message(uint256 number)&quot;);
		bytes32 digest = keccak256(abi.encode(MESSAGE_TYPEHASH, Message({ number: message })));
		(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ecrecover(digest, v, r, s);
&nbsp;&nbsp;&nbsp;&nbsp;}
}

function verifySignerEIP712(
&nbsp;&nbsp;&nbsp;&nbsp;uint256 message,
&nbsp;&nbsp;&nbsp;&nbsp;uint8 _v,
&nbsp;&nbsp;&nbsp;&nbsp;bytes32 _r,
&nbsp;&nbsp;&nbsp;&nbsp;bytes32 _s,
&nbsp;&nbsp;&nbsp;&nbsp;address signer
) public view returns (bool) {
&nbsp;&nbsp;&nbsp;&nbsp;address actualSigner = getSignerEIP712(message, v, r, _s);
&nbsp;&nbsp;&nbsp;&nbsp;require(signer == actualSigner);
&nbsp;&nbsp;&nbsp;&nbsp;return true;
}"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSignerEIP712</span>(<span class="hljs-params">
		<span class="hljs-keyword">uint256</span> message, 
</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-comment">// Prepare data for hashing</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes1</span> prefix <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x19</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes1</span> eip712Version <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x01</span>); <span class="hljs-comment">// EIP-712 is version 1 of EIP-191</span>
		
		<span class="hljs-keyword">struct</span> <span class="hljs-title">Message</span> {
			<span class="hljs-keyword">uint256</span> number;
			<span class="hljs-keyword">uint256</span> nonce;	<span class="hljs-comment">// inserting nonce into every signature prevents replay attacks</span>
		}

		<span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> MESSAGE_TYPEHASH <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"Message(uint256 number)"</span>);
		<span class="hljs-keyword">bytes32</span> digest <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(MESSAGE_TYPEHASH, Message({ number: message })));
		(<span class="hljs-keyword">uint8</span> v, <span class="hljs-keyword">bytes32</span> r, <span class="hljs-keyword">bytes32</span> s) <span class="hljs-operator">=</span> vm.sign(privateKey, digest);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">return</span> <span class="hljs-built_in">ecrecover</span>(digest, v, r, s);
&nbsp;&nbsp;&nbsp;&nbsp;}
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifySignerEIP712</span>(<span class="hljs-params">
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">uint256</span> message,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">uint8</span> _v,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> _r,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> _s,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> signer
</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> actualSigner <span class="hljs-operator">=</span> getSignerEIP712(message, v, r, _s);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in">require</span>(signer <span class="hljs-operator">=</span><span class="hljs-operator">=</span> actualSigner);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}</code></pre><h2 id="h-eip-712-openzeppelin" class="text-3xl font-header"><strong>EIP-712: OpenZeppelin</strong></h2><p>It's recommended to use OpenZeppelin libraries as the standard and more secure way to integrate EIP-712 for signature creation and hashing. It comes with the function: <code>_hashTypedDataV4 </code></p><p>Create the message type hash and hash it with the message data:</p><pre data-type="codeBlock" text="bytes32 public constant MESSAGE_TYPEHASH = keccak256(&quot;Message(uint256 message)&quot;);

function _hashTypedDataV4(bytes32 hashData) public view returns(bytes32 digest) {
		return keccak256(
			abi.encodePacked(
				&quot;\x19\x01&quot;,
                domainSeparator,
				hashData
			)
		)
}

function getMessageHash(Message message) public view returns (bytes32) {
&nbsp;&nbsp;&nbsp;&nbsp;return _hashTypedDataV4(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;keccak256(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;abi.encode(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MESSAGE_TYPEHASH,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;message.number,
				message.nonce
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)
&nbsp;&nbsp;&nbsp;&nbsp;);
}"><code><span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> MESSAGE_TYPEHASH <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"Message(uint256 message)"</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_hashTypedDataV4</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> hashData</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> digest</span>) </span>{
		<span class="hljs-keyword">return</span> <span class="hljs-built_in">keccak256</span>(
			<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
				<span class="hljs-string">"\x19\x01"</span>,
                domainSeparator,
				hashData
			)
		)
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMessageHash</span>(<span class="hljs-params">Message message</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bytes32</span></span>) </span>{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">return</span> _hashTypedDataV4(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in">keccak256</span>(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MESSAGE_TYPEHASH,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;message.number,
				message.nonce
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)
&nbsp;&nbsp;&nbsp;&nbsp;);
}</code></pre><p>Retrieve the signer with ECDSA.tryRecover and compare it to the actual signer:</p><pre data-type="codeBlock" text="function getSignerOZ(
		uint256 digest, 
		uint8 v, 
		bytes32 r, 
		bytes32 _s
) public pure returns (address) {
&nbsp;&nbsp;&nbsp;&nbsp;(address signer, /* ECDSA.RecoverError recoverError /, / bytes32 signatureLength */ ) = ECDSA.tryRecover(digest, v, r, _s);
&nbsp;&nbsp;&nbsp;&nbsp;return signer;
}

function verifySignerOZ(
&nbsp;&nbsp;&nbsp;&nbsp;uint256 message,
&nbsp;&nbsp;&nbsp;&nbsp;uint8 _v,
&nbsp;&nbsp;&nbsp;&nbsp;bytes32 _r,
&nbsp;&nbsp;&nbsp;&nbsp;bytes32 _s,
&nbsp;&nbsp;&nbsp;&nbsp;address signer
) public pure returns (bool) {
&nbsp;&nbsp;&nbsp;&nbsp;address actualSigner = getSignerOZ(getMessageHash(message), v, r, _s);
&nbsp;&nbsp;&nbsp;&nbsp;require(actualSigner == signer);
&nbsp;&nbsp;&nbsp;&nbsp;return true;
}"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSignerOZ</span>(<span class="hljs-params">
		<span class="hljs-keyword">uint256</span> digest, 
		<span class="hljs-keyword">uint8</span> v, 
		<span class="hljs-keyword">bytes32</span> r, 
		<span class="hljs-keyword">bytes32</span> _s
</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
&nbsp;&nbsp;&nbsp;&nbsp;(<span class="hljs-keyword">address</span> signer, <span class="hljs-comment">/* ECDSA.RecoverError recoverError /, / bytes32 signatureLength */</span> ) <span class="hljs-operator">=</span> ECDSA.tryRecover(digest, v, r, _s);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">return</span> signer;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifySignerOZ</span>(<span class="hljs-params">
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">uint256</span> message,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">uint8</span> _v,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> _r,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">bytes32</span> _s,
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> signer
</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">address</span> actualSigner <span class="hljs-operator">=</span> getSignerOZ(getMessageHash(message), v, r, _s);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in">require</span>(actualSigner <span class="hljs-operator">=</span><span class="hljs-operator">=</span> signer);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}</code></pre><h2 id="h-signature-replay-attack-prevention" class="text-3xl font-header"><strong>Signature Replay Attack Prevention</strong></h2><p>As mentioned earlier, EIP-712 is key to preventing <strong>replay attacks.</strong></p><p>Understanding EIP-191 and EIP-712 is important for understanding how to create replay-resistant data to sign into a signature. The extra data in the structure of EIP-712 ensures replay resistance.</p><p>To prevent replay attacks, smart contracts must:</p><ol><li><p>Have every signature have a unique nonce that is validated</p></li><li><p>Set and check an expiration date</p></li><li><p>Restrict the s value to a single half</p></li><li><p>Include a chain ID to prevent cross-chain replay attacks</p></li><li><p>Any other unique identifiers (for example, if there are multiple objects to sign in the same contract/chain/etc).</p></li></ol><h1 id="h-constructing-signature" class="text-4xl font-header"><strong>Constructing Signature.</strong></h1><p>Earlier we mentioned the ux issues solved by EIP-712, and that signatures can constructed off-chain which helps in gas optimization.<br><br>Whenever you integrate EIP-712 into a project, how do you construct the signatures off-chain? This section focuses on constructing off-chain signatures using ether.js. We will be working with this project: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/PaulElisha/airdropdistribution-merkle-eip712">Airdrop Contract</a>.</p><pre data-type="codeBlock" text="import { ethers } from 'ethers';

async function generateSignature(userAddress, amount, merkleAirdropContractAddress) {
  // 1. Connect to the user's wallet
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  
  // 2. Prepare the domain and types for EIP-712 signature
  const domain = {
    name: &quot;MerkleAirdrop&quot;,
    version: &quot;1&quot;,
    chainId: await signer.getChainId(),
    verifyingContract: merkleAirdropContractAddress
  };
  
  const types = {
    AirdropClaim: [
      { name: &quot;account&quot;, type: &quot;address&quot; },
      { name: &quot;amount&quot;, type: &quot;uint256&quot; }
    ]
  };
  
  // 3. Create the value object
  const value = {
    account: userAddress,
    amount: amount
  };
  
  // 4. Request the signature from the user's wallet
  try {
    const signature = await signer.signTypedData(domain, types, value);
    return signature;
  } catch (error) {
    console.error(&quot;Error signing typed data:&quot;, error);
    throw error;
  }
}"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">ethers</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'ethers'</span>;

async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateSignature</span>(<span class="hljs-params">userAddress, amount, merkleAirdropContractAddress</span>) </span>{
  <span class="hljs-comment">// 1. Connect to the user's wallet</span>
  const provider <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.BrowserProvider(window.ethereum);
  const signer <span class="hljs-operator">=</span> await provider.getSigner();
  
  <span class="hljs-comment">// 2. Prepare the domain and types for EIP-712 signature</span>
  const domain <span class="hljs-operator">=</span> {
    name: <span class="hljs-string">"MerkleAirdrop"</span>,
    version: <span class="hljs-string">"1"</span>,
    chainId: await signer.getChainId(),
    verifyingContract: merkleAirdropContractAddress
  };
  
  const types <span class="hljs-operator">=</span> {
    AirdropClaim: [
      { name: <span class="hljs-string">"account"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"address"</span> },
      { name: <span class="hljs-string">"amount"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"uint256"</span> }
    ]
  };
  
  <span class="hljs-comment">// 3. Create the value object</span>
  const value <span class="hljs-operator">=</span> {
    account: userAddress,
    amount: amount
  };
  
  <span class="hljs-comment">// 4. Request the signature from the user's wallet</span>
  <span class="hljs-keyword">try</span> {
    const signature <span class="hljs-operator">=</span> await signer.signTypedData(domain, types, value);
    <span class="hljs-keyword">return</span> signature;
  } <span class="hljs-keyword">catch</span> (<span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    console.error(<span class="hljs-string">"Error signing typed data:"</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>)</span>;
    <span class="hljs-keyword">throw</span> <span class="hljs-function"><span class="hljs-keyword">error</span></span>;
  }
}</code></pre><h2 id="h-using-the-signature-in-a-function-call" class="text-3xl font-header"><strong>Using the Signature in a function call.</strong></h2><p>Once you have the signature, you'll need to split it into its components (v, r, s) before calling the <code>claim</code> function:</p><pre data-type="codeBlock" text="async function claimAirdrop(userAddress, amount, merkleProof, signature) {
  // Split the signature into v, r, s components
  const sig = ethers.Signature.from(signature);
  
  // Call the contract
  const tx = await merkleAirdropContract.claim(
    userAddress,
    amount,
    merkleProof,
    sig.v,
    sig.r,
    sig.s
  );
  return tx;
}"><code>async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">claimAirdrop</span>(<span class="hljs-params">userAddress, amount, merkleProof, signature</span>) </span>{
  <span class="hljs-comment">// Split the signature into v, r, s components</span>
  const sig <span class="hljs-operator">=</span> ethers.Signature.from(signature);
  
  <span class="hljs-comment">// Call the contract</span>
  const <span class="hljs-built_in">tx</span> <span class="hljs-operator">=</span> await merkleAirdropContract.claim(
    userAddress,
    amount,
    merkleProof,
    sig.v,
    sig.r,
    sig.s
  );
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">tx</span>;
}</code></pre><p>The above steps and code explained how to construct off-chain signatures in a smart contract that integrates EIP-712.</p><h1 id="h-conclusion" class="text-4xl font-header"><strong>Conclusion</strong></h1><p>EIP-712 is a valuable tool for Ethereum developers looking to enhance security and user experience in their dApps. By signing structured data, you reduce the risk of signature misuse and provide clarity to users about what they are signing. As you build and deploy your applications, consider integrating EIP-712 to ensure a safer and more user-friendly experience.</p><p><br><br><br></p>]]></content:encoded>
            <author>break-into-defi@newsletter.paragraph.com (paul elisha)</author>
            <category>eip-712</category>
            <category>defi</category>
            <category>crytography</category>
            <category>signature</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/8005694ed3a0fcc32abdf8eb1417970e.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Uniswap V3: Advanced Mechanics]]></title>
            <link>https://paragraph.com/@break-into-defi/uniswap-v3-advanced-mechanics</link>
            <guid>5PnF3G0xaOrNY59xJ49A</guid>
            <pubDate>Wed, 08 Jan 2025 12:09:19 GMT</pubDate>
            <description><![CDATA[In this article, we will explore the entire Liquidity concepts of the Uniswap V3 protocol. Requisite to following along in this guide is a proper understanding of the Liquidity concepts in Uniswap V2. In the link attached, we touched some keywords like: reserve, price, shares, quote, current or total pool liquidity. We used the following Uniswap smart contracts to explain the aforementioned concepts: Uniswap V2 Library, Uniswap V2 Pool contract, and the Uniswap V2 Router contract. To explain ...]]></description>
            <content:encoded><![CDATA[<p>In this article, we will explore the entire Liquidity concepts of the Uniswap V3 protocol. Requisite to following along in this guide is a proper understanding of the Liquidity concepts in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.com/editor/NNYzaUF9hoT4LOTHqYEn">Uniswap V2.</a></p><p><br>In the link attached, we touched some keywords like: <strong>reserve, price, shares, quote,&nbsp;current or total pool liquidity</strong>. We used the following Uniswap smart contracts to explain the aforementioned concepts:<em> </em><strong><em>Uniswap V2 Library, Uniswap V2 Pool contract, and the Uniswap V2 Router contract</em></strong><em>.</em><br><br>To explain the liquidity concepts in Uniswap V3 for maximum comprehension, we will create a sync with the V2 concepts to further easier the explanation of the V3 concepts.<br><br>In Uniswap V2, the pool contract <strong><em>tracks reserves</em></strong> to determine the current pool liquidity and price. We have reserves for token A and B as state variables. While the Uniswap V3 <strong><em>tracks liquidity and price.</em></strong><br><br>To determine the current price of token A in terms of token B, we will divide,  Reserve B / Reserve A.<br><br>Reserve A - total amount of token X in the pool.<br>Reserve B - total amount of token Y in the pool.<br><br>Using the constant product formula: X * Y = K (invariant).<br><br>Price = Y / X.<br><br>This represents the spot price of token A in terms of token B.<br><br>As mentioned in my article on Uniswap V2, the formula to derive liquidity in Uniswap V2 is:<br><br>√xy = L <br> :: xy = L^2.</p><p>Let us put things to perspective as we progress.</p><h2 id="h-the-workings-of-uniswap-v3" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>The Workings of Uniswap V3.</strong></h2><p>Uniswap V3 unlike Uniswap V2 aims to enable capital efficiency. Know that Uniswap V2 allows LPs to provide liquidity uniformly across all price ranges in a price curve from 0 to ∞. But it affects capital efficiency because the utilization of liquidity either below or above the current market price is least probable. And LPs only earn swap fees when their capital at a price point is used to facilitate trade. While this creates a passive yield strategy (like the set-and-forget pattern) - as the LPs rely on if the liquidity will be used later - it doesn’t incentivize LPs enough to make markets because their earning capacity is based on chances, and what is the chance that price will fall below or rise above? This is what V3 does significantly as it limits liquidity fragmentation.<br><br>For example: A liquidity of $1 million USDC in Uniswap V2 distributed uniformly across all price ranges in a price curve could only facilitate trades of 200 USDC while the same liquidity in a Uniswap V3 pool can facilitate a trade of up to 500,000 USDC. This is what capital efficiency means as it means that LPs earn more swap fees for making markets for volume trades.<br><br>This is made possible by an AMM algorithm called Concentrated Liquidity AMM or CLAMM. This enables LPs to provide liquidity in a specific price range. Imagine a ruler, with each line of the centimeter (<strong><em>CM</em></strong>) called tick. If 0 cm to the end (assume 30 cm) is a price curve in a liquidity pool. V3 enables LPs to only provide liquidity in a range, let’s say 10 <em>CM</em> to 15 <em>CM</em>. That is called a <strong>price range</strong>. Instead of spreading liquidity uniformly to the entire price points, you have the flexibility of determining a tick or price range where you want to concentrate liquidity into. This removes Idle liquidity and creates Liquidity depth because more trades will be facilitated in a price range with more depth than when it’s uniformly distributed. <br><br>Also, this means that LPs can only earn swap fees in a range where their liquidity is active. If a large swap occurs that shifts liquidity from the left (lower price range) to the right (upper price range) and it uses up the liquidity in that particular range, it shifts liquidity to the inactive state, that is, LPs will no longer be able to earn fees on their liquidity in that range and leaving it will create impermanent loss. This means, liquidity will no longer be passively managed because the LPs will have to remove their liquidity from an inactive pool thereby avoiding impermanent loss, to an active pool leading to an active management of liquidity. While this creates stress for the LP, it reduces slippage, removes liquidity fragmentation and makes the market efficient.<br><br>In addition, this AMM design unlocks a level of possibility where LPs can provide liquidity in a range where the current price on the curve sits above the upper or below the lower range specified by the user. That is what the Uniswap V3 range limit order is all about and LPs can create multiple positions across varying price ranges providing an opportunity to hedge against risks and capture more opportunities.<br><br><strong>Note: Liquidity concentrated in a range is called A Position and the tick-based pool functions similarly to Uniswap V2 (mini-V2).<br><br></strong>This poses a question, how do you track reserves in a tick-based pool?</p><h2 id="h-reserves" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Reserves.</strong></h2><p>Like I said earlier, Reserve is the total amount of token X and token Y in a pool. Since the Uniswap V3 tracks liquidity and price, we can determine reserves using the formula for liquidity and price below..</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9a4a1b8dab141838817acf78309092ff.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAZCAIAAADfbbvGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACyUlEQVR4nK2VPWvbQBjHD/Ihshs6eE27OEO+wA1aXBAlHkTpFTzkBSf0Frdo8aIWTEctGrwIyhWatKgF2aBA01A3oISoaWIaG9zLoBA0CWKZ+kp8sSTbqmOc/Adzeiw9v3te7jnAkhQEAbsngXBFKbUsizHm+z6ldAZfQdK2QPiHZVm5XI4x5nkeQsj3/QmOgoGvW2MdAiCEuBVC6DhO3KPv+9wvX4zzKKX1en1agCAIjUZjJHbaF2Os0he3E0Jc12WMLS0tAXDtbST0ZIAkSbu7u+HbYEyKojDGHMfhj4uLi5Ikzc/Pe543krdkQKVSwRiHgIWFBQAAhLBYLKbT6VQqxV+zbZsQoiiKruucNF6VZMDW1hYveJBUw3iRQxFCVldXpwUYhhEHBMNFHodN6KUIYBgG3wLvVAghT+iU+h8mAhBC4nmHEMYbaWZFAE3TSqVSCMAYE0LuPjYiQLlcVlU1fJxc51kAGON3sS1fdToQwtmGUjJgfW3NNE1u5RZZlnnS7hIE4B9/3N4Gc3OV9x9O2+fO77OfZ822e+FeXqYfPnqj3UyF2TA3gFq1CgCo7e213YuTZnv/+PSo2dyxDx88XgYAfPm0ndj70yCjFOVyucODA2792+sxxj5/3Vt5/Xbne/05epbNZhVFabVa49N0nJ0MEEXxqD+iuXf+26Tn3w6P+FxTVRUhJIpiPp/HGBcKBU3TKKXeQIlXSDRgRVG0bXv49Pd+NVs7+3a3273qdMJvDMNQVVXTNFmWBUGAAwmCgBDK5/Ohn6EIJEmKXzJcf1z3x/FJGNCtopSaphmfMdcAy7JkWQYAZLPZYrFYGGhzo4Bfvnq6sv5ic6MwnXhnY4zDOK4BjUbDNM1UKlUqlQzDIH3pul6rVjOZzJPl5Vq1qus6t0+W3hdf8EMapQghNHIPM8Z4THc5azeAIAhc1w2H/r2IA/4BevAkAvoBg98AAAAASUVORK5CYII=" nextheight="358" nextwidth="452" class="image-node embed"><figcaption htmlattributes="[object Object]" class="">Graph showing how reserves is tracked using price and liquidity in a Uniswap V3 Pool.</figcaption></figure><p>x.y = L^2<br>y / x = price<br><br>Using the formula for liquidity and price mentioned earlier, let us determine reserve X in the price range.<br><br><strong>Math Calculations:</strong> <em>Expressing reserve X in terms of L and P.</em><br><br>xy = L^2 ………..eq 1<br>y / x = price …….eq 2.<br><br>y = price <em> x :: px.<br>Putting y in eq 1.<br><br>x (px) = L^2<br>px^2 = L^2<br>x^2 = L^2 / p<br>x = L / √p<br><br>Putting the value for x = L / √p in eq 1.<br><br>L / √p </em> y = L^2<br>Ly / √p = L^2<br>Ly = L^2 <em> √p<br>y = L^2 </em> √p / L<br>y = L√p<br><br>These are the values of reserve of X and Y of the entire pool.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b2ee7a00aec24d7590c07cbbe46097d6.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAZCAIAAADfbbvGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACl0lEQVR4nLWVT2jTYBTAnwjzVC/iRdGL5NDLxFuLVqEHt8NAgpDbxEztYcRpNka2Q9iWDeIfMsmCaNZDBCtILvbioRQUQ9mKoAhOCvXgpjjYn1jdtxXarn2yfVtbW2Wytr9LPj7C+33vfS8vgC0G6COXyyEiz/OEEESMRqOLi4vNF7S3t9O4giBomlbeb5rA5/OVBbIst1AgSVKrBK7rIqLjOB0dHS0UEEK8Xm+DofcQMAzTwgwQ0e/3J5PJxh07AkogECCEbJY2EfHW7T59Sm+JgEY8BIcB2hBxY32jOQLXdYPBIA33+EEYtsEmZjA3N9fV1UXXr2Nvui/xAG1Pw88QsZAvNEGQTCZZlqVreuqey9ePHTzZYJUqApZlL3Z2ForFpZXV70vLq5nMWjYLANaT54iY+fEzt0V+/wIAYE6f+fjlWzwx+9JJxBIzb1Op89wVAJjWw9tpbUXPVfG/AvpqeHr6Wk9P9a2m5xcu3Ogdv2fQC78/Njn/eb4+RLWyXlwRGIbB8zz9jIulEt3P/FqLJWYQ8eHkI4ADAHD86ImR0RHbtiORiOM4e+ZREZimWRbQgxTy+dVM5kX8Fe2iU0e8vd03l1eWFWWcZVmO41iWDQQCvl2CwaAoiqZpuq5bFlcElmWFQqGaxt/IZuOJWUQslkrvZt9/+pCq79pyZVzXtW1blmX6W6wICCGKong8HgBQFCUUCgmCIIriQH//wODgVaHv7h11dGxMn9L1KX1ifELZRVVVTdOMKizLsm2bblLNTgbpdJrjOIZhHMcxTdOyLNM0DcNQVdV39tzw0JAsy8NDw9LfEKsQdpEkic7NSokikYgoijUl+rqwAADlEbsP/phF9H9Z03Nkm/pe3JNawb9ocNj9BpQeQiaGaIJ7AAAAAElFTkSuQmCC" nextheight="361" nextwidth="464" class="image-node embed"><figcaption htmlattributes="[object Object]" class="">This graph shows the reserves available for trades in a price range.</figcaption></figure><p>To factor in the price ranges, the reserve of X will change when all of Y is depleted or when X is in an active range; we can calculate the value for reserve X between the prices Pa and Pb in an active range using the formula:<br><br>If x = L / √p <br><strong>Reserve of X</strong> = L / √p - L / √pb<br><br>Note: The reserve formula for X is the reserve available for trading in a price range.<br><br>Where the reserve of Y is depleted, the current price would have shifted above the upper price.<br><br><strong>X</strong> = L (1 / √pa&nbsp; - 1 / √pb).<br><br>Since Y is depleted, this can also be likened to the maximum amount of X the price range can&nbsp; hold or a position can hold.<br><br>For Reserve Y.<br><br>If y = L√p<br><br>y = L√p - L √pa<br><br>This is the formula for Y when liquidity is in active range.<br><br><strong>Reserve of Y</strong> = L(√p - √pa)<br><br>However, where X is depleted in the range, the reserve of y is:<br><strong>Y</strong> = L(√pa - √pb).<br><br><strong>Note: The following formulas apply to different cases:</strong><br><br>x = L / √p - L / √pb<br>y = L√p - L √pa<br><br>1. When you want to derive the amount of X and Y (reserves) backing the current liquidity - it uses the current pool's state.<br>2. When you want to derive the amount of X or Y to add when adding liquidity to a pool - it uses the user specified ranges.<br><br><strong><em>For case 1:</em></strong><br>If the following parameters are true:<br><br>L - current liquidity.<br>p - current price (it could be the lower or upper price if it's an active range.<br>pb- current upper price of a range.<br>pa - current lower price of a range.</p><pre data-type="codeBlock" text="    function getAmount0ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return
            FullMath.mulDiv(
                uint256(liquidity) << FixedPoint96.RESOLUTION,
                sqrtRatioBX96 - sqrtRatioAX96,
                sqrtRatioBX96
            ) / sqrtRatioAX96;
    }

    /// @notice Computes the amount of token1 for a given amount of liquidity and a price range
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount1 The amount of token1
    function getAmount1ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
    }"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAmount0ForLiquidity</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint160</span> sqrtRatioAX96,
        <span class="hljs-keyword">uint160</span> sqrtRatioBX96,
        <span class="hljs-keyword">uint128</span> liquidity
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount0</span>) </span>{
        <span class="hljs-keyword">if</span> (sqrtRatioAX96 <span class="hljs-operator">&gt;</span> sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) <span class="hljs-operator">=</span> (sqrtRatioBX96, sqrtRatioAX96);

        <span class="hljs-keyword">return</span>
            FullMath.mulDiv(
                <span class="hljs-keyword">uint256</span>(liquidity) <span class="hljs-operator">&lt;</span><span class="hljs-operator">&lt;</span> FixedPoint96.RESOLUTION,
                sqrtRatioBX96 <span class="hljs-operator">-</span> sqrtRatioAX96,
                sqrtRatioBX96
            ) <span class="hljs-operator">/</span> sqrtRatioAX96;
    }

    <span class="hljs-comment">/// @notice Computes the amount of token1 for a given amount of liquidity and a price range</span>
    <span class="hljs-comment">/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary</span>
    <span class="hljs-comment">/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary</span>
    <span class="hljs-comment">/// @param liquidity The liquidity being valued</span>
    <span class="hljs-comment">/// @return amount1 The amount of token1</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAmount1ForLiquidity</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint160</span> sqrtRatioAX96,
        <span class="hljs-keyword">uint160</span> sqrtRatioBX96,
        <span class="hljs-keyword">uint128</span> liquidity
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount1</span>) </span>{
        <span class="hljs-keyword">if</span> (sqrtRatioAX96 <span class="hljs-operator">&gt;</span> sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) <span class="hljs-operator">=</span> (sqrtRatioBX96, sqrtRatioAX96);

        <span class="hljs-keyword">return</span> FullMath.mulDiv(liquidity, sqrtRatioBX96 <span class="hljs-operator">-</span> sqrtRatioAX96, FixedPoint96.Q96);
    }</code></pre><p><strong><em>Note:</em></strong> Based on the formula used to derive the amount of token 0 and token 1 backing a particular amount of pool liquidity. We can equally get the Liquidity backing the given amounts simply by making Liquidity (L) the subject of formula.<br><br><strong><em>Another case is that</em></strong>, we can get the liquidity to add if we have a given amounts desired to add.<br><br>If the following parameters is true:<br><br>Pa - price lower specified by the user based on the given tick lower.<br>Pb - price upper specified by the user based on the given tick upper.<br>Amount0 - amount desired to add for token 0.<br>Amount1 - amount desired to add for token 1.</p><pre data-type="codeBlock" text="    function getLiquidityForAmount0(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96);
        return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96));
    }

    /// @notice Computes the amount of liquidity received for a given amount of token1 and price range
    /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount1 The amount1 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount1(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96));
    }"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLiquidityForAmount0</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint160</span> sqrtRatioAX96,
        <span class="hljs-keyword">uint160</span> sqrtRatioBX96,
        <span class="hljs-keyword">uint256</span> amount0
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint128</span> liquidity</span>) </span>{
        <span class="hljs-keyword">if</span> (sqrtRatioAX96 <span class="hljs-operator">&gt;</span> sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) <span class="hljs-operator">=</span> (sqrtRatioBX96, sqrtRatioAX96);
        <span class="hljs-keyword">uint256</span> intermediate <span class="hljs-operator">=</span> FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96);
        <span class="hljs-keyword">return</span> toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 <span class="hljs-operator">-</span> sqrtRatioAX96));
    }

    <span class="hljs-comment">/// @notice Computes the amount of liquidity received for a given amount of token1 and price range</span>
    <span class="hljs-comment">/// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).</span>
    <span class="hljs-comment">/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary</span>
    <span class="hljs-comment">/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary</span>
    <span class="hljs-comment">/// @param amount1 The amount1 being sent in</span>
    <span class="hljs-comment">/// @return liquidity The amount of returned liquidity</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLiquidityForAmount1</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint160</span> sqrtRatioAX96,
        <span class="hljs-keyword">uint160</span> sqrtRatioBX96,
        <span class="hljs-keyword">uint256</span> amount1
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint128</span> liquidity</span>) </span>{
        <span class="hljs-keyword">if</span> (sqrtRatioAX96 <span class="hljs-operator">&gt;</span> sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) <span class="hljs-operator">=</span> (sqrtRatioBX96, sqrtRatioAX96);
        <span class="hljs-keyword">return</span> toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 <span class="hljs-operator">-</span> sqrtRatioAX96));
    }</code></pre><p>However, while using those formulas to calculate the Liquidity (L) by making it the subject of formula, we can't tell the liquidity to add based on where the current price is in the curve. the code above only gets the liquidity based on which token is dominant in the pool where the other is depleted.<br><br>But the code below does justice to deriving Liquidity based on the amounts desired regardless of where price sits in the curve.</p><pre data-type="codeBlock" text="    function getLiquidityForAmounts(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
            uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);
			
			// If you follow along with our explanation on Uniswap V2, you would be aware that liquidity is the minimum when compared.
            liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
        } else {
            liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
        }
    }"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLiquidityForAmounts</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint160</span> sqrtRatioX96,
        <span class="hljs-keyword">uint160</span> sqrtRatioAX96,
        <span class="hljs-keyword">uint160</span> sqrtRatioBX96,
        <span class="hljs-keyword">uint256</span> amount0,
        <span class="hljs-keyword">uint256</span> amount1
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint128</span> liquidity</span>) </span>{
        <span class="hljs-keyword">if</span> (sqrtRatioAX96 <span class="hljs-operator">&gt;</span> sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) <span class="hljs-operator">=</span> (sqrtRatioBX96, sqrtRatioAX96);

        <span class="hljs-keyword">if</span> (sqrtRatioX96 <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> sqrtRatioAX96) {
            liquidity <span class="hljs-operator">=</span> getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (sqrtRatioX96 <span class="hljs-operator">&lt;</span> sqrtRatioBX96) {
            <span class="hljs-keyword">uint128</span> liquidity0 <span class="hljs-operator">=</span> getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
            <span class="hljs-keyword">uint128</span> liquidity1 <span class="hljs-operator">=</span> getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);
			
			<span class="hljs-comment">// If you follow along with our explanation on Uniswap V2, you would be aware that liquidity is the minimum when compared.</span>
            liquidity <span class="hljs-operator">=</span> liquidity0 <span class="hljs-operator">&lt;</span> liquidity1 ? liquidity0 : liquidity1;
        } <span class="hljs-keyword">else</span> {
            liquidity <span class="hljs-operator">=</span> getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
        }
    }</code></pre><p><strong><em>For case 2:</em></strong><br>If the following parameters are true;<br><br>L - Liquidity Delta (Liquidity to add or remove)<br>p - current price (it could be the lower or upper price if it's an active range.<br>pb - upper price specified by the user based on the tick upper.<br>pa - lower price specified by the user based on the tick lower.</p><pre data-type="codeBlock" text="    function getAmount1Delta(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity,
        bool roundUp
    ) internal pure returns (uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return
            roundUp
                ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96)
                : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
    }

    /// @notice Helper that gets signed token0 delta
    /// @param sqrtRatioAX96 A sqrt price
    /// @param sqrtRatioBX96 Another sqrt price
    /// @param liquidity The change in liquidity for which to compute the amount0 delta
    /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices
    function getAmount0Delta(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        int128 liquidity
    ) internal pure returns (int256 amount0) {
        return
            liquidity < 0
                ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
                : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
    }"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAmount1Delta</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint160</span> sqrtRatioAX96,
        <span class="hljs-keyword">uint160</span> sqrtRatioBX96,
        <span class="hljs-keyword">uint128</span> liquidity,
        <span class="hljs-keyword">bool</span> roundUp
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount1</span>) </span>{
        <span class="hljs-keyword">if</span> (sqrtRatioAX96 <span class="hljs-operator">&gt;</span> sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) <span class="hljs-operator">=</span> (sqrtRatioBX96, sqrtRatioAX96);

        <span class="hljs-keyword">return</span>
            roundUp
                ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 <span class="hljs-operator">-</span> sqrtRatioAX96, FixedPoint96.Q96)
                : FullMath.mulDiv(liquidity, sqrtRatioBX96 <span class="hljs-operator">-</span> sqrtRatioAX96, FixedPoint96.Q96);
    }

    <span class="hljs-comment">/// @notice Helper that gets signed token0 delta</span>
    <span class="hljs-comment">/// @param sqrtRatioAX96 A sqrt price</span>
    <span class="hljs-comment">/// @param sqrtRatioBX96 Another sqrt price</span>
    <span class="hljs-comment">/// @param liquidity The change in liquidity for which to compute the amount0 delta</span>
    <span class="hljs-comment">/// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAmount0Delta</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint160</span> sqrtRatioAX96,
        <span class="hljs-keyword">uint160</span> sqrtRatioBX96,
        <span class="hljs-keyword">int128</span> liquidity
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">int256</span> amount0</span>) </span>{
        <span class="hljs-keyword">return</span>
            liquidity <span class="hljs-operator">&lt;</span> <span class="hljs-number">0</span>
                ? <span class="hljs-operator">-</span>getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, <span class="hljs-keyword">uint128</span>(<span class="hljs-operator">-</span>liquidity), <span class="hljs-literal">false</span>).toInt256()
                : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, <span class="hljs-keyword">uint128</span>(liquidity), <span class="hljs-literal">true</span>).toInt256();
    }</code></pre><p><strong>Note</strong>: If you want to calculate an estimate amounts, the second case does the calculation as it provides the flexibility to choose the rounding direction based on the amounts. There will be a difference in Wei if you use the first case.<br><br>The second case is in a function <strong>_modifyPosition</strong> and even though it's internal, you can copy it for use in your code if you need to make an estimate.</p><pre data-type="codeBlock" text="library PositionModifier {

	
	function modifyPosition(ModifyPositionParams memory params)
        public
        noDelegateCall
        returns (
            Position.Info storage position,
            int256 amount0,
            int256 amount1
        )
    {
        checkTicks(params.tickLower, params.tickUpper);

        Slot0 memory _slot0 = pool.slot0(); // SLOAD for gas optimization
        if (params.liquidityDelta != 0) {
            if (_slot0.tick < params.tickLower) {
                // current tick is below the passed range; liquidity can only become in range by crossing from left to
                // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it
                amount0 = SqrtPriceMath.getAmount0Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            } else if (_slot0.tick < params.tickUpper) {
                amount0 = SqrtPriceMath.getAmount0Delta(
                    _slot0.sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
                amount1 = SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    _slot0.sqrtPriceX96,
                    params.liquidityDelta
                );

                liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta);
            } else {
                // current tick is above the passed range; liquidity can only become in range by crossing from right to
                // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it
                amount1 = SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            }
        }
    }


}"><code><span class="hljs-class"><span class="hljs-keyword">library</span> <span class="hljs-title">PositionModifier</span> </span>{

	
	<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">modifyPosition</span>(<span class="hljs-params">ModifyPositionParams <span class="hljs-keyword">memory</span> params</span>)
        <span class="hljs-title"><span class="hljs-keyword">public</span></span>
        <span class="hljs-title">noDelegateCall</span>
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">
            Position.Info <span class="hljs-keyword">storage</span> position,
            <span class="hljs-keyword">int256</span> amount0,
            <span class="hljs-keyword">int256</span> amount1
        </span>)
    </span>{
        checkTicks(params.tickLower, params.tickUpper);

        Slot0 <span class="hljs-keyword">memory</span> _slot0 <span class="hljs-operator">=</span> pool.slot0(); <span class="hljs-comment">// SLOAD for gas optimization</span>
        <span class="hljs-keyword">if</span> (params.liquidityDelta <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">if</span> (_slot0.tick <span class="hljs-operator">&lt;</span> params.tickLower) {
                <span class="hljs-comment">// current tick is below the passed range; liquidity can only become in range by crossing from left to</span>
                <span class="hljs-comment">// right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it</span>
                amount0 <span class="hljs-operator">=</span> SqrtPriceMath.getAmount0Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (_slot0.tick <span class="hljs-operator">&lt;</span> params.tickUpper) {
                amount0 <span class="hljs-operator">=</span> SqrtPriceMath.getAmount0Delta(
                    _slot0.sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
                amount1 <span class="hljs-operator">=</span> SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    _slot0.sqrtPriceX96,
                    params.liquidityDelta
                );

                liquidity <span class="hljs-operator">=</span> LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta);
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// current tick is above the passed range; liquidity can only become in range by crossing from right to</span>
                <span class="hljs-comment">// left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it</span>
                amount1 <span class="hljs-operator">=</span> SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            }
        }
    }


}</code></pre><p>Towards the end of this article, you will understand how everything fits in by creating a connection.</p><h2 id="h-real-reserves-vs-virtual-reserves" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Real Reserves Vs Virtual Reserves.</strong></h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/278d1418aece25d672ccf7f87758ad75.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAVCAIAAACor3u9AAAACXBIWXMAAAsTAAALEwEAmpwYAAADBUlEQVR4nLWU3U/TUBjGz+2udkH8A7jAEBMNJuoNCQhR4hcYFaYZ0ZkJJBIEQXF+gITJYJGMWWVegFAHJhVkuHihaQiDNGAhIKnEKikCy4ZUUDe6yRl2sBp2QgmMECLzuWh7Tnre33nf5z0HSOsliiJ6oo+dC6AXz/MqlUqhUMTGxqampgIAMAyTedEBJCcnx8TEJCQkqNXqjIwMkiSjCUBCu6YoiqbpqESXZACKZTQaJUkiCMJut0cZ4HLOTE1MWyz1KAOCIKIM+PzpK9Uz9KypAcIAhLCqqio8HfovgACEZWXlOw+9DjDzbY5822ex1EMIJUkymeq6X+M+z6y0Y611UWd7V7WhBtV9t3Lfw7s1krQUfYAkhaY459mk85XFBggD2/R5i5O/BhikR68X3pSN1abnsSPsFosjLxUY1ob/1wAu5/e0w6f7u98vh1YYeHPbrtiDrslpvw96PZ4Fn0cOJ4oihFAICw1ZluU4TsZsDvDMzQIAzPqVo7AUXOoeHW1q6dCm51Xfrq1/UNf3joBwUd44wzAgLKVSGR8fr9Vq0RAAoFAoAADyTQNQTSbYIY5x5Gpzex3DkiQN9494PF4EVh/RpO49hlufYxhGEETHqoqKikiSJAiitbVVEASapo1Go06noygKwzC3270uA8+vny1NT1VnTuLNbVbclpWSbSPe+H/7Fhb8vWTvxPjEcig4P++lKIpclcPhsFgsgiBACDmOEwTB7XbzPC8IwiYmi6LYT9NpKYnmmvulhVdPHDp+7tRFtfqKKlNzWZNfeK1UX2nQVxrMdY9NtY8aGxutLVYcxzEMs9lscXFxcn0AACaTCTmx0QMI4QviJTvYNckOhCdCYXt9gtc/Puai+xi6j6F6PoyxU8FgEPmEFvI8P/dj1ulyMgxD0/TmGSAtimJgpQf+RLZmMBw0O6sk8UBWjuYO3vBq/55TR5M06szikoLqgpyKj8xYZCtvBGwhtN/hgdEbBYZynXnsy2RnO5mfV3Hpwq3WZrv+3hMEiDwx2wX8s/4CQATkr5KvSjkAAAAASUVORK5CYII=" nextheight="428" nextwidth="666" class="image-node embed"><figcaption htmlattributes="[object Object]" class="">This graph shows the relationship between virtual and real reserves in a CLAMM.</figcaption></figure><p>In Uniswap V2, the constant product formula ensures that liquidity is evenly distributed across all price ranges making them fragmented at price points where trading is least probable which makes capital inefficient. As a result, you can’t zoom in to specific price ranges, this makes the concept of concentrated liquidity a significant solution as liquidity is only available in price ranges that liquidity providers choose. <br><br>As I earlier said, the Uniswap V3 ticks act like mini-V2s but with concentrated liquidity. Liquidity is only available in ranges where liquidity providers specify. It makes it possible to facilitate trade with less liquidity but with much more efficiency than V2.<br><br>For instance, if you want to swap in a range in V2, and liquidity of 10 ETH and 20,000 USDC is provided (Virtual liquidity - liquidity in V2), that would eventually be spread across all price points in the price curve from 0 to ∞. In the Uniswap V3 tick-based system,the same amount won’t be needed to facilitate trade within that range because it’s concentrated, it may require 1 ETH and 2000 USDC (Real liquidity - liquidity in V3 concentrated range) to make the same trade happen. The former explains the concept of <strong>Virtual reserves</strong>. Uniswap V3 has a formula to calculate the actual amount to be provided or pre-computed just to get the real amount to concentrate in a V3 specific price range. It ensures that the amount of tradable assets of token X and token Y is equal to the real liquidity that has been deposited into only that range. The real liquidity deposited is called the <strong>real reserves</strong>. If liquidity reaches either bounds of the tokens, they are no more concentrated and liquidity is only in one token. So, virtual reserves make it possible to avoid impermanent loss, or avoid holding just one token and to predict which range to add liquidity into.<br><br>Note: Virtual reserves allows the V3 AMM to behave like Uniswap V2 but they don’t represent actual deposited tokens. Real reserves are the <strong>actual tokens</strong> supplied by LPs.</p><h2 id="h-price-calculations-in-uniswap-v3" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Price calculations in Uniswap V3?</strong></h2><p>In Uniswap V2, Price is the value of y / x. But in Uniswap V3, Price is stored as √p (sqrtPriceX96). This is because of precision. In Solidity, 5 / 2 = 2 because the EVM does not store decimal value. But it can be rewritten as 25 / 10 = 2.5. This way, it doesn’t lose its precision and it’s contained with 256 bits. This is how we derive values for less than 1 ether. <br><br>For Instance, <br>1 Ether = 10^18. <br>0.5 Ether = 5 * 10^17<br><br>Now, we have scaled the fractional part of “.5” by 10 ^ 17 to avoid losing precision. Precision is always lost when Solidity doesn’t allow representing fractional numbers.<br><br>However, this uses a mathematical notation called “Standard Notation”. This notation scales fractions using decimal (10).<br><br>Uniswap uses “Q Notation”, this type scales fractions using binary. It now depends on how many bits you want to scale by.<br><br>For instance, assuming we want to scale 1.5 in Q notation format by 8 bits, that is Q8.<br><br>1 * 2^8 = 256</p><p>0.5 * 2^8 = 128<br><br>Total is 384 in Q notation. The fraction has been converted to an integer without losing its precision. We can always get our value back by dividing by 2^8. Why this is easy for Uniswap to use is because the multiplication and division operations can be performed using bit shifting operations.<br><br>E.g. X * 2^n = x &lt;&lt; n<br><br>Q number notation is usually written as Qm.n, where m = the integer part and n is the fractional part, that we are trying to scale.<br><br>Earlier, we used Q8. If we write it using the Qm.n format, we can write Q8.8, the integer value must contain 8 bits and the fraction can be scaled using binary scaling by bits.<br><br>QX96 used by Uniswap means the fraction is scaled by 96 bits. So we can express 1.5X96 as 1.5 * 2 ^96 or 1.5 &lt;&lt; 96 but to store Q8.8 in a solidity integer, we need 8 + 8 = 16 bits because it’s both the integer and fractional bits that we want to store. This perfectly fits into uint16 or max uint256, which is <strong>uint16 variableX16</strong>.<br><br>To get the sqrtPriceX96 used in Uniswap, we will use the spot price of an AMM, Y / X. <br><br>If token X is 500 and token Y is 20.<br>Price is 500 / 20 = 25.<br>So, the price is 25.<br>SqrtPrice: √25 = 5<br>SqrtPriceX96: 5 * 2 ^96.<br><br>Since we scale the fractional part by 96, Uniswap stores the integer in a <code>uint160 SqrtPriceX96 </code>variable to make up the max number of bits of an unsigned integer in Solidity. This adds to optimize gas by storing all the state variables in the first slot - slot0.</p><h2 id="h-adding-liquidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Adding Liquidity.</strong> </h2><p>When providing liquidity, like in Uniswap V2, we specify the token amounts to send to the pool as liquidity, these are the desired amounts. Then it maps the amounts of both tokens to a certain amount of liquidity to ensure that we can re-compute the delta for those amounts. <br><br><strong><em>Note</em></strong>: <strong><em>Liquidity, in the context of AMMs,</em> measures the combined reserves of the token pair x and y.</strong><br><br>For instance, amount0Desired → amount1Desired → liquidity.<br><br>The liquidity is also called Liquidity Delta. Delta is a financial term used to describe the amount needed to move an asset from point A to point B.<br><br>What Uniswap does is that it first calculates the liquidity delta for the amounts to be provided. And then re-calculate the delta of both amount 0 and amount1. Now, this calculation might be&nbsp; seen as circling about but it’s needed because using the amounts desired, you can’t know where price sits in the curve if the amountsDelta is not computed from the liquidity.<br><br>Let’s examine the following smart contract that is put together to ensure we provide liquidity the Uniswap way.<br><br><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol">Pool.sol</a><br><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/LiquidityManagement.sol">LiquidityManagement.sol</a><br><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v3-periphery/blob/main/contracts/NonfungiblePositionManager.sol">NonfungiblePositionManager.sol</a><br><br>Before we dive deep, keep in mind that a Uniswap V3 Position is an object:<br><br>Position – &gt; {<br> tickLower;<br> tickUpper;<br> liquidityDelta;<br> }<br><br>To create a position by supplying liquidity, it’s important to know the pool state, especially the current tick of the pool.<br><br>If the current tick of the pool is less than the tick Lower, then the pool is out of range.<br>If the current tick of the pool is greater than the tick Upper, then the pool is out of range.<br>If the current tick of the pool is less than the tick Upper, then the pool is in the active range.<br><br>When a pool is in active range, we can supply amount0Delta and amount1Delta<br>When a pool tick is less than the tickLower, then we can only supply the amount0Delta because that’s the amount depleted in the pool.<br>When a pool tick is greater than the tickUpper, then we can only supply the amount1Delta because that’s the amount depleted in the pool.<br><br>The amountsDelta are the amount required to shift the pool price from point A to point B. Uniswap uses the same formula mentioned earlier to calculate the estimated amounts required to shift the range from price lower to upper.<br><br><strong>Estimated amountdelta of X and Y.</strong><br><br>x = L / √pL - L / √pU.    --&gt;    <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/libraries/SqrtPriceMath.sol#L201">amount0Delta</a><br>y = L√pU - L √pL          --&gt;.   <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/libraries/SqrtPriceMath.sol#L217">amount1Delta</a><br><br>Although, they are used in different contexts that require different parameters. We use the price lower and price upper specified by the user to calculate the estimates here whenever we want to add liquidity.<br><br><strong>Let’s check the code.</strong></p><p>The <strong>mint</strong> function is the core function that mints liquidity to an LP after confirming the Balance updates. It calls the `_modifyPosition` function which does modify liquidity(either adding or removing) which is + or - liquidityDelta which is a int256 type. Note that the delta value returned (amount0Int and amount1Int) is pre-computed and hasn't been sent into the pool until the <strong>uniswapV3MintCallback</strong> is called.</p><pre data-type="codeBlock" text="    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount,
        bytes calldata data
    ) external override lock returns (uint256 amount0, uint256 amount1) {
        require(amount > 0);
        (, int256 amount0Int, int256 amount1Int) =
            _modifyPosition(
                ModifyPositionParams({
                    owner: recipient,
                    tickLower: tickLower,
                    tickUpper: tickUpper,
                    liquidityDelta: int256(amount).toInt128()
                })
            );

        amount0 = uint256(amount0Int);
        amount1 = uint256(amount1Int);

        uint256 balance0Before;
        uint256 balance1Before;
        if (amount0 > 0) balance0Before = balance0();
        if (amount1 > 0) balance1Before = balance1();
        IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data);
        if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0');
        if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1');

        emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1);
    }"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> recipient,
        <span class="hljs-keyword">int24</span> tickLower,
        <span class="hljs-keyword">int24</span> tickUpper,
        <span class="hljs-keyword">uint128</span> amount,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">lock</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount0, <span class="hljs-keyword">uint256</span> amount1</span>) </span>{
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>);
        (, <span class="hljs-keyword">int256</span> amount0Int, <span class="hljs-keyword">int256</span> amount1Int) <span class="hljs-operator">=</span>
            _modifyPosition(
                ModifyPositionParams({
                    owner: recipient,
                    tickLower: tickLower,
                    tickUpper: tickUpper,
                    liquidityDelta: <span class="hljs-keyword">int256</span>(amount).toInt128()
                })
            );

        amount0 <span class="hljs-operator">=</span> <span class="hljs-keyword">uint256</span>(amount0Int);
        amount1 <span class="hljs-operator">=</span> <span class="hljs-keyword">uint256</span>(amount1Int);

        <span class="hljs-keyword">uint256</span> balance0Before;
        <span class="hljs-keyword">uint256</span> balance1Before;
        <span class="hljs-keyword">if</span> (amount0 <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) balance0Before <span class="hljs-operator">=</span> balance0();
        <span class="hljs-keyword">if</span> (amount1 <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) balance1Before <span class="hljs-operator">=</span> balance1();
        IUniswapV3MintCallback(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>).uniswapV3MintCallback(amount0, amount1, data);
        <span class="hljs-keyword">if</span> (amount0 <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) <span class="hljs-built_in">require</span>(balance0Before.add(amount0) <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> balance0(), <span class="hljs-string">'M0'</span>);
        <span class="hljs-keyword">if</span> (amount1 <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) <span class="hljs-built_in">require</span>(balance1Before.add(amount1) <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> balance1(), <span class="hljs-string">'M1'</span>);

        <span class="hljs-keyword">emit</span> Mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, recipient, tickLower, tickUpper, amount, amount0, amount1);
    }</code></pre><p>The <strong>_modifyPosition</strong> function then confirms if the liquidityDelta is non-zero, and as we said earlier a position comprises the tick lower and upper, we must confirm the pool state by checking in the <code>slot0</code> variable to check where the current pool tick is placed. If it's lesser than the specified tick lower (out of range) or greater than the tick Upper(out of range) or less than the tick Upper (in active range). That is the conditional branching you can see in the code to determine which token and amountDelta will be transferred to the pool.<br></p><p>Note: The amountDelta returned here (amount0Int and amount1Int) can only be positive when adding liquidity.</p><p>Amount Owed in this context is the amount0Delta pre-calculated to be sent to the pool and it must be greater than 0 when adding liquidity. Basically, it means you owe the pool some amounts of liquidity to be sent.<br><br>When removing liquidity, I call the delta amounts, amount owned, that is the amount the pool owe you when removing liquidity.<br><br>For context, see the Uniswap's comment in the code below.</p><pre data-type="codeBlock" text="	/// @dev Effect some changes to a position
	/// @param params the position details and the change to the position's liquidity to effect
	/// @return position a storage pointer referencing the position with the given owner and tick range
	/// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient
	/// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient
    

	function _modifyPosition(ModifyPositionParams memory params)
        private
        noDelegateCall
        returns (
            Position.Info storage position,
            int256 amount0,
            int256 amount1
        )
    {
        checkTicks(params.tickLower, params.tickUpper);

        Slot0 memory _slot0 = slot0; // SLOAD for gas optimization

        position = _updatePosition(
            params.owner,
            params.tickLower,
            params.tickUpper,
            params.liquidityDelta,
            _slot0.tick
        );

        if (params.liquidityDelta != 0) {
            if (_slot0.tick < params.tickLower) {
                // current tick is below the passed range; liquidity can only become in range by crossing from left to
                // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it
                amount0 = SqrtPriceMath.getAmount0Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            } else if (_slot0.tick < params.tickUpper) {
                // current tick is inside the passed range
                uint128 liquidityBefore = liquidity; // SLOAD for gas optimization

                // write an oracle entry
                (slot0.observationIndex, slot0.observationCardinality) = observations.write(
                    _slot0.observationIndex,
                    _blockTimestamp(),
                    _slot0.tick,
                    liquidityBefore,
                    _slot0.observationCardinality,
                    _slot0.observationCardinalityNext
                );

                amount0 = SqrtPriceMath.getAmount0Delta(
                    _slot0.sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
                amount1 = SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    _slot0.sqrtPriceX96,
                    params.liquidityDelta
                );

                liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta);
            } else {
                // current tick is above the passed range; liquidity can only become in range by crossing from right to
                // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it
                amount1 = SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            }
        }
    }"><code>	<span class="hljs-comment">/// @dev Effect some changes to a position</span>
	<span class="hljs-comment">/// @param params the position details and the change to the position's liquidity to effect</span>
	<span class="hljs-comment">/// @return position a storage pointer referencing the position with the given owner and tick range</span>
	<span class="hljs-comment">/// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient</span>
	<span class="hljs-comment">/// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient</span>
    

	<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_modifyPosition</span>(<span class="hljs-params">ModifyPositionParams <span class="hljs-keyword">memory</span> params</span>)
        <span class="hljs-title"><span class="hljs-keyword">private</span></span>
        <span class="hljs-title">noDelegateCall</span>
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">
            Position.Info <span class="hljs-keyword">storage</span> position,
            <span class="hljs-keyword">int256</span> amount0,
            <span class="hljs-keyword">int256</span> amount1
        </span>)
    </span>{
        checkTicks(params.tickLower, params.tickUpper);

        Slot0 <span class="hljs-keyword">memory</span> _slot0 <span class="hljs-operator">=</span> slot0; <span class="hljs-comment">// SLOAD for gas optimization</span>

        position <span class="hljs-operator">=</span> _updatePosition(
            params.owner,
            params.tickLower,
            params.tickUpper,
            params.liquidityDelta,
            _slot0.tick
        );

        <span class="hljs-keyword">if</span> (params.liquidityDelta <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">if</span> (_slot0.tick <span class="hljs-operator">&lt;</span> params.tickLower) {
                <span class="hljs-comment">// current tick is below the passed range; liquidity can only become in range by crossing from left to</span>
                <span class="hljs-comment">// right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it</span>
                amount0 <span class="hljs-operator">=</span> SqrtPriceMath.getAmount0Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (_slot0.tick <span class="hljs-operator">&lt;</span> params.tickUpper) {
                <span class="hljs-comment">// current tick is inside the passed range</span>
                <span class="hljs-keyword">uint128</span> liquidityBefore <span class="hljs-operator">=</span> liquidity; <span class="hljs-comment">// SLOAD for gas optimization</span>

                <span class="hljs-comment">// write an oracle entry</span>
                (slot0.observationIndex, slot0.observationCardinality) <span class="hljs-operator">=</span> observations.write(
                    _slot0.observationIndex,
                    _blockTimestamp(),
                    _slot0.tick,
                    liquidityBefore,
                    _slot0.observationCardinality,
                    _slot0.observationCardinalityNext
                );

                amount0 <span class="hljs-operator">=</span> SqrtPriceMath.getAmount0Delta(
                    _slot0.sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
                amount1 <span class="hljs-operator">=</span> SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    _slot0.sqrtPriceX96,
                    params.liquidityDelta
                );

                liquidity <span class="hljs-operator">=</span> LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta);
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// current tick is above the passed range; liquidity can only become in range by crossing from right to</span>
                <span class="hljs-comment">// left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it</span>
                amount1 <span class="hljs-operator">=</span> SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            }
        }
    }</code></pre><p>The <strong>LiquidityManagement</strong> contract in Uniswap V3 manages liquidity. <br> <br><strong>The flow:</strong><br>- The LP sends in the amount0Desired and amount1Desired. <br>- Earlier we said, these amounts map to a liquidityDelta which is the amount of liquidity required to change the pool's original liquidity. It is delta because we could either add or remove liquidity. <br>- The liquidityDelta is sent to the pool to pre-calculate the amountsDelta <strong>(amount0, amount1) = pool.mint</strong><br>- The pool calls back the `uniswapV3MintCallback` to pay the pre-calculated amounts `pay(decoded.poolKey.token0, decoded.payer, msg.sender, amount0Owed);`<br>- The <strong>CallbackValidation.verifyCallback(factory, decoded.poolKey)</strong> verifies the caller is the pool.<br>- In this case, <strong>amount0Owed &gt; 0</strong> must be true because LiquidityDelta is positive when we want to add liquidity.</p><pre data-type="codeBlock" text="abstract contract LiquidityManagement is IUniswapV3MintCallback, PeripheryImmutableState, PeripheryPayments {
    struct MintCallbackData {
        PoolAddress.PoolKey poolKey;
        address payer;
    }

    /// @inheritdoc IUniswapV3MintCallback
    function uniswapV3MintCallback(
        uint256 amount0Owed,
        uint256 amount1Owed,
        bytes calldata data
    ) external override {
        MintCallbackData memory decoded = abi.decode(data, (MintCallbackData));
        CallbackValidation.verifyCallback(factory, decoded.poolKey);

        if (amount0Owed > 0) pay(decoded.poolKey.token0, decoded.payer, msg.sender, amount0Owed);
        if (amount1Owed > 0) pay(decoded.poolKey.token1, decoded.payer, msg.sender, amount1Owed);
    }

    struct AddLiquidityParams {
        address token0;
        address token1;
        uint24 fee;
        address recipient;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
    }

    /// @notice Add liquidity to an initialized pool
    function addLiquidity(AddLiquidityParams memory params)
        internal
        returns (
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1,
            IUniswapV3Pool pool
        )
    {
        PoolAddress.PoolKey memory poolKey =
            PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee});

        pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));

        // compute the liquidity amount
        {
            (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
            uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(params.tickLower);
            uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(params.tickUpper);

            liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                sqrtRatioAX96,
                sqrtRatioBX96,
                params.amount0Desired,
                params.amount1Desired
            );
        }

        (amount0, amount1) = pool.mint(
            params.recipient,
            params.tickLower,
            params.tickUpper,
            liquidity,
            abi.encode(MintCallbackData({poolKey: poolKey, payer: msg.sender}))
        );

        require(amount0 >= params.amount0Min &amp;&amp; amount1 >= params.amount1Min, 'Price slippage check');
    }
}"><code><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">LiquidityManagement</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IUniswapV3MintCallback</span>, <span class="hljs-title">PeripheryImmutableState</span>, <span class="hljs-title">PeripheryPayments</span> </span>{
    <span class="hljs-keyword">struct</span> <span class="hljs-title">MintCallbackData</span> {
        PoolAddress.PoolKey poolKey;
        <span class="hljs-keyword">address</span> payer;
    }

    <span class="hljs-comment">/// @inheritdoc IUniswapV3MintCallback</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uniswapV3MintCallback</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> amount0Owed,
        <span class="hljs-keyword">uint256</span> amount1Owed,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> </span>{
        MintCallbackData <span class="hljs-keyword">memory</span> decoded <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (MintCallbackData));
        CallbackValidation.verifyCallback(factory, decoded.poolKey);

        <span class="hljs-keyword">if</span> (amount0Owed <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) pay(decoded.poolKey.token0, decoded.payer, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount0Owed);
        <span class="hljs-keyword">if</span> (amount1Owed <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) pay(decoded.poolKey.token1, decoded.payer, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount1Owed);
    }

    <span class="hljs-keyword">struct</span> <span class="hljs-title">AddLiquidityParams</span> {
        <span class="hljs-keyword">address</span> token0;
        <span class="hljs-keyword">address</span> token1;
        <span class="hljs-keyword">uint24</span> fee;
        <span class="hljs-keyword">address</span> recipient;
        <span class="hljs-keyword">int24</span> tickLower;
        <span class="hljs-keyword">int24</span> tickUpper;
        <span class="hljs-keyword">uint256</span> amount0Desired;
        <span class="hljs-keyword">uint256</span> amount1Desired;
        <span class="hljs-keyword">uint256</span> amount0Min;
        <span class="hljs-keyword">uint256</span> amount1Min;
    }

    <span class="hljs-comment">/// @notice Add liquidity to an initialized pool</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addLiquidity</span>(<span class="hljs-params">AddLiquidityParams <span class="hljs-keyword">memory</span> params</span>)
        <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">
            <span class="hljs-keyword">uint128</span> liquidity,
            <span class="hljs-keyword">uint256</span> amount0,
            <span class="hljs-keyword">uint256</span> amount1,
            IUniswapV3Pool pool
        </span>)
    </span>{
        PoolAddress.PoolKey <span class="hljs-keyword">memory</span> poolKey <span class="hljs-operator">=</span>
            PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee});

        pool <span class="hljs-operator">=</span> IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));

        <span class="hljs-comment">// compute the liquidity amount</span>
        {
            (<span class="hljs-keyword">uint160</span> sqrtPriceX96, , , , , , ) <span class="hljs-operator">=</span> pool.slot0();
            <span class="hljs-keyword">uint160</span> sqrtRatioAX96 <span class="hljs-operator">=</span> TickMath.getSqrtRatioAtTick(params.tickLower);
            <span class="hljs-keyword">uint160</span> sqrtRatioBX96 <span class="hljs-operator">=</span> TickMath.getSqrtRatioAtTick(params.tickUpper);

            liquidity <span class="hljs-operator">=</span> LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                sqrtRatioAX96,
                sqrtRatioBX96,
                params.amount0Desired,
                params.amount1Desired
            );
        }

        (amount0, amount1) <span class="hljs-operator">=</span> pool.mint(
            params.recipient,
            params.tickLower,
            params.tickUpper,
            liquidity,
            <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(MintCallbackData({poolKey: poolKey, payer: <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>}))
        );

        <span class="hljs-built_in">require</span>(amount0 <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> params.amount0Min <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> amount1 <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> params.amount1Min, <span class="hljs-string">'Price slippage check'</span>);
    }
}</code></pre><p>The actual contract to call whenever an LP wants to add liquidity is the <strong>NonfungibleManager.sol</strong>. It has an external mint function that calls into the <strong>addLiquidity</strong> function in the <strong>LiquidityManagement.sol</strong> contract.</p><pre data-type="codeBlock" text="    /// @inheritdoc INonfungiblePositionManager
    function mint(MintParams calldata params)
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (
            uint256 tokenId,
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        )
    {
        IUniswapV3Pool pool;
        (liquidity, amount0, amount1, pool) = addLiquidity(
            AddLiquidityParams({
                token0: params.token0,
                token1: params.token1,
                fee: params.fee,
                recipient: address(this),
                tickLower: params.tickLower,
                tickUpper: params.tickUpper,
                amount0Desired: params.amount0Desired,
                amount1Desired: params.amount1Desired,
                amount0Min: params.amount0Min,
                amount1Min: params.amount1Min
            })
        );

        _mint(params.recipient, (tokenId = _nextId++));

        bytes32 positionKey = PositionKey.compute(address(this), params.tickLower, params.tickUpper);
        (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);

        // idempotent set
        uint80 poolId =
            cachePoolKey(
                address(pool),
                PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee})
            );

        _positions[tokenId] = Position({
            nonce: 0,
            operator: address(0),
            poolId: poolId,
            tickLower: params.tickLower,
            tickUpper: params.tickUpper,
            liquidity: liquidity,
            feeGrowthInside0LastX128: feeGrowthInside0LastX128,
            feeGrowthInside1LastX128: feeGrowthInside1LastX128,
            tokensOwed0: 0,
            tokensOwed1: 0
        });

        emit IncreaseLiquidity(tokenId, liquidity, amount0, amount1);
    }"><code>    <span class="hljs-comment">/// @inheritdoc INonfungiblePositionManager</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params">MintParams <span class="hljs-keyword">calldata</span> params</span>)
        <span class="hljs-title"><span class="hljs-keyword">external</span></span>
        <span class="hljs-title"><span class="hljs-keyword">payable</span></span>
        <span class="hljs-title"><span class="hljs-keyword">override</span></span>
        <span class="hljs-title">checkDeadline</span>(<span class="hljs-params">params.deadline</span>)
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">
            <span class="hljs-keyword">uint256</span> tokenId,
            <span class="hljs-keyword">uint128</span> liquidity,
            <span class="hljs-keyword">uint256</span> amount0,
            <span class="hljs-keyword">uint256</span> amount1
        </span>)
    </span>{
        IUniswapV3Pool pool;
        (liquidity, amount0, amount1, pool) <span class="hljs-operator">=</span> addLiquidity(
            AddLiquidityParams({
                token0: params.token0,
                token1: params.token1,
                fee: params.fee,
                recipient: <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>),
                tickLower: params.tickLower,
                tickUpper: params.tickUpper,
                amount0Desired: params.amount0Desired,
                amount1Desired: params.amount1Desired,
                amount0Min: params.amount0Min,
                amount1Min: params.amount1Min
            })
        );

        _mint(params.recipient, (tokenId <span class="hljs-operator">=</span> _nextId<span class="hljs-operator">+</span><span class="hljs-operator">+</span>));

        <span class="hljs-keyword">bytes32</span> positionKey <span class="hljs-operator">=</span> PositionKey.compute(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), params.tickLower, params.tickUpper);
        (, <span class="hljs-keyword">uint256</span> feeGrowthInside0LastX128, <span class="hljs-keyword">uint256</span> feeGrowthInside1LastX128, , ) <span class="hljs-operator">=</span> pool.positions(positionKey);

        <span class="hljs-comment">// idempotent set</span>
        <span class="hljs-keyword">uint80</span> poolId <span class="hljs-operator">=</span>
            cachePoolKey(
                <span class="hljs-keyword">address</span>(pool),
                PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee})
            );

        _positions[tokenId] <span class="hljs-operator">=</span> Position({
            nonce: <span class="hljs-number">0</span>,
            operator: <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>),
            poolId: poolId,
            tickLower: params.tickLower,
            tickUpper: params.tickUpper,
            liquidity: liquidity,
            feeGrowthInside0LastX128: feeGrowthInside0LastX128,
            feeGrowthInside1LastX128: feeGrowthInside1LastX128,
            tokensOwed0: <span class="hljs-number">0</span>,
            tokensOwed1: <span class="hljs-number">0</span>
        });

        <span class="hljs-keyword">emit</span> IncreaseLiquidity(tokenId, liquidity, amount0, amount1);
    }</code></pre><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Conclusion:</strong></h2><p>Uniswap V3 introduces a groundbreaking approach to liquidity provision through its Concentrated Liquidity Market Maker (CLAMM) model, significantly enhancing capital efficiency compared to Uniswap V2. By allowing liquidity providers (LPs) to concentrate their funds within specific price ranges (ticks), Uniswap V3 ensures that liquidity is deployed where it is most likely to be utilized, reducing idle capital and maximizing fee earnings for active market makers.</p><p><strong>Key takeaways include:</strong></p><ol><li><p><strong>Capital Efficiency</strong>: Unlike V2, where liquidity is spread uniformly across all price ranges, V3 enables LPs to allocate funds to targeted price intervals. This means the same amount of capital can facilitate larger trades within a specified range, improving capital utilization and reducing slippage.</p></li><li><p><strong>Tick-Based System</strong>: Liquidity in V3 is managed within discrete price ranges (ticks), functioning like mini-V2 pools but with concentrated depth. This allows LPs to create multiple positions, hedge risks, and implement advanced strategies such as limit orders.</p></li><li><p><strong>Reserves &amp; Price Calculation</strong>: V3 tracks liquidity (L) and price (√P) rather than raw reserves. Virtual reserves ensure that liquidity behaves like a traditional AMM within an active range while optimizing gas and precision using Q notation (sqrtPriceX96).</p></li><li><p><strong>Active Liquidity Management</strong>: LPs must monitor their positions and adjust liquidity as price moves out of their designated range to remain active and earn fees. This shifts liquidity provision from a passive to a more active strategy.</p></li><li><p><strong>Minting Liquidity</strong>: Adding liquidity in V3 involves specifying a price range, calculating liquidity delta (L), and pre-computing token amounts (amount0Delta and amount1Delta) to ensure optimal capital allocation. The NonfungiblePositionManager facilitates this process, minting NFTs representing LP positions.</p></li></ol><p>Uniswap V3’s innovations make it a powerful tool for both retail and professional liquidity providers, offering greater control, efficiency, and earning potential. However, this comes with increased complexity, requiring LPs to actively manage their positions to maximize returns. By understanding these core concepts—liquidity concentration, tick-based pricing, and virtual reserves—users can better navigate Uniswap V3’s advanced features and leverage its capabilities for decentralized market-making.</p><p>The evolution from V2 to V3 marks a significant step forward in decentralized finance (DeFi), paving the way for more sophisticated and capital-efficient AMM designs in the future.</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><br><br></h2><br>]]></content:encoded>
            <author>break-into-defi@newsletter.paragraph.com (paul elisha)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/c5bd505199f6a2e6476a60e1e3c99827.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Uniswap V2: Advanced Mechanics]]></title>
            <link>https://paragraph.com/@break-into-defi/uniswap-v2-advanced-mechanics</link>
            <guid>NNYzaUF9hoT4LOTHqYEn</guid>
            <pubDate>Tue, 17 Dec 2024 07:12:57 GMT</pubDate>
            <description><![CDATA[Uniswap V2 is one of the most popular decentralized exchanges that relies on the Automated Market Maker (AMM) principles to facilitate interaction with decentralized pools of liquidity either by trading, or earning yield from liquidity provision. DeFi protocols like Uniswap rely on precise mathematical models to calculate user shares, distribute profits and maintain pool balance. Fundamental to these protocols is the concept of user shares — the proportional ownership of assets in a pool — an...]]></description>
            <content:encoded><![CDATA[<br><p>Uniswap V2 is one of the most popular decentralized exchanges that relies on the Automated Market Maker (AMM) principles to facilitate interaction with decentralized pools of liquidity either by trading, or earning yield from liquidity provision.&nbsp;</p><p>DeFi protocols like Uniswap rely on precise mathematical models to calculate user shares, distribute profits and maintain pool balance.</p><p>Fundamental to these protocols is the concept of user shares — the proportional ownership of assets in a pool — and the mechanism of profit distribution.</p><p>In this article, I provide an in-depth explanation of the mechanisms, math, and processes used in these protocols for share, profit calculation and pool rebalancing.</p><h3 id="h-shares-in-defi" class="text-2xl font-header"><strong>Shares in&nbsp;DeFi</strong></h3><p><br>Shares in DeFi protocols represent ownership or a claim to a portion of pooled assets. These shares track user contributions relative to the pool, determine user entitlement to profits. It simplifies proportional ownership in scenarios with many participants. In Uniswap V2, shares represent a user’s liquidity in token pairs.</p><h3 id="h-amm-based-share-calculation" class="text-2xl font-header"><strong>AMM-based Share Calculation.</strong></h3><p><br>When liquidity providers deposit tokens into a uniswap pool, they receive liquidity provider (LP) tokens as proof of ownership which entitles them to:</p><ol><li><p>A share of the pool’s trading fees.</p></li><li><p>A proportional claim on the pool’s reserves or total liquidity.</p></li></ol><p>The value of a pool changes based on market conditions — deposits, withdrawals, trading fees.</p><h3 id="h-mint-pool-share-math" class="text-2xl font-header"><strong>Mint Pool Share Math.</strong></h3><p><br>In a single token pool, when the total deposits match the pool’s reserves or total liquidity, the amount of shares to mint can be calculated based on ratio 1:1.</p><p><strong><em>Example 1:&nbsp;</em></strong><br>User A deposits 300 usdc in a pool.<br>User B deposits 500 usdc in a pool.<br>User C deposits 200 usdc in a pool.</p><p>The pool’s value is 1000 usdc.&nbsp;<br>The shares to mint is ratio-based, 1:1, because;</p><p>Total liquidity provided = The pool’s total liquidity or reserves.<br>The shares to mint per user = 1000 total deposits/1000 total liquidity.</p><p>Supposing each user add 1 usdc to the pool, they get 1 shares minted to them each.&nbsp;</p><p>Though, the shares to mint is calculated based on the users total deposits. The shares to mint of a user who provided 300 usdc to the pool will be 300 shares.</p><p>LP tokens are minted based on the ratio of the liquidity provided by the user compared to the total liquidity in the pool.</p><p><strong><em>Example 2:&nbsp;</em></strong><br>If the pool profits 100 usdc from a yield bearing vault, now the total deposits is not equal to the pool value since the pool has generated 100 usdc profit which totals to 1,100 usdc.</p><p>If a User D deposits 110 usdc, the total deposited changes forwad.&nbsp;<br>To calculate the shares to mint,&nbsp;<br>T — Total shares minted<br>S — Shares to mint<br>L0 — Value of the pool before deposit<br>L1 — Value of the pool after deposit</p><p>Unlike the previous example where no share has been minted because it was based on first deposit, we assume T &gt; 0.</p><p>Shares to mint = L1 — L0/L0 * T</p><p>The value of the pool could be derived using the balance of tokens deposited in the pool or it could be derived in other ways since the pool could generate some yield.</p><p>The percentage of shares to mint should be proportional to the percentage increase from L0 to L1. The amount of shares to mint should equally be proportional to the change in increase.</p><p>Let us put numbers into the formula.</p><p>If L0 = 1100, L1 = 1210, T = 1000, S =&nbsp;?</p><p>S = L1-L0/L0 <em> T<br></em>1210–1100/1100<em> </em> 1000<br>Shares to mint = 100 LP tokens.</p><p>The same ratio applies to calculate token B.</p><h3 id="h-pool-tokens-amounts-math" class="text-2xl font-header"><strong>Pool Tokens Amounts Math.</strong></h3><p><br>Unlike the single token pool which could have a yield generation strategy contract deployed separately for it to generate profit for the pool which is proportionally distributed across all liquidity providers. Uniswap V2 generates profits through a fee-based exchange mechanism that allows a user to trade tokens which is proportionally distributed to liquidity providers.</p><p>Uniswap V2 pool is designed in such a way that its pool manages two tokens, token X and token Y, for the exchange to occur. Similar to the traditional exchange market, the exchange rate is the price of exchange for the desired token. For the exchange to occur, the two tokens must be available in Uniswap V2 pool, which is provided by a liquidity provider. The responsibility of a liquidity provider is to create a liquidity pool or add to an existing pool which is ratio-based. The idea of a ratio-based pool is that the ratio of token X and token Y in the pool is able to efficiently cater for traders who desire either token. This ratio is set when creating a liquidity pool, it is simply the amount of both tokens added at the point of creation.&nbsp;</p><p>Uniswap does not allow adding arbitrary amounts of tokens as liquidity unless it is proportional to the ratio of tokens in the pool. To determine the <em>amount of liquidity</em> to provide, let’s consider:</p><ol><li><p>The pool’s reserve.</p></li><li><p>The spot price.</p></li></ol><p>The pool’s reserve is the total amount of token X and token Y in a Uniswap pool sufficient to efficiently enable traders to swap. The ratio of the reserve is used to determine the price of tokens. The concept of price is such that without price you cannot know the value of something (token X or token Y) added to a pool, in other words, the price of tokens is used to determine the optimal amounts of tokens that can added to the pool. </p><blockquote><p><strong><em>Note</em></strong>: When the value(liquidity) of token X or token Y is added to a pool at a certain price, it is distributed evenly along every possible price curve. </p></blockquote><p>For example, if an LP provides 10 ETH and 10,000 USDT, the liquidity is added across the entire price curve, which includes prices like:</p><p>1 ETH = 500 USDT (low price)<br>1 ETH = 5,000 USDT (high price)<br><br>And every possible price in between. Even if the current market price of ETH is 1 ETH = 1,000 USDT, some of the liquidity is allocated to very low prices (e.g., 1 ETH = 500 USDT) and very high prices (e.g., 1 ETH = 5,000 USDT). If trading doesn't occur at those price ranges, the liquidity spread to that price range stays at that price until a swap occur otherwise the value remain unused (fragmented).<br><br>So, to keep the price constant, since price only changes when a swap occurs and not when liquidity is provided, the ratio of liquidity to provide must equal the ratio of the pool’s reserves.</p><p><strong><em>Mathematically,</em></strong></p><p>Price = Token X Reserve / Token Y Reserve.</p><p>If we have 1 million dai and 2,000 eth in a pool.&nbsp;</p><p>Price of token Y = 1 million DAI / 2,000 ETH.</p><p>Therefore, Price of token Y in terms of token X= 500 DAI per ETH.</p><p>Note: This is the spot price of token Y in terms of token X.&nbsp;</p><p>The price of tokens after adding liquidity = The price of tokens before adding liquidity.</p><p>X0 — amount of token X before adding liquidity.<br>Y0 — amount of token Ybefore adding liquidity.<br>ΔX — amount of token X to add as liquidity.<br>ΔY  — amount of token Y to add as liquidity.</p><p>ΔX/ΔY = X/Y</p><p>Uniswap operates using the constant product AMM where <em>X * Y= K</em></p><blockquote><p>It says, liquidity must be added in proportion to the pool’s current reserves, ensuring X and Y remain in the correct ratio. This is because any imbalance would break the <strong>constant product invariant</strong>.</p></blockquote><p>Therefore, X/Y represents the spot price.&nbsp;<br>Equally, ΔX/ΔY represents the spot price since the price doesn’t change based on the given ratio applied even after adding liquidity.</p><p>The spot price is derived from the ratio of the tokens reserves. It is the price of the tokens in the Uniswap pool which does not reflect external price unless an arbitrage occurs to even the prices.</p><p>The formula shows that to keep the price constant, using the same ratio we must optimally derive how much of token X and Y to provide.</p><p>Expanding the formula, Y+ ΔY/ X + ΔX = X / Y<br>Y.ΔY = X.ΔX<br>ΔX/ΔY = X / Y satisfies the equation above.</p><p>So to calculate the amount of token X to add:<br>ΔX = Y.ΔY / X</p><p>using this formula, we can derive the optimal amount of token X to add to the pool.<br><br>you can find this in the uniswap code:</p><pre data-type="codeBlock" text="    function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
        require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
        require(reserveA > 0 &amp;&amp; reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        amountB = amountA.mul(reserveB) / reserveA;
    }"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">quote</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> amountA, <span class="hljs-keyword">uint</span> reserveA, <span class="hljs-keyword">uint</span> reserveB</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> amountB</span>) </span>{
        <span class="hljs-built_in">require</span>(amountA <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">'UniswapV2Library: INSUFFICIENT_AMOUNT'</span>);
        <span class="hljs-built_in">require</span>(reserveA <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> reserveB <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">'UniswapV2Library: INSUFFICIENT_LIQUIDITY'</span>);
        amountB <span class="hljs-operator">=</span> amountA.mul(reserveB) <span class="hljs-operator">/</span> reserveA;
    }</code></pre><p>In a scenario where the deposited tokens or liquidity doesn’t match the correct ratio.</p><ol><li><p>Uniswap adjusts your deposit to fit the correct ratio.</p></li><li><p>Uniswap will use only the amount required to match the pool ratio and refund excess amount of tokens.</p></li><li><p>You will receive fewer LP tokens since only the portion of tokens that optimally fit the correct ratio is used to provide liquidity.</p></li></ol><h3 id="h-pool-shares-math" class="text-2xl font-header"><strong>Pool Shares&nbsp;Math.</strong></h3><p><br>Uniswap uses a math function to represent the total amount of Liquidity in the pool,&nbsp;</p><p>f(x,y) = L</p><p>also, uniswap uses the square of x and y to represent liquidity.</p><p>i.e. √x.y = L</p><p>in other words, f(x,y) = √x.y.<br>x and y — the pool reserves of tokens<br>L — the total liquidity tokens minted or that LPs hold altogether.<br><br>see the uniswap code:</p><pre data-type="codeBlock" text="
        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
        if (_totalSupply == 0) {
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
           _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens"><code>
        <span class="hljs-keyword">uint</span> _totalSupply <span class="hljs-operator">=</span> totalSupply; <span class="hljs-comment">// gas savings, must be defined here since totalSupply can update in _mintFee</span>
        <span class="hljs-keyword">if</span> (_totalSupply <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            liquidity <span class="hljs-operator">=</span> Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
           _mint(<span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), MINIMUM_LIQUIDITY); <span class="hljs-comment">// permanently lock the first MINIMUM_LIQUIDITY tokens</span></code></pre><pre data-type="codeBlock" text="    // this low-level function should be called from a contract which performs important safety checks
    function mint(address to) external lock returns (uint liquidity) {
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
        uint balance0 = IERC20(token0).balanceOf(address(this));
        uint balance1 = IERC20(token1).balanceOf(address(this));
        uint amount0 = balance0.sub(_reserve0);
        uint amount1 = balance1.sub(_reserve1);

        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
        if (_totalSupply == 0) {
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
           _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
        } else {
            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
        }
        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
        _mint(to, liquidity);

        _update(balance0, balance1, _reserve0, _reserve1);
        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
        emit Mint(msg.sender, amount0, amount1);
    }"><code>    <span class="hljs-comment">// this low-level function should be called from a contract which performs important safety checks</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">lock</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> liquidity</span>) </span>{
        (<span class="hljs-keyword">uint112</span> _reserve0, <span class="hljs-keyword">uint112</span> _reserve1,) <span class="hljs-operator">=</span> getReserves(); <span class="hljs-comment">// gas savings</span>
        <span class="hljs-keyword">uint</span> balance0 <span class="hljs-operator">=</span> IERC20(token0).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
        <span class="hljs-keyword">uint</span> balance1 <span class="hljs-operator">=</span> IERC20(token1).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
        <span class="hljs-keyword">uint</span> amount0 <span class="hljs-operator">=</span> balance0.sub(_reserve0);
        <span class="hljs-keyword">uint</span> amount1 <span class="hljs-operator">=</span> balance1.sub(_reserve1);

        <span class="hljs-keyword">bool</span> feeOn <span class="hljs-operator">=</span> _mintFee(_reserve0, _reserve1);
        <span class="hljs-keyword">uint</span> _totalSupply <span class="hljs-operator">=</span> totalSupply; <span class="hljs-comment">// gas savings, must be defined here since totalSupply can update in _mintFee</span>
        <span class="hljs-keyword">if</span> (_totalSupply <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            liquidity <span class="hljs-operator">=</span> Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
           _mint(<span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), MINIMUM_LIQUIDITY); <span class="hljs-comment">// permanently lock the first MINIMUM_LIQUIDITY tokens</span>
        } <span class="hljs-keyword">else</span> {
            liquidity <span class="hljs-operator">=</span> Math.<span class="hljs-built_in">min</span>(amount0.mul(_totalSupply) <span class="hljs-operator">/</span> _reserve0, amount1.mul(_totalSupply) <span class="hljs-operator">/</span> _reserve1);
        }
        <span class="hljs-built_in">require</span>(liquidity <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'</span>);
        _mint(to, liquidity);

        _update(balance0, balance1, _reserve0, _reserve1);
        <span class="hljs-keyword">if</span> (feeOn) kLast <span class="hljs-operator">=</span> <span class="hljs-keyword">uint</span>(reserve0).mul(reserve1); <span class="hljs-comment">// reserve0 and reserve1 are up-to-date</span>
        <span class="hljs-keyword">emit</span> Mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount0, amount1);
    }</code></pre><p>Don’t forget — For optimal liquidity addition, the ratio of the deposited tokens must match the current pool ratio.</p><p>Δx/Δy​=x/y</p><p>If √x.y = L is the total liquidity LP’s collectively hold before adding liquidity.<br>Then, √x1.y1 = L1 is the total liquidity LP’s collectively hold after adding liquidity.</p><p>So, L1 — L = LP’s token amount.</p><p>LP’s token amount/L1 * T = LP’s pool share.</p><p><strong><em>Example 3:</em></strong><br>If the reserves of two tokens in a pool are 1000 and 500 respectively.<br>The ratio is 1000:500 = 2:1<br><br>Supposing you deposit 200 amount of token X and token Y<br>To maintain the 2:1 ratio , token X optimally fits the pool ratio but we need 100 amount of token Y to match the ratio.<br><br>So, uniswap will calculate the amount of token Y to add to the pool optimally and refund the remaining 100 tokens.<br><br>you can find this in the uniswap code:</p><pre data-type="codeBlock" text="(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
        if (reserveA == 0 &amp;&amp; reserveB == 0) {
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
            uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
            if (amountBOptimal <= amountBDesired) {
                require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
                (amountA, amountB) = (amountADesired, amountBOptimal);"><code>(<span class="hljs-keyword">uint</span> reserveA, <span class="hljs-keyword">uint</span> reserveB) <span class="hljs-operator">=</span> UniswapV2Library.getReserves(factory, tokenA, tokenB);
        <span class="hljs-keyword">if</span> (reserveA <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> reserveB <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            (amountA, amountB) <span class="hljs-operator">=</span> (amountADesired, amountBDesired);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">uint</span> amountBOptimal <span class="hljs-operator">=</span> UniswapV2Library.quote(amountADesired, reserveA, reserveB);
            <span class="hljs-keyword">if</span> (amountBOptimal <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> amountBDesired) {
                <span class="hljs-built_in">require</span>(amountBOptimal <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amountBMin, <span class="hljs-string">'UniswapV2Router: INSUFFICIENT_B_AMOUNT'</span>);
                (amountA, amountB) <span class="hljs-operator">=</span> (amountADesired, amountBOptimal);</code></pre><pre data-type="codeBlock" text="    function _addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin
    ) internal virtual returns (uint amountA, uint amountB) {
        // create the pair if it doesn't exist yet
        if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
            IUniswapV2Factory(factory).createPair(tokenA, tokenB);
        }
        (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
        if (reserveA == 0 &amp;&amp; reserveB == 0) {
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
            uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
            if (amountBOptimal <= amountBDesired) {
                require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
                (amountA, amountB) = (amountADesired, amountBOptimal);
            } else {
                uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
                assert(amountAOptimal <= amountADesired);
                require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
                (amountA, amountB) = (amountAOptimal, amountBDesired);
            }
        }
    }"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_addLiquidity</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> tokenA,
        <span class="hljs-keyword">address</span> tokenB,
        <span class="hljs-keyword">uint</span> amountADesired,
        <span class="hljs-keyword">uint</span> amountBDesired,
        <span class="hljs-keyword">uint</span> amountAMin,
        <span class="hljs-keyword">uint</span> amountBMin
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> amountA, <span class="hljs-keyword">uint</span> amountB</span>) </span>{
        <span class="hljs-comment">// create the pair if it doesn't exist yet</span>
        <span class="hljs-keyword">if</span> (IUniswapV2Factory(factory).getPair(tokenA, tokenB) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
            IUniswapV2Factory(factory).createPair(tokenA, tokenB);
        }
        (<span class="hljs-keyword">uint</span> reserveA, <span class="hljs-keyword">uint</span> reserveB) <span class="hljs-operator">=</span> UniswapV2Library.getReserves(factory, tokenA, tokenB);
        <span class="hljs-keyword">if</span> (reserveA <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> reserveB <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            (amountA, amountB) <span class="hljs-operator">=</span> (amountADesired, amountBDesired);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">uint</span> amountBOptimal <span class="hljs-operator">=</span> UniswapV2Library.quote(amountADesired, reserveA, reserveB);
            <span class="hljs-keyword">if</span> (amountBOptimal <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> amountBDesired) {
                <span class="hljs-built_in">require</span>(amountBOptimal <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amountBMin, <span class="hljs-string">'UniswapV2Router: INSUFFICIENT_B_AMOUNT'</span>);
                (amountA, amountB) <span class="hljs-operator">=</span> (amountADesired, amountBOptimal);
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">uint</span> amountAOptimal <span class="hljs-operator">=</span> UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
                <span class="hljs-built_in">assert</span>(amountAOptimal <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> amountADesired);
                <span class="hljs-built_in">require</span>(amountAOptimal <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amountAMin, <span class="hljs-string">'UniswapV2Router: INSUFFICIENT_A_AMOUNT'</span>);
                (amountA, amountB) <span class="hljs-operator">=</span> (amountAOptimal, amountBDesired);
            }
        }
    }</code></pre><h2 id="h-adding-liquidity-to-uniswap-v2" class="text-3xl font-header"><strong>Adding Liquidity to Uniswap V2:</strong></h2><p>The main entry point to add liquidity to Uniswap V2 pool is the <code>addLiquidity</code> function in the periphery contract.</p><pre data-type="codeBlock" text="    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
        (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
        TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
        liquidity = IUniswapV2Pair(pair).mint(to);
    }"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addLiquidity</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> tokenA,
        <span class="hljs-keyword">address</span> tokenB,
        <span class="hljs-keyword">uint</span> amountADesired,
        <span class="hljs-keyword">uint</span> amountBDesired,
        <span class="hljs-keyword">uint</span> amountAMin,
        <span class="hljs-keyword">uint</span> amountBMin,
        <span class="hljs-keyword">address</span> to,
        <span class="hljs-keyword">uint</span> deadline
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">ensure</span>(<span class="hljs-params">deadline</span>) <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> amountA, <span class="hljs-keyword">uint</span> amountB, <span class="hljs-keyword">uint</span> liquidity</span>) </span>{
        (amountA, amountB) <span class="hljs-operator">=</span> _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
        <span class="hljs-keyword">address</span> pair <span class="hljs-operator">=</span> UniswapV2Library.pairFor(factory, tokenA, tokenB);
        TransferHelper.safeTransferFrom(tokenA, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, pair, amountA);
        TransferHelper.safeTransferFrom(tokenB, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, pair, amountB);
        liquidity <span class="hljs-operator">=</span> IUniswapV2Pair(pair).mint(to);
    }</code></pre><p>Here is how it works:<br><br>- It calls the internal function <code>_addLiquidity</code> explained earlier which does the ratio calculation and the optimal amounts to add.<br><br>- It transfers the tokens to the pair contract.<br><br>- It mints liquidity to the user.</p><h2 id="h-key-takeaways" class="text-3xl font-header"><br><strong>Key Takeaways:</strong></h2><br><p><strong>Shares in Uniswap V2</strong>: Represented by LP tokens, which track user contributions to the pool.</p><p><strong>Profit Mechanism</strong>: Profits from trading fees are embedded in the value of LP tokens.</p><p><strong>Fee Distribution</strong>: Automatically proportional to LP tokens held.</p><p><strong>Dynamic Rebalancing</strong>: Ensured by the constant product formula.</p><h3 id="h-conclusion" class="text-2xl font-header"><strong>Conclusion:</strong></h3><p>The Uniswap V2's mechanisms for calculating shares and distributing profits create a seamless, decentralized experience for liquidity providers. The protocol simplifies ownership tracking, and by leveraging its constant product formula ensures efficient pool rebalancing and fee collection. Understanding these mechanisms enables LPs to make informed decisions and optimize their DeFi strategies.<br></p><br>]]></content:encoded>
            <author>break-into-defi@newsletter.paragraph.com (paul elisha)</author>
            <category>uniswap</category>
            <category>v2</category>
            <category>liquidity</category>
            <category>swap</category>
            <category>amm</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/73de9b8d3e9e3db0170cba09bb88cea3.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Deep dive into Permit2]]></title>
            <link>https://paragraph.com/@break-into-defi/deep-dive-into-permit2</link>
            <guid>ERcVrUExtsdK5o2bMJHb</guid>
            <pubDate>Fri, 13 Dec 2024 23:10:40 GMT</pubDate>
            <description><![CDATA[Permit2 is a token approval mechanism introduced to enhance the user experience of decentralized token transactions. It allows users to smart contracts without requiring on-chain approvals. It shifts the intensive work on to smart contracts. Users express their intents to modify their permissions. Usually in a normal ERC-20 transfer transaction, an approval is given to the spender smart contract by modify allowances. The token smart contract has an allowance mapping that manages every given a...]]></description>
            <content:encoded><![CDATA[<p>Permit2 is a token approval mechanism introduced to enhance the user experience of decentralized token transactions. It allows users to smart contracts without requiring on-chain approvals. It shifts the intensive work on to smart contracts. Users express their intents to modify their permissions. <br><br>Usually in a normal ERC-20 transfer transaction, an approval is given to the spender smart contract by modify allowances. The token smart contract has an allowance mapping that manages every given approval issued by the sender. Every application that has been approved has their permissions remain indefinitely unless the user revokes the permission granted to the application.<br><br>Instead of granting this permission to the application indefinitely, we could grant permission to a smart contract which has infinite amount permitted to spend on behalf of the user that makes on-chain transactions efficient, and reduces the computation over-head required - a normal approval ERC-20 transaction would first require the application to initiate a transfer from which triggers the approval thus requiring two separate transactions. </p><h2 id="h-the-workings-of-permit2" class="text-3xl font-header"><strong>The Workings of Permit2.</strong></h2><p>1. The user initiates a normal token approval to the permit2 contract and by doing so, they have granted permission to the permit2 contract to be able to spend their tokens on their behalf. <br>2. The user signs a gasless off-chain permit message expressing their intents to modify their permission.<br>3. The application takes the permit message and delivers to the permit2 contract informing it of the user’s intent.<br>4. The permit2 contract then verifies that signature is from the actual signer and does transfer of tokens to the actual spender which is the application.</p><h1 id="h-building-a-simple-permit2-contract" class="text-4xl font-header"><strong>Building a simple Permit2 contract.</strong></h1><p>Permit2 smart contract has two token approval mechanisms - allowance transfer and signature transfer.<br><br>In this section, we will explore the signature transfer mechanism and build a simple permit2 smart contract to understand the flow of the perrmit2 contract.</p><p>This section requires the user to have a knowledge of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.com/@break-into-defi/understanding-eip-712-a-beginners-guide-to-typed-data-signing-in-ethereum"><strong>EIP-712</strong></a> which is a signature standard for typed structured data for the off-chain signature mechanism to express intents.<br></p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import &quot;https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol&quot;;
import &quot;https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol&quot;;
import &quot;https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol&quot;;

contract SimplePermit2 is EIP712 {
    using ECDSA for bytes32;

    struct TokenPermissions {
        address token;
        uint256 amount;
    }

    struct PermitTransferFrom {
        TokenPermissions permitted;
        uint256 nonce;
        uint256 deadline;
    }

    struct SignatureTransferDetails {
        address to;
        uint256 requestedAmount;
    }

    bytes32 private constant _PERMIT_TRANSFER_FROM_TYPEHASH =
        keccak256(&quot;PermitTransferFrom(TokenPermissions permitted, address spender, uint256 nonce, uint256 deadline)TokenPermissions(address token,uint256 amount)&quot;);

    bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256(&quot;TokenPermissions(address token,uint256 amount)&quot;);

    constructor() EIP712(&quot;SimplePermit2&quot;, &quot;1&quot;) {}

    function permitTransferFrom(
        PermitTransferFrom memory permit,
        SignatureTransferDetails calldata transferDetails,
        address owner,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public view  {
        bytes memory signature = bytes.concat(r, s, bytes1(v));
        
        uint256 requestedAmount = transferDetails.requestedAmount;

        if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline);
        if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount);

        permit.nonce++;

        signature.verify( _hashTypedData(getHashData(permit)), owner);

        ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
    }

    function getHashData(
        PermitTransferFrom memory permit
    ) public view returns (bytes32) {

        bytes32 tokenPermissionHash = keccak256(
                abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permit.permitted)
            );

        return keccak256(
                    abi.encode(
                        _PERMIT_TRANSFER_FROM_TYPEHASH,
                        keccak256(abi.encodePacked(tokenPermissionHash)),
                        msg.sender,
                        permit.nonce,
                        permit.deadline
                    )
                );
    }

    function verify(
        bytes calldata signature,
        bytes32 digest,
        address claimedSigner,
    ) internal pure returns (bool) {
        bytes32 r;
        bytes32 s;
        uint8 v;

        (r, s) = abi.decode(signature, (bytes32, bytes32));
        v = uint8(signature[64]);

        (address signer, , ) = digest.tryRecover(v, r, s);

        if (signer == address(0)) revert InvalidSignature();
        if (signer != claimedSigner) revert InvalidSigner();

        return signer == claimedSigner;
    }

}"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimplePermit2</span> <span class="hljs-keyword">is</span> <span class="hljs-title">EIP712</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">ECDSA</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title"><span class="hljs-keyword">bytes32</span></span>;

    <span class="hljs-keyword">struct</span> <span class="hljs-title">TokenPermissions</span> {
        <span class="hljs-keyword">address</span> token;
        <span class="hljs-keyword">uint256</span> amount;
    }

    <span class="hljs-keyword">struct</span> <span class="hljs-title">PermitTransferFrom</span> {
        TokenPermissions permitted;
        <span class="hljs-keyword">uint256</span> nonce;
        <span class="hljs-keyword">uint256</span> deadline;
    }

    <span class="hljs-keyword">struct</span> <span class="hljs-title">SignatureTransferDetails</span> {
        <span class="hljs-keyword">address</span> to;
        <span class="hljs-keyword">uint256</span> requestedAmount;
    }

    <span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">constant</span> _PERMIT_TRANSFER_FROM_TYPEHASH <span class="hljs-operator">=</span>
        <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"PermitTransferFrom(TokenPermissions permitted, address spender, uint256 nonce, uint256 deadline)TokenPermissions(address token,uint256 amount)"</span>);

    <span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> _TOKEN_PERMISSIONS_TYPEHASH <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"TokenPermissions(address token,uint256 amount)"</span>);

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) <span class="hljs-title">EIP712</span>(<span class="hljs-params"><span class="hljs-string">"SimplePermit2"</span>, <span class="hljs-string">"1"</span></span>) </span>{}

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">permitTransferFrom</span>(<span class="hljs-params">
        PermitTransferFrom <span class="hljs-keyword">memory</span> permit,
        SignatureTransferDetails <span class="hljs-keyword">calldata</span> transferDetails,
        <span class="hljs-keyword">address</span> owner,
        <span class="hljs-keyword">uint8</span> v,
        <span class="hljs-keyword">bytes32</span> r,
        <span class="hljs-keyword">bytes32</span> s
    </span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span>  </span>{
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> signature <span class="hljs-operator">=</span> <span class="hljs-built_in">bytes</span>.<span class="hljs-built_in">concat</span>(r, s, <span class="hljs-keyword">bytes1</span>(v));
        
        <span class="hljs-keyword">uint256</span> requestedAmount <span class="hljs-operator">=</span> transferDetails.requestedAmount;

        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">&gt;</span> permit.deadline) <span class="hljs-keyword">revert</span> SignatureExpired(permit.deadline);
        <span class="hljs-keyword">if</span> (requestedAmount <span class="hljs-operator">&gt;</span> permit.permitted.amount) <span class="hljs-keyword">revert</span> InvalidAmount(permit.permitted.amount);

        permit.nonce+<span class="hljs-operator">+</span>;

        signature.verify( _hashTypedData(getHashData(permit)), owner);

        ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getHashData</span>(<span class="hljs-params">
        PermitTransferFrom <span class="hljs-keyword">memory</span> permit
    </span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bytes32</span></span>) </span>{

        <span class="hljs-keyword">bytes32</span> tokenPermissionHash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(
                <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(_TOKEN_PERMISSIONS_TYPEHASH, permit.permitted)
            );

        <span class="hljs-keyword">return</span> <span class="hljs-built_in">keccak256</span>(
                    <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(
                        _PERMIT_TRANSFER_FROM_TYPEHASH,
                        <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(tokenPermissionHash)),
                        <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>,
                        permit.nonce,
                        permit.deadline
                    )
                );
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verify</span>(<span class="hljs-params">
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> signature,
        <span class="hljs-keyword">bytes32</span> digest,
        <span class="hljs-keyword">address</span> claimedSigner,
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
        <span class="hljs-keyword">bytes32</span> r;
        <span class="hljs-keyword">bytes32</span> s;
        <span class="hljs-keyword">uint8</span> v;

        (r, s) <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(signature, (<span class="hljs-keyword">bytes32</span>, <span class="hljs-keyword">bytes32</span>));
        v <span class="hljs-operator">=</span> <span class="hljs-keyword">uint8</span>(signature[<span class="hljs-number">64</span>]);

        (<span class="hljs-keyword">address</span> signer, , ) <span class="hljs-operator">=</span> digest.tryRecover(v, r, s);

        <span class="hljs-keyword">if</span> (signer <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) <span class="hljs-keyword">revert</span> InvalidSignature();
        <span class="hljs-keyword">if</span> (signer <span class="hljs-operator">!</span><span class="hljs-operator">=</span> claimedSigner) <span class="hljs-keyword">revert</span> InvalidSigner();

        <span class="hljs-keyword">return</span> signer <span class="hljs-operator">=</span><span class="hljs-operator">=</span> claimedSigner;
    }

}</code></pre><h1 id="h-frontend-integration-of-permit2" class="text-4xl font-header"><strong>Frontend integration of Permit2.</strong></h1><p>The frontend integration of our <code>SimplePermit2</code> contract using ethers.js. This is similar to how Uniswap Permit2-sdk is written with a little of abstraction. But we will be writing an integration code of our contract using ethers.js. </p><pre data-type="codeBlock" text="import { ethers } from 'ethers';

async function generatePermitSignature(
  ownerAddress: string,
  tokenAddress: string,
  amount: bigint,
  nonce: bigint,
  deadline: bigint,
  spenderAddress: string,
  permit2ContractAddress: string
) {
  // 1. Connect to the user's wallet
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();

  // 2. Prepare the domain and types for EIP-712 signature
  const domain = {
    name: &quot;SimplePermit2&quot;,
    version: &quot;1&quot;,
    chainId: await signer.getChainId(),
    verifyingContract: permit2ContractAddress
  };

  const types = {
    TokenPermissions: [
      { name: &quot;token&quot;, type: &quot;address&quot; },
      { name: &quot;amount&quot;, type: &quot;uint256&quot; }
    ],
    PermitTransferFrom: [
      { name: &quot;permitted&quot;, type: &quot;TokenPermissions&quot; },
      { name: &quot;spender&quot;, type: &quot;address&quot; },
      { name: &quot;nonce&quot;, type: &quot;uint256&quot; },
      { name: &quot;deadline&quot;, type: &quot;uint256&quot; }
    ]
  };

  // 3. Create the value object
  const value = {
    permitted: {
      token: tokenAddress,
      amount: amount.toString()
    },
    spender: spenderAddress,
    nonce: nonce.toString(),
    deadline: deadline.toString()
  };

  // 4. Request the signature from the user's wallet
  try {
    const signature = await signer.signTypedData(domain, types, value);
    
    // Split the signature into v, r, s components
    const sig = ethers.Signature.from(signature);
    return {
      v: sig.v,
      r: sig.r,
      s: sig.s,
      fullSignature: signature
    };
  } catch (error) {
    console.error(&quot;Error signing typed data:&quot;, error);
    throw error;
  }
}
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">ethers</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'ethers'</span>;

async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generatePermitSignature</span>(<span class="hljs-params">
  ownerAddress: <span class="hljs-keyword">string</span>,
  tokenAddress: <span class="hljs-keyword">string</span>,
  amount: bigint,
  nonce: bigint,
  deadline: bigint,
  spenderAddress: <span class="hljs-keyword">string</span>,
  permit2ContractAddress: <span class="hljs-keyword">string</span>
</span>) </span>{
  <span class="hljs-comment">// 1. Connect to the user's wallet</span>
  const provider <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.BrowserProvider(window.ethereum);
  const signer <span class="hljs-operator">=</span> await provider.getSigner();

  <span class="hljs-comment">// 2. Prepare the domain and types for EIP-712 signature</span>
  const domain <span class="hljs-operator">=</span> {
    name: <span class="hljs-string">"SimplePermit2"</span>,
    version: <span class="hljs-string">"1"</span>,
    chainId: await signer.getChainId(),
    verifyingContract: permit2ContractAddress
  };

  const types <span class="hljs-operator">=</span> {
    TokenPermissions: [
      { name: <span class="hljs-string">"token"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"address"</span> },
      { name: <span class="hljs-string">"amount"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"uint256"</span> }
    ],
    PermitTransferFrom: [
      { name: <span class="hljs-string">"permitted"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"TokenPermissions"</span> },
      { name: <span class="hljs-string">"spender"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"address"</span> },
      { name: <span class="hljs-string">"nonce"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"uint256"</span> },
      { name: <span class="hljs-string">"deadline"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"uint256"</span> }
    ]
  };

  <span class="hljs-comment">// 3. Create the value object</span>
  const value <span class="hljs-operator">=</span> {
    permitted: {
      token: tokenAddress,
      amount: amount.toString()
    },
    spender: spenderAddress,
    nonce: nonce.toString(),
    deadline: deadline.toString()
  };

  <span class="hljs-comment">// 4. Request the signature from the user's wallet</span>
  <span class="hljs-keyword">try</span> {
    const signature <span class="hljs-operator">=</span> await signer.signTypedData(domain, types, value);
    
    <span class="hljs-comment">// Split the signature into v, r, s components</span>
    const sig <span class="hljs-operator">=</span> ethers.Signature.from(signature);
    <span class="hljs-keyword">return</span> {
      v: sig.v,
      r: sig.r,
      s: sig.s,
      fullSignature: signature
    };
  } <span class="hljs-keyword">catch</span> (<span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
    console.error(<span class="hljs-string">"Error signing typed data:"</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>)</span>;
    <span class="hljs-keyword">throw</span> <span class="hljs-function"><span class="hljs-keyword">error</span></span>;
  }
}
</code></pre><h1 id="h-key-benefits-of-permit2" class="text-4xl font-header"><br><strong>Key Benefits of Permit2.</strong></h1><ol><li><p><strong>Gas Cost Savings</strong>:</p><ul><li><p>Traditional ERC-20 token approvals require a separate transaction for each allowance, incurring gas fees. Permit2 eliminates the need for these redundant approval transactions, saving users gas costs.</p></li></ul></li><li><p><strong>Simplified User Experience</strong>:</p><ul><li><p>Users can sign a single message off-chain instead of interacting with the blockchain twice (approval + transaction). This reduces complexity and enhances accessibility, especially for new users.</p></li></ul></li><li><p><strong>Reduced Approval Risks</strong>:</p><ul><li><p>Traditional token approvals often involve granting large or unlimited allowances to smart contracts, exposing users to potential security risks. Permit2 enables users to set:</p><ul><li><p><strong>Custom Allowance Limits</strong>: Users can specify exact amounts to be approved.</p></li><li><p><strong>Time-Limited Approvals</strong>: Permissions can expire automatically after a defined period, reducing exposure.</p></li></ul></li></ul></li><li><p><strong>Cross-Platform Compatibility</strong>:</p><ul><li><p>Permit2 is designed to work seamlessly across multiple decentralized applications (dApps) and protocols. This makes it a versatile solution for trading platforms, DeFi protocols, and wallet integrations.</p></li></ul></li><li><p><strong>Batch Operations</strong>:</p><ul><li><p>Permit2 allows bulk approvals and transfers in a single transaction, optimizing workflows for advanced use cases such as portfolio management and multi-token swaps.</p></li></ul></li></ol><h2 id="h-conclusion" class="text-3xl font-header"><br><strong>Conclusion</strong></h2><p>Permit2 is a significant step forward in enhancing the efficiency, security, and user experience of token approvals for decentralized trading. By reducing gas costs, simplifying workflows, and offering advanced features such as batch operations and time-limited allowances, Permit2 provides a powerful tool for DeFi protocols and traders alike.</p><p>For developers and platforms, integrating Permit2 into their systems can unlock better user experiences while optimizing transaction workflows. To integrate Permit2 into your solidity project, click <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/PaulElisha/permit2-example-integration">here</a>.<br></p>]]></content:encoded>
            <author>break-into-defi@newsletter.paragraph.com (paul elisha)</author>
            <category>permit2</category>
            <category>erc20</category>
            <category>nft</category>
            <category>approval</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/0d5b05e82b105156f33a105bbe35b86c.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>