<?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>Zero To One</title>
        <link>https://paragraph.com/@021</link>
        <description>undefined</description>
        <lastBuildDate>Sat, 18 Apr 2026 21:39:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[How Endgame Forked Seaport to Plug in to Better Order Data]]></title>
            <link>https://paragraph.com/@021/how-endgame-forked-seaport-to-plug-in-to-better-order-data</link>
            <guid>5AxrVQO4MJlqxdJpCFuu</guid>
            <pubDate>Tue, 14 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Endgame, a non-custodial NFT rentals marketplace, recently went through an audit with Code4rena. During the course of the audit, a vulnerability was ...]]></description>
            <content:encoded><![CDATA[<p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">Endgame</a>, a non-custodial NFT rentals marketplace, recently went through an audit with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://code4rena.com/audits/2024-01-renft">Code4rena</a>. During the course of the audit, a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/code-423n4/2024-01-renft-findings/issues/203">vulnerability</a> was surfaced which showed that if a Seaport order was set to <code>PARTIAL_RESTRICTED</code>, it could be used to replay the order on Endgame and lock assets in the protocol indefinitely. <span data-name="exploding_head" class="emoji" data-type="emoji"><img src="https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f92f.png" draggable="false" loading="lazy" align="absmiddle"></span></p><p>To mitigate this, the Endgame protocol would need to reject any incoming Seaport orders that were set to <code>PARTIAL_RESTRICTED</code>. Since all data coming from Seaport to the Endgame protocol occurs via a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/packages/Zone.sol">Seaport Zone</a> contract, using the <code>ZoneParameters</code> struct for all the data, this mitigation should be as easy as checking the struct for the <code>orderType</code> and rejecting the fulfillment, right?</p><p>Well... not exactly. Much to my dismay, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ProjectOpenSea/seaport-core/tree/c17f6a8b238fa3b4d23e28a4a72efad4baeafb5d">Seaport Core v1.5</a> doesn't expose the <code>orderType</code> of the order on its own. If I wanted to access the <code>orderType</code>, I would have to fork Seaport.</p><p>This blog post will be an overview of how I did that, and will hopefully help unlock some of the interesting tricks that the optimized Seaport contracts use under the hood.</p><p>Let's get started!</p><div class="relative header-and-anchor"><h3 id="h-seaport-zones">Seaport Zones</h3></div><p>A Seaport <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ProjectOpenSea/seaport/blob/2ff6ea378d484019647a0cc28a5552d9b184ee2e/docs/ZoneDocumentation.md">zone contract</a> is a permission-less extension of the Seaport protocol which can be implemented by a 3rd party. The signer of an order gets the option of whether they want to include that zone as part of the execution of the order. </p><p>The zone will execute its logic only after all token swaps in the order have occurred. This gives the zone contract the final say on whether the order should be fulfilled or not. Intuitively, you can think of zones as hooks for the Seaport protocol.</p><p>The Zone interface exposes one public function which must be implemented for the Seaport core contract to call: </p><pre data-type="codeBlock"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ZoneInterface</span> </span>{
    <span class="hljs-comment">/**
     * @dev Validates an order.
     *
     * @param zoneParameters The context about the order fulfillment and any
     *                       supplied extraData.
     *
     * @return validOrderMagicValue The magic value that indicates a valid
     *                              order.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateOrder</span>(<span class="hljs-params">
        ZoneParameters <span class="hljs-keyword">calldata</span> zoneParameters
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bytes4</span> validOrderMagicValue</span>)</span>;
}</code></pre><p>And its <code>ZoneParameters</code> are as follows: </p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">ZoneParameters</span> {
    <span class="hljs-keyword">bytes32</span> orderHash;
    <span class="hljs-keyword">address</span> fulfiller;
    <span class="hljs-keyword">address</span> offerer;
    SpentItem[] offer;
    ReceivedItem[] consideration;
    <span class="hljs-keyword">bytes</span> extraData;
    <span class="hljs-keyword">bytes32</span>[] orderHashes;
    <span class="hljs-keyword">uint256</span> startTime;
    <span class="hljs-keyword">uint256</span> endTime;
    <span class="hljs-keyword">bytes32</span> zoneHash;
}</code></pre><p>For the purposes of Endgame, I would need to include a new parameter called <code>orderType</code> which could be tacked onto the <code>ZoneParameters</code> struct.</p><div class="relative header-and-anchor"><h3 id="h-bridging-the-gap-from-seaport-to-a-zone">Bridging the Gap: From Seaport to a Zone</h3></div><p>So, where in the weeds of the Seaport protocol does it make its call to a zone contract? The answer can be found in the <code>ZoneInteraction.sol</code> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ProjectOpenSea/seaport-core/blob/c17f6a8b238fa3b4d23e28a4a72efad4baeafb5d/src/lib/ZoneInteraction.sol#L77">contract</a>. </p><p>Inside, there is a function <code>_assertRestrictedAdvancedOrderValidity</code> which will manually build up the calldata for the zone interaction and then make the call. A truncated version, with only the parts we care about, looks like this:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_assertRestrictedAdvancedOrderValidity</span>(<span class="hljs-params">
    AdvancedOrder <span class="hljs-keyword">memory</span> advancedOrder,
    <span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">memory</span> orderHashes,
    <span class="hljs-keyword">bytes32</span> orderHash
</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
    <span class="hljs-comment">// Declare variables that will be assigned based on the order type.</span>
    <span class="hljs-keyword">address</span> target;
    <span class="hljs-keyword">uint256</span> errorSelector;
    MemoryPointer callData;
    <span class="hljs-keyword">uint256</span> size;

    <span class="hljs-comment">// Retrieve the parameters of the order in question.</span>
    OrderParameters <span class="hljs-keyword">memory</span> parameters <span class="hljs-operator">=</span> advancedOrder.parameters;

    <span class="hljs-comment">// ... </span>

    <span class="hljs-comment">// Encode the `validateOrder` call in memory.</span>
    (callData, size) <span class="hljs-operator">=</span> _encodeValidateOrder(orderHash, parameters, advancedOrder.extraData, orderHashes);

    <span class="hljs-comment">// ...</span>

    <span class="hljs-comment">// Perform call and ensure a corresponding magic value was returned.</span>
    _callAndCheckStatus(target, orderHash, callData, size, errorSelector);
}</code></pre><p>We can see that <code>_encodeValidateOrder</code> returns the calldata for a <code>CALL</code> to the <code>validateOrder</code> function on the <code>ZoneInterface</code>. This calldata can then be executed by <code>_callAndCheckStatus</code>.</p><p>Let's now turn to <code>_encodeValidateOrder</code>.</p><div class="relative header-and-anchor"><h3 id="h-encoding-calldata-by-hand">Encoding Calldata by Hand</h3></div><p>Now, we find ourselves inside the <code>ConsiderationEncoder.sol</code> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ProjectOpenSea/seaport-core/blob/c17f6a8b238fa3b4d23e28a4a72efad4baeafb5d/src/lib/ConsiderationEncoder.sol#L327">contract</a> to get a better look at <code>_encodeValidateOrder</code>.</p><p>Let's start by reviewing the first few lines of code in the function:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_encodeValidateOrder</span>(<span class="hljs-params">
    <span class="hljs-keyword">bytes32</span> orderHash,
    OrderParameters <span class="hljs-keyword">memory</span> orderParameters,
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> extraData,
    <span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">memory</span> orderHashes
</span>) <span class="hljs-title"><span class="hljs-keyword">internal</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">MemoryPointer dst, <span class="hljs-keyword">uint256</span> size</span>) </span>{
    <span class="hljs-comment">// Get free memory pointer to write calldata to. This isn't allocated as</span>
    <span class="hljs-comment">// it is only used for a single function call.</span>
    dst <span class="hljs-operator">=</span> getFreeMemoryPointer();

    <span class="hljs-comment">// Write validateOrder selector and get pointer to start of calldata.</span>
    dst.write(validateOrder_selector);
    dst <span class="hljs-operator">=</span> dst.offset(validateOrder_selector_offset);

    <span class="hljs-comment">// ...logic continues on</span>
}</code></pre><p>That got complicated very quickly. The first thing to notice is the return type of <code>MemoryPointer</code>. This is just a custom type wrapper around a <code>uint256</code>, and points to the address in memory where this constructed calldata will reside. And, by returning its size as well, the full calldata can be extracted from memory when needed.</p><p>Additionally, Seaport has a few helper functions such as <code>write</code> and <code>offset</code> which makes interacting directly with memory a bit easier to understand. For example, the <code>write</code> implementation looks like this: </p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">write</span>(<span class="hljs-params">MemoryPointer mPtr, <span class="hljs-keyword">uint256</span> value</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> </span>{
    <span class="hljs-keyword">assembly</span> {
        <span class="hljs-built_in">mstore</span>(mPtr, value)
    }
}</code></pre><p>To start, we can see that the free memory pointer has been obtained, and that the selector for <code>validateOrder</code> has been added to it. Once it has been added, the current pointer <code>dst</code> is incremented forward in memory by an offset which is equal in size to the selector.</p><p>With the selector added, we can skip around a bit in this function to see how the values of the <code>ZoneParameters</code> struct are added to the calldata as well. </p><p>Let's look at how the <code>offerer</code> value is added.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Get the memory pointer to the order parameters struct.</span>
MemoryPointer src <span class="hljs-operator">=</span> orderParameters.toMemoryPointer();

<span class="hljs-comment">// Copy offerer to zoneParameters.</span>
dst.offset(ZoneParameters_offerer_offset).write(src.readUint256());</code></pre><p>Again, we can see that the offset here is used to figure out where, in memory, the <code>offerer</code> address should be copied. By doing this for all inputs to the <code>ZoneParameters</code>, the calldata will be completely built up and ready to execute. </p><div class="relative header-and-anchor"><h3 id="h-adding-to-the-consideration-encoder">Adding to the Consideration Encoder</h3></div><p>Since we want to add <code>orderType</code> to the <code>ZoneParameters</code> struct, let's see how that would look:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">ZoneParameters</span> {
    <span class="hljs-keyword">bytes32</span> orderHash;
    <span class="hljs-keyword">address</span> fulfiller;
    <span class="hljs-keyword">address</span> offerer;
    SpentItem[] offer;
    ReceivedItem[] consideration;
    <span class="hljs-keyword">bytes</span> extraData;
    <span class="hljs-keyword">bytes32</span>[] orderHashes;
    <span class="hljs-keyword">uint256</span> startTime;
    <span class="hljs-keyword">uint256</span> endTime;
    <span class="hljs-keyword">bytes32</span> zoneHash;
    OrderType orderType; <span class="hljs-comment">// &lt;-- new field added</span>
}</code></pre><p>Because all offsets in memory for this struct are hardcoded, we will need to insert a new offset so that we can know where to place the <code>OrderType</code>.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_orderHash_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x00</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_fulfiller_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x20</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_offerer_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x40</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_offer_head_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x60</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_consideration_head_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x80</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_extraData_head_offset <span class="hljs-operator">=</span> <span class="hljs-number">0xa0</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_orderHashes_head_offset <span class="hljs-operator">=</span> <span class="hljs-number">0xc0</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_startTime_offset <span class="hljs-operator">=</span> <span class="hljs-number">0xe0</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_endTime_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x100</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_zoneHash_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x120</span>;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_orderType_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x140</span>; <span class="hljs-comment">// &lt;-- our new order type value</span>
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> ZoneParameters_base_tail_offset <span class="hljs-operator">=</span> <span class="hljs-number">0x160</span>; <span class="hljs-comment">// was 0x140, bumped to 0x160 to make room</span></code></pre><p>The rest of the constant definitions can be found <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ProjectOpenSea/seaport-types/blob/b72493221ee1d2f2fb30ed94a3cc535a9028d09f/src/lib/ConsiderationConstants.sol">here</a>.</p><p>Once the offset is in place, we can now extract the <code>OrderType</code> from the <code>OrderParameters</code> struct:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Get the memory pointer to the order parameters struct.</span>
MemoryPointer src <span class="hljs-operator">=</span> orderParameters.toMemoryPointer();

<span class="hljs-comment">// ... arbitrary logic</span>

<span class="hljs-comment">// Write the OrderType into memory</span>
dstHead.offset(ZoneParameters_orderType_offset).write(src.offset(OrderParameters_orderType_offset).readUint256());</code></pre><p>And that should be everything! Seaport will now pass in our customized data during calls to <code>ValidateOrder</code>.</p><div class="relative header-and-anchor"><h3 id="h-a-debugging-side-quest">A Debugging Side-Quest</h3></div><p>So close, yet so far. One of the hard things about constructing raw calldata is that if you don't get it right the first time, it can be impossible to try to debug. While testing my adjusted <code>ZoneParameters</code> struct with <code>validateOrder()</code>, I was getting inexplicable reverts each time I executed a Seaport order.</p><p>So what's the deal? To save you hours of hair-pulling, the issue lies in the hard-coded nature of how Seaport creates this calldata. Recall the <code>validateOrder_selector</code> which gets written to memory at the start of the encoding process. </p><p>Since we have changed the construction of the <code>ZoneParameters</code> struct, we also have changed the calculated value of the function selector for <code>validateOrder()</code>. By updating <code>validateOrder_selector</code> to the newly generated value, I was able to get the call to start succeeding again.</p><div class="relative header-and-anchor"><h3 id="h-a-final-test">A Final Test</h3></div><p>Now all that's left is a quick test to ensure everything is looking good. For this, we can start with a simple zone implementation that receives the <code>ZoneParameters</code> and assigns them to storage:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Zone</span> </span>{
    <span class="hljs-comment">// public values to test against</span>
    OrderType <span class="hljs-keyword">public</span> orderType;
  
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateOrder</span>(<span class="hljs-params">ZoneParameters <span class="hljs-keyword">calldata</span> zoneParams</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bytes4</span> validOrderMagicValue</span>) </span>{

        <span class="hljs-comment">// store zone orderType</span>
        orderType <span class="hljs-operator">=</span> zoneParams.orderType;

        <span class="hljs-comment">// ... store all other zone parameters</span>
    }
}</code></pre><p>In our test, we can construct an order like so:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test_Success_AssertRestrictedAdvancedOrderValidity</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    <span class="hljs-comment">// ... setup logic occurs here</span>

    <span class="hljs-comment">// create the order parameters</span>
    OrderParameters <span class="hljs-keyword">memory</span> orderParameters <span class="hljs-operator">=</span> OrderParameters({
        <span class="hljs-comment">// ...</span>
        orderType: OrderType.FULL_RESTRICTED,
        <span class="hljs-comment">// ...</span>
    }); 

    <span class="hljs-comment">// create the advanced order</span>
    AdvancedOrder <span class="hljs-keyword">memory</span> advancedOrder <span class="hljs-operator">=</span> AdvancedOrder({
        parameters: orderParameters,
        numerator: <span class="hljs-number">1</span>,
        denominator: <span class="hljs-number">1</span>,
        signature: <span class="hljs-string">"signature"</span>,
        extraData: <span class="hljs-string">"extraData"</span>
    });

    <span class="hljs-comment">// create the order hash</span>
    <span class="hljs-keyword">bytes32</span> orderHash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"order hash"</span>);

    <span class="hljs-comment">// create the order hashes</span>
    <span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">memory</span> orderHashes <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes32</span>[](<span class="hljs-number">1</span>);
    orderHashes[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> orderHash;

    <span class="hljs-comment">// make a call to the zone using the internal Seaport `validateOrder` calldata builder</span>
    _assertRestrictedAdvancedOrderValidity(
        advancedOrder,
        orderHashes,
        orderHash
    );

    <span class="hljs-comment">// Assert the orderType is expected</span>
    assertEq(OrderType.FULL_RESTRICTED, zone.orderType());
}</code></pre><p>Here we construct an order with a type of <code>OrderType.FULL_RESTRICTED</code>, and the test shows that the call was successfully made to the zone contract because the proper <code>OrderType</code> value was set in storage. You can view the full test <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/seaport-core/blob/cbe841804b69dee8e23882b3ae9efcbd4cbec31b/test/ZoneInteraction.sol">here</a>.</p><div class="relative header-and-anchor"><h3 id="h-conclusion">Conclusion</h3></div><p>Hopefully this post has shed a bit of light on what it takes to fork seaport core and make small tweaks to it. The codebase is filled with low-level gems and I encourage you to poke around the repository as well.</p><p>This post focused on adjusting a single value in <code>ZoneParameters</code>. However, not included in this post is how I extended the <code>ZoneParameters</code> struct to include an array of all transfers that occur within a single order. </p><p>This technique required considerably more work because of the increase in complexity to encode arrays in calldata, and I felt that a tutorial on that would be a bit out of reach. But for the full source of the forked seaport core repo, you can view that <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/seaport-core">here</a>.</p><p>As always, if you have any questions about my forked Seaport core implementation, feel free to reach out to myself at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="mailto:alec@021.gg">alec@021.gg</a> or on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/alecdifederico">x.com</a>.</p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Zero To One)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/861b8644ec53823776a4a045bd0c2119.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[End-to-End Testing Blockchain Applications]]></title>
            <link>https://paragraph.com/@021/end-to-end-testing-blockchain-applications</link>
            <guid>drzur8wumYVyo7ljJYLp</guid>
            <pubDate>Mon, 13 May 2024 09:27:04 GMT</pubDate>
            <description><![CDATA[A walkthrough on how to do end-to-end testing blockchain applications with Playwright, Wagmi/Viem and Anvil.]]></description>
            <content:encoded><![CDATA[<p>More shop talk today. We’re going to have some fun with End-to-End testing blockchain applications.</p><p>What I’m <em>not</em> going to write about is testing philosophy. The discourse on why, what and how to test is saturated enough. I’ll just leave it at the following. I like tests. They provide confidence my code is working as intended. I like tests with a high ROI better than tests with a low ROI. In some cases TDD helps in designing an API. In most cases E2E is all you need.</p><p>In all cases one test is infinitely better than no test.</p><div class="relative header-and-anchor"><h2 id="h-setting-up-for-success">Setting up for success</h2></div><p>A long while back (over a year ago—84 years in web3 time), I <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/renftlabs/end-to-end-testing-dapps-with-playwright-rainbowkit-wagmi-and-anvil-90d1d143512c">wrote a piece</a> on how we approached E2E testing for our v2 protocol application. The basic initial setup is still the same:</p><ol><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://playwright.dev/">Playwright</a> as the test runner.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/anvil/">Foundry’s Anvil</a> for running a testnet node.</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wagmi.sh/">Wagmi</a> &amp; <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://viem.sh/">Viem</a> to connect wallets and interface with the blockchain.</p></li><li><p>Leverage <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wagmi.sh/react/api/connectors/mock">Wagmi’s mock connector</a> to set up a testing wallet.</p></li></ol><p>The previous article provided a get-running-quick tutorial. This one will be more in-depth. The goal is setting up a web3-ready Remix boilerplate with a big focus on E2E testing.</p><p>If you just want to see the code, there’s an <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/blockchain-web-app-e2e-testing-remix-wagmi">example repository on GitHub</a> you can check out.</p><div class="relative header-and-anchor"><h3 id="h-scaffolding">Scaffolding</h3></div><p>Lets get this show on the road. The following will create a Remix starter repository:</p><pre data-type="codeBlock" language="sh"><code>npx create-remix@latest --<span class="hljs-built_in">yes</span> blockchain-web-app-e2e-testing-remix-wagmi \
  &amp;&amp; <span class="hljs-built_in">cd</span> blockchain-web-app-e2e-testing-remix-wagmi</code></pre><p>Make sure to install Viem, Wagmi and Tanstack Query as dependencies. We also need to install Playwright as a development dependency. And install Playwright’s browsers.</p><pre data-type="codeBlock" language="sh"><code>npm install --save-exact @tanstack/react-query viem wagmi
npm install --save-dev @playwright/test
npx playwright install</code></pre><p>Grab the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://playwright.dev/docs/test-configuration#basic-configuration">example configuration from </a><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://playwright.dev">playwright.dev</a> and save it to the file <code>playwright.config.ts</code>. There is one tiny change we might want to make, which is the <code>fullyParallel</code> flag.</p><pre data-type="codeBlock" language="ts"><code>  ...
  <span class="hljs-comment">// Run all tests in parallel.</span>
  <span class="hljs-attr">fullyParallel</span>: <span class="hljs-literal">false</span>, 
  ...</code></pre><p>The reason we probably want to switch this off is because, if <code>fullyParallel</code> is enabled, Playwright—assuming there are workers available—will not only execute separate test suites in parallel <em>but also execute each test in a test suite in parallel.</em> When testing against a blockchain there’s often a linearity to the tests, requiring you to execute things serially as opposed to fully parallel.</p><p>We will get back to this at the end of the article.</p><p>Lets add our first test in <code>tests/smoke.spec.ts</code>:</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/smoke.spec.ts</span>
<span class="hljs-keyword">import</span> { expect, test } <span class="hljs-keyword">from</span> <span class="hljs-string">'@playwright/test'</span>;

<span class="hljs-title function_">test</span>(<span class="hljs-string">'load the homepage'</span>, <span class="hljs-keyword">async</span> ({ page }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">'/'</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page).<span class="hljs-title function_">toHaveTitle</span>(<span class="hljs-string">'New Remix App'</span>);
});</code></pre><p>Update <code>scripts</code> in <code>package.json</code> to execute Playwright:</p><pre data-type="codeBlock" language="json"><code><span class="hljs-comment">// package.json</span>
    <span class="hljs-attr">"start"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"remix-serve ./build/server/index.js"</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"test"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"playwright test"</span><span class="hljs-punctuation">,</span> 
    <span class="hljs-attr">"typecheck"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"tsc</span></code></pre><p>And run it:</p><pre data-type="codeBlock" language="sh"><code>npm run build
npm run <span class="hljs-built_in">test</span></code></pre><p>Okay. One more thing. Notice the <code>webServer.reuseExistingServer</code> option in <code>playwright.config.ts</code>. This tells Playwright, if an existing process exposed on <code>webServer.url</code> returns a valid response, it won’t execute <code>webServer.command</code>. This allows us to run tests against our development server. Unfortunately, Vite uses <code>:5173</code> as the port when executing it in development mode. Lets fix this by pinning the development server to <code>host: '0.0.0.0'</code> and <code>port: 3000</code>.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// vite.config.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title function_">defineConfig</span>({
  <span class="hljs-attr">plugins</span>: [<span class="hljs-title function_">remix</span>(), <span class="hljs-title function_">tsconfigPaths</span>()],
  <span class="hljs-attr">server</span>: { <span class="hljs-attr">host</span>: <span class="hljs-string">'0.0.0.0'</span>, <span class="hljs-attr">port</span>: <span class="hljs-number">3000</span> }, 
});</code></pre><p>Alright, sparky! We can now run the test against the dev server. Get that running with <code>npm run dev</code>.</p><div class="relative header-and-anchor"><h3 id="h-running-a-testnet-node">Running a testnet node</h3></div><p>We need a local RPC node. Why do we need a local RPC node? Because testing on a live chain is anything but idempotent. With a local testnet node you can spin up a fresh blockchain state each run. It also enables you to snapshot and/or revert/restore the chain state. Moreover, it allows you to fork an EVM-compatible chain at a certain block number. All this tooling is essential for any serious testing of blockchain applications.</p><p>And, it will save you a whole load of gas.</p><p>We prefer Foundry’s Anvil, but if you’re familiar with Hardhat or Truffle the same principles and processes should apply, roughly. I’ll refer to the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/getting-started/installation">Foundry installation instructions</a> to get it set up.</p><p>After installing Foundry, open a new terminal and execute <code>anvil</code>. This will boot up your testnet node to develop against.</p><div class="relative header-and-anchor"><h3 id="h-setting-up-wagmi">Setting up Wagmi</h3></div><p>Lets set up Wagmi. Create <code>app/wagmi.ts</code> and paste in the following:</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// app/wagmi.ts</span>
<span class="hljs-keyword">import</span> { createClient, http } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>;
<span class="hljs-keyword">import</span> { createConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> { injected } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/connectors"</span>;
<span class="hljs-keyword">import</span> { foundry, mainnet } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/chains"</span>;

<span class="hljs-keyword">const</span> chains = [mainnet, foundry] <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = <span class="hljs-title function_">createConfig</span>({
  chains,
  <span class="hljs-attr">client</span>: <span class="hljs-function">(<span class="hljs-params">{ chain }</span>) =&gt;</span> <span class="hljs-title function_">createClient</span>({ chain, <span class="hljs-attr">transport</span>: <span class="hljs-title function_">http</span>() }),
  <span class="hljs-attr">connectors</span>: [<span class="hljs-title function_">injected</span>()],
  <span class="hljs-attr">ssr</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// you want this to avoid hydration errors.</span>
});</code></pre><p>The next step is setting up our Remix application to make use of our Wagmi configuration. Note that we’re putting the <code>QueryClient</code> in a <code>useState</code> because we need this to be unique for every visitor. If you’d use a non-<code>useState</code> initialized <code>QueryClient</code> the client would be shared by all visitors on the server-side parts of Remix. Anyway. We’re going to do the same for the Wagmi configuration because we want to change this on-the-fly later.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// app/root.tsx</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">QueryClientProvider</span>, <span class="hljs-title class_">QueryClient</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>; 
<span class="hljs-keyword">import</span> {
  <span class="hljs-title class_">Links</span>,
  <span class="hljs-title class_">Meta</span>,
  <span class="hljs-title class_">Outlet</span>,
  <span class="hljs-title class_">Scripts</span>,
  <span class="hljs-title class_">ScrollRestoration</span>,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@remix-run/react"</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>; 
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">WagmiProvider</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>; 

<span class="hljs-keyword">import</span> { config } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/wagmi"</span>; 

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Layout</span>(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) {
  <span class="hljs-keyword">const</span> [queryClient] = <span class="hljs-title function_">useState</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">QueryClient</span>()); 
  <span class="hljs-keyword">const</span> [wagmiConfig] = <span class="hljs-title function_">useState</span>(config); 
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charSet</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Meta</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Links</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">QueryClientProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{queryClient}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">WagmiProvider</span> <span class="hljs-attr">config</span>=<span class="hljs-string">{wagmiConfig}</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">WagmiProvider</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">QueryClientProvider</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ScrollRestoration</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Scripts</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">App</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Outlet</span> /&gt;</span></span>;
}</code></pre><p>The last thing we want to do here is actually implement some blockchain interfacing so we can connect a wallet to the application and switch chains (if required). Lets change the <code>app/_index.tsx</code> completely so we can:</p><ol><li><p>Connect a wallet</p></li><li><p>Display the address connected</p></li><li><p>Switch chains</p></li></ol><p>As we’re changing the complete file, I’m adding the complete implementation below:</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// app/_index.tsx</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">MetaFunction</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@remix-run/node"</span>;
<span class="hljs-keyword">import</span> {
  useAccount,
  useConnect,
  useDisconnect,
  useSwitchChain,
  useChainId,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">meta</span>: <span class="hljs-title class_">MetaFunction</span> = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> [
    { <span class="hljs-attr">title</span>: <span class="hljs-string">"New Remix App"</span> },
    { <span class="hljs-attr">name</span>: <span class="hljs-string">"description"</span>, <span class="hljs-attr">content</span>: <span class="hljs-string">"Welcome to Remix!"</span> },
  ];
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Index</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> { address, isConnected } = <span class="hljs-title function_">useAccount</span>();
  <span class="hljs-keyword">const</span> chainId = <span class="hljs-title function_">useChainId</span>();
  <span class="hljs-keyword">const</span> { connectAsync, connectors } = <span class="hljs-title function_">useConnect</span>();
  <span class="hljs-keyword">const</span> { disconnect } = <span class="hljs-title function_">useDisconnect</span>();
  <span class="hljs-keyword">const</span> { chains, switchChainAsync } = <span class="hljs-title function_">useSwitchChain</span>();
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">fontFamily:</span> "<span class="hljs-attr">system-ui</span>, <span class="hljs-attr">sans-serif</span>", <span class="hljs-attr">lineHeight:</span> "<span class="hljs-attr">1.8</span>" }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> <span class="hljs-attr">16</span>, <span class="hljs-attr">border:</span> "<span class="hljs-attr">solid</span> <span class="hljs-attr">1px</span>" }}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Connected: {address ?? "no"}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> "<span class="hljs-attr">flex</span>", <span class="hljs-attr">gap:</span> <span class="hljs-attr">8</span> }}&gt;</span>
          Chain:
          {chains.map((chain) =&gt; (
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">key</span>=<span class="hljs-string">{chain.id}</span>
              <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> void switchChainAsync({ chainId: chain.id })}
              type="button"
            &gt;
              {chain.id === chainId &amp;&amp; "✅"} {chain.name} ({chain.id})
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          ))}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> "<span class="hljs-attr">flex</span>", <span class="hljs-attr">gap:</span> <span class="hljs-attr">8</span> }}&gt;</span>
          {isConnected ? (
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> disconnect()} type="button"&gt;
              Disconnect
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          ) : (
            connectors.map((connector) =&gt; (
              <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                <span class="hljs-attr">key</span>=<span class="hljs-string">{connector.id}</span>
                <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> void connectAsync({ connector })}
                type="button"
              &gt;
                {connector.name}
              <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            ))
          )}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}</code></pre><p>Time to see what we have so far. Make sure you have the Remix dev server (<code>npm run dev</code>) and anvil (<code>anvil</code>) running. Lets see if we can connect a browser extension wallet like MetaMask. Note that in the demo below I imported the first available anvil testing account into MetaMask. Whenever you boot up <code>anvil</code>, by default it provides you with 10 testing accounts and their corresponding private keys.</p><div data-type="youtube" videoid="sMg4n6f_MWU">
      <div class="youtube-player" data-id="sMg4n6f_MWU" style="background-image: url('https://i.ytimg.com/vi/sMg4n6f_MWU/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=sMg4n6f_MWU">
          <img src="https://paragraph.xyz/editor/youtube/play.png" class="play">
        </a>
      </div></div><div class="relative header-and-anchor"><h3 id="h-how-to-mock-a-wallet">How to mock a wallet</h3></div><p>Finally we’re getting to the meat. When attempting to conjure up a test for connecting a wallet we get stuck. There’s no easy way to add wallet functionality to Playwright. There are projects like Synpress and Dappeteer (deprecated at the time of writing) which wrap MetaMask. Personally I’m not a fan of this approach as it’s locking you into testing on a specific wallet. Any fundamental changes to MetaMask will require changes in your tests. Any fundamental breakages in MetaMask will break your tests. Icky.</p><p>The way we like to solve this is making use of Wagmi’s mock connector. The mock connector offers a fantastic low-level abstraction for connecting a wallet to a blockchain application. You can integrate it in your application to test wallet connections and interactions. It even allows you to test non-happy paths by passing error cases to its <code>features</code> option. This allows you to test errors when switching chains, connecting wallets, or signing messages or transactions.</p><p>We need to initialize the mock connector. There are several ways to do this. The simplest would be to add it to our list of <code>connectors</code> in <code>app/wagmi.ts</code>. The mock connector requires one argument with a list of accounts it’s able to use. Lets limit this to the first two test accounts provided by anvil:</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// app/wagmi.ts</span>
<span class="hljs-keyword">import</span> { createClient, http } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>;
<span class="hljs-keyword">import</span> { createConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> { injected, mock } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/connectors"</span>; 
<span class="hljs-keyword">import</span> { foundry, mainnet } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/chains"</span>;

<span class="hljs-keyword">const</span> chains = [mainnet, foundry] <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = <span class="hljs-title function_">createConfig</span>({
  chains,
  <span class="hljs-attr">client</span>: <span class="hljs-function">(<span class="hljs-params">{ chain }</span>) =&gt;</span> <span class="hljs-title function_">createClient</span>({ chain, <span class="hljs-attr">transport</span>: <span class="hljs-title function_">http</span>() }),
  <span class="hljs-attr">connectors</span>: [ 
    <span class="hljs-title function_">injected</span>(), 
    <span class="hljs-title function_">mock</span>({ 
      <span class="hljs-attr">accounts</span>: [ 
        <span class="hljs-string">"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"</span>, 
        <span class="hljs-string">"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"</span>, 
      ], 
    }), 
  ], 
  <span class="hljs-attr">ssr</span>: <span class="hljs-literal">true</span>,
});</code></pre><p>Yep. That adds it to our list of connectors:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/407b3ce53fe69fad4b976dd2bef26eb7.png" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>And it allows us to connect with it. Woo!</p><img src="https://storage.googleapis.com/papyrus_images/76e624a141786a0adacbdf4ba9d38712.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAVCAIAAACor3u9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABuElEQVR4nO2VTU8aURSG7x73YlJMh8mIzg0yDVYB00zAqKRdEOKiGxZsXPTHiJYPgfKhoqCZX2T8SMYUkwkCA43MnZnM3GPAdmdCNbjjybs4q/ucvItzEUIoFPq87PevCMHvO8lk8kcisZtI7IbDXzEOCsKX/wnGwX8RPB6B9azw/BrP+xBCaAbN+DF2LywtcJ/W17c3wrFQcCuwtomXVuecrHN2fOacrOuDZ961OO9aZD5iN4M5BnNuzDC8wzGLOI7bS+VOjs6LhWr+sJzL/kqnC8VCpVis/jzIp/YyY7OfyuZypfxhpZCvlEu1arl2Wjo+O6qlDzIsi1EgEFAUBQA0TXscMRg8atqAEE0jGiEE3kqn8yCKIop8i1xf3dy1mpe3t/dNRZZ/y3Kz3/9jGCYhRNd16/WYpgkAiqKMBJGILN+pap8QnVKglNo2fV6B0uFAX49lWQDQarX+CtrtNgAYhmFOCF3XnwXRaHQoUFUVAGzbhglhj57q9XrxeHwqeJFpRWOZVvTGiizLsieENbpF3W53KBBF8Z1OhaqqsVgM+Xy+RqMhSdLFRJEkqV6ve73e4a/5rjwBFds6JsK3x6EAAAAASUVORK5CYII=" nextheight="1056" nextwidth="1624" class="image-node embed"><p>Lets add a test where we connect the first account from the mock connector.</p><pre data-type="codeBlock" language="typescript"><code>

<span class="hljs-comment">// tests/smoke.spec.ts</span>
<span class="hljs-keyword">import</span> { expect, test } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

<span class="hljs-title function_">test</span>(<span class="hljs-string">"load the homepage"</span>, <span class="hljs-keyword">async</span> ({ page }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page).<span class="hljs-title function_">toHaveTitle</span>(<span class="hljs-string">"New Remix App"</span>);
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"connect wallet"</span>, <span class="hljs-keyword">async</span> ({ page }) =&gt; { 
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>); 
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">getByRole</span>(<span class="hljs-string">"button"</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">"Mock Connector"</span> }).<span class="hljs-title function_">click</span>(); 
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>( 
    page.<span class="hljs-title function_">getByText</span>(<span class="hljs-string">"Connected: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"</span>) 
  ).<span class="hljs-title function_">toBeVisible</span>(); 
}); </code></pre><p>When we run <code>npm test</code>, we should be green:</p><pre data-type="codeBlock"><code><span class="hljs-meta prompt_">&gt; </span><span class="bash"><span class="hljs-built_in">test</span></span>
<span class="hljs-meta prompt_">&gt; </span><span class="bash">playwright <span class="hljs-built_in">test</span></span>


Running 2 tests using 2 workers
  2 passed (1.1s)</code></pre><p>Yep.</p><p>The catch here is that the mock connector is initialized for everyone—regardless of environment. If we were to build and deploy this people would be able to connect with it, which could be highly confusing. We could add some logic which initializes the mock connector only on development environments by checking Vite’s <code>import.meta.env.DEV</code>. This is what we did for our v2 protocol application and our <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/dapp-e2e-example/blob/main/pages/_app.tsx#L19-L21">previous example repository</a>.</p><p>It works great, but there’s an opportunity here to make the mock connector useful for more than just testing.</p><div class="relative header-and-anchor"><h2 id="h-setting-up-for-failure">Setting up for failure</h2></div><p>You could say that the web is quite a volatile environment. Users have different systems, different configurations, different browsers, plugins, etc. I would argue web3 is an even more volatile environment. On top of different systems and configurations, there’s a wide variety of wallet providers and an even bigger variety of tokens held in these wallets.</p><p>One of the most powerful abilities you can give yourself and your team is being able to browse an application as any user. For <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">Endgame</a> we set up the mock connector in a way to allow us to do precisely this.</p><p>We expose an interface which swaps our production configuration with a configuration leveraging the mock connector. This allows us to initialize a mock connection from any arbitrary account. What’s more, through this interface we can pass options into the mock connector’s <code>features</code> configuration to test specific scenarios. Scenario’s like connection failure, user rejections, etc.</p><p>The goals here are:</p><ol><li><p>Set up a function accepting a private key/address and the mock connector’s <code>features</code> options.</p></li><li><p>Expose this to the browser in some way. The path of least resistance (and least intrusion into the UI) is slapping it onto <code>window</code>.</p></li><li><p>Set up a Playwright fixture which initializes mock connector configuration in test environments.</p></li></ol><div class="relative header-and-anchor"><h3 id="h-it-swaps-the-config">It swaps the config</h3></div><p>The trick here is having the Wagmi configuration live inside React too. Remember how we wrapped it in a <code>useState()</code> earlier? We can access and expose its setter.</p><pre data-type="codeBlock" language="typescript"><code>  <span class="hljs-keyword">const</span> [wagmiConfig] = <span class="hljs-title function_">useState</span>(config); </code></pre><p>What we need is a factory function which creates a new Wagmi configuration for us. The resulting configuration can be passed to a <code>setWagmiConfig()</code> state dispatcher. Because it’s part of React state, any update to this state makes parts of the application dependent on the configuration rerender automagically.</p><p>The configuration factory will need to mirror most of our general configuration. The key thing to set up is the account it will be initialized for. See the highlighted line.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// app/wagmi.ts</span>
<span class="hljs-keyword">import</span> { createClient, http, isAddress } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>; 
<span class="hljs-keyword">import</span> { privateKeyToAccount } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem/accounts"</span>; 
<span class="hljs-keyword">import</span> { createConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> { injected, mock, <span class="hljs-keyword">type</span> <span class="hljs-title class_">MockParameters</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/connectors"</span>; 
<span class="hljs-keyword">import</span> { foundry, mainnet } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/chains"</span>;

<span class="hljs-keyword">const</span> chains = [mainnet, foundry] <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = <span class="hljs-title function_">createConfig</span>({
  chains,
  <span class="hljs-attr">client</span>: <span class="hljs-function">(<span class="hljs-params">{ chain }</span>) =&gt;</span> <span class="hljs-title function_">createClient</span>({ chain, <span class="hljs-attr">transport</span>: <span class="hljs-title function_">http</span>() }),
  <span class="hljs-attr">connectors</span>: [<span class="hljs-title function_">injected</span>()],
  <span class="hljs-attr">ssr</span>: <span class="hljs-literal">true</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">createMockConfig</span>(<span class="hljs-params"> 
  addressOrPkey: <span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span>, 
  features?: MockParameters[<span class="hljs-string">"features"</span>] 
</span>) { 
  <span class="hljs-keyword">const</span> account = <span class="hljs-title function_">isAddress</span>(addressOrPkey) 
    ? addressOrPkey
    : <span class="hljs-title function_">privateKeyToAccount</span>(addressOrPkey); 
  <span class="hljs-keyword">const</span> address = <span class="hljs-keyword">typeof</span> account === <span class="hljs-string">"string"</span> ? account : account.<span class="hljs-property">address</span>; 
  <span class="hljs-keyword">return</span> <span class="hljs-title function_">createConfig</span>({ 
    <span class="hljs-attr">connectors</span>: [<span class="hljs-title function_">mock</span>({ <span class="hljs-attr">accounts</span>: [address], features })], 
    chains, 
    <span class="hljs-attr">client</span>: <span class="hljs-function">(<span class="hljs-params">{ chain }</span>) =&gt;</span> <span class="hljs-title function_">createClient</span>({ account, <span class="hljs-attr">transport</span>: <span class="hljs-title function_">http</span>(), chain }), 
    <span class="hljs-attr">ssr</span>: <span class="hljs-literal">true</span>, 
  }); 
} </code></pre><p>That’s the first part. The <code>createMockConfig()</code> function is set up to accept a private key or an address and the mock connector <code>features</code> configuration. By allowing any account address to be passed we can impersonate any account.</p><p>Next part is hooking this up to <code>WagmiProvider</code> initialization and slapping this helper onto the global <code>window</code> interface.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// app/root.tsx</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">QueryClientProvider</span>, <span class="hljs-title class_">QueryClient</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;
<span class="hljs-keyword">import</span> {
  <span class="hljs-title class_">Links</span>,
  <span class="hljs-title class_">Meta</span>,
  <span class="hljs-title class_">Outlet</span>,
  <span class="hljs-title class_">Scripts</span>,
  <span class="hljs-title class_">ScrollRestoration</span>,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@remix-run/react"</span>;
<span class="hljs-keyword">import</span> { useCallback, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>; 
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">WagmiProvider</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;

<span class="hljs-keyword">import</span> { config, createMockConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/wagmi"</span>; 

<span class="hljs-keyword">declare</span> <span class="hljs-variable language_">global</span> { 
  <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Window</span> { 
    <span class="hljs-attr">_setupAccount</span>: <span class="hljs-keyword">typeof</span> createMockConfig; 
  } 
} 

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Layout</span>(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) {
  <span class="hljs-keyword">const</span> [queryClient] = <span class="hljs-title function_">useState</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">QueryClient</span>());
  <span class="hljs-keyword">const</span> [wagmiConfig, setWagmiConfig] = <span class="hljs-title function_">useState</span>(config); 

  <span class="hljs-keyword">const</span> _setupAccount = <span class="hljs-title function_">useCallback</span>( 
    <span class="hljs-function">(<span class="hljs-params">...args: Parameters&lt;Window[<span class="hljs-string">"_setupAccount"</span>]&gt;</span>) =&gt;</span> { 
      <span class="hljs-keyword">const</span> config = <span class="hljs-title function_">createMockConfig</span>(...args); 
      <span class="hljs-title function_">setWagmiConfig</span>(config); 
    }, 
    [] 
  ); 

  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-variable language_">window</span> !== <span class="hljs-string">"undefined"</span>) <span class="hljs-variable language_">window</span>.<span class="hljs-property">_setupAccount</span> = _setupAccount; 

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charSet</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Meta</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Links</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">QueryClientProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{queryClient}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">WagmiProvider</span> <span class="hljs-attr">config</span>=<span class="hljs-string">{wagmiConfig}</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">WagmiProvider</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">QueryClientProvider</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ScrollRestoration</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Scripts</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">App</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Outlet</span> /&gt;</span></span>;
}</code></pre><p>Lets test it out.</p><div data-type="youtube" videoid="w1nYsH7YT3A">
      <div class="youtube-player" data-id="w1nYsH7YT3A" style="background-image: url('https://i.ytimg.com/vi/w1nYsH7YT3A/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=w1nYsH7YT3A">
          <img src="https://paragraph.xyz/editor/youtube/play.png" class="play">
        </a>
      </div></div><p>Cool. We can now browse our app pretending to be Vitalik.</p><div class="relative header-and-anchor"><h3 id="h-it-hooks-up-a-fixture">It hooks up a fixture</h3></div><p>Fixtures are one of the core concepts to grok in Playwright. They’re a very powerful tool, allowing you to abstract a lot of setup and application-specific initializations and interactions into a very simple interface.</p><blockquote><p>Test fixtures are used to establish the environment for each test, giving the test everything it needs and nothing else. […] With fixtures, you can group tests based on their meaning, instead of their common setup.</p></blockquote><p>When testing blockchain applications there are a few things which are <em>very</em> useful to have set up in fixtures:</p><ul><li><p>A Viem test client, extended with public- and wallet client actions.</p></li><li><p>A date mocking mechanism so we can pretend it’s earlier or later.</p></li><li><p>An account connection fixture so we don’t have to repeat this for each test.</p></li></ul><p>In order to set up a Playwright fixture you want to grab <code>test</code> from <code>@playwright/test</code> and use its <code>test.extend()</code> method. When you <code>export</code> the extended <code>test</code> the fixture you’ve set up will be made available. In your tests, instead of importing Playwright’s <code>test</code>, you would import your own.</p><p>Let’s set up our first fixture. This one will concern itself with abstracting connecting a wallet through our mock connector. I’ll provide some more useful fixtures in the next section.</p><p>Lets create two files: <code>tests/fixtures/wallet.ts</code> and <code>tests/fixtures/index.ts</code>. The former will house our application-specific wallet connection initialization. The latter we’ll use as an entrypoint which re-exports anything from <code>@playwright/test</code> plus our extended <code>test</code> function.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/fixtures/wallet.ts</span>
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> <span class="hljs-title class_">Page</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> <span class="hljs-title class_">Address</span>, <span class="hljs-keyword">type</span> <span class="hljs-title class_">Hex</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>;
<span class="hljs-keyword">import</span> { privateKeyToAccount } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem/accounts"</span>;
<span class="hljs-keyword">import</span> { test <span class="hljs-keyword">as</span> base } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> <span class="hljs-title class_">MockParameters</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/connectors"</span>;

<span class="hljs-comment">// It helps if we give accounts names, as it makes discerning</span>
<span class="hljs-comment">// different accounts more clear. It's easier to talk about</span>
<span class="hljs-comment">// Alice and Bob in tests than "wallet starting with 0xf39...".</span>
<span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> These private keys are provided by `anvil`.</span>
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">ACCOUNT_PKEYS</span> = {
  <span class="hljs-attr">alice</span>: <span class="hljs-string">"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"</span>,
  <span class="hljs-attr">bob</span>: <span class="hljs-string">"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"</span>,
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;

<span class="hljs-comment">// A fixture doesn't need to be a class. It could just as well</span>
<span class="hljs-comment">// be a POJO, function, scalar, etc. In our case a class keeps</span>
<span class="hljs-comment">// things nice and organized.</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WalletFixture</span> {
  address?: <span class="hljs-title class_">Address</span>;
  #<span class="hljs-attr">page</span>: <span class="hljs-title class_">Page</span>;

  <span class="hljs-comment">// The only thing we require for setting up a wallet connection</span>
  <span class="hljs-comment">// through the mock connector is the page to look up elements.</span>
  <span class="hljs-title function_">constructor</span>(<span class="hljs-params">{ page }: { page: Page }</span>) {
    <span class="hljs-variable language_">this</span>.#page = page;
  }

  <span class="hljs-comment">// You can make this as contrived or expansive as required for</span>
  <span class="hljs-comment">// your use-case. For Endgame, we actually derive accounts from</span>
  <span class="hljs-comment">// an `ANVIL_MNEMONIC` env with viem's `mnemonicToAccount()`.</span>
  <span class="hljs-keyword">async</span> <span class="hljs-title function_">connect</span>(<span class="hljs-params">
    name: keyof <span class="hljs-keyword">typeof</span> ACCOUNT_PKEYS,
    features?: MockParameters[<span class="hljs-string">'features'</span>]
  </span>) {
    <span class="hljs-keyword">const</span> pkey = <span class="hljs-variable constant_">ACCOUNT_PKEYS</span>[name];
    <span class="hljs-keyword">const</span> account = <span class="hljs-title function_">privateKeyToAccount</span>(pkey);

    <span class="hljs-variable language_">this</span>.<span class="hljs-property">address</span> = account.<span class="hljs-property">address</span>;

    <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.#<span class="hljs-title function_">setup</span>(pkey, features);
    <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.#<span class="hljs-title function_">login</span>();
  }

  <span class="hljs-comment">// Any application-specific rituals to get a wallet connected</span>
  <span class="hljs-comment">// can be put here. In our demo app we click a button.</span>
  <span class="hljs-keyword">async</span> #<span class="hljs-title function_">login</span>(<span class="hljs-params"></span>) {
    <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.#page.<span class="hljs-title function_">getByRole</span>(<span class="hljs-string">"button"</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">"Mock Connector"</span> }).<span class="hljs-title function_">click</span>();
  }

  <span class="hljs-comment">// Remember how we slapped our mock configuration helper onto</span>
  <span class="hljs-comment">// `window._setupAccount`? Here's how to use it in Playwright:</span>
  <span class="hljs-keyword">async</span> #<span class="hljs-title function_">setup</span>(<span class="hljs-params">...args: [Hex, MockParameters[<span class="hljs-string">"features"</span>]]</span>) {
    <span class="hljs-comment">// We let Playwright wait for the function to be non-nullable</span>
    <span class="hljs-comment">// on the `window` global. This ensures we can use it.</span>
    <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.#page.<span class="hljs-title function_">waitForFunction</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-variable language_">window</span>.<span class="hljs-property">_setupAccount</span>);
    <span class="hljs-comment">// `page.evaluate()` is a _very_ powerful method which allows</span>
    <span class="hljs-comment">// you to evaluate a script inside the browser page context.</span>
    <span class="hljs-comment">// In this example, we evaluate `window._setupAccount()`</span>
    <span class="hljs-comment">// with arguments passed from inside Playwright tests.</span>
    <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.#page.evaluate(<span class="hljs-function">(<span class="hljs-params">args</span>) =&gt;</span> <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">_setupAccount</span>(...args), args);
  }
}

<span class="hljs-comment">// Lastly, we export a `test` with the `WalletFixture` attached.</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> test = base.<span class="hljs-property">extend</span>&lt;{ <span class="hljs-attr">wallet</span>: <span class="hljs-title class_">WalletFixture</span> }&gt;({
  <span class="hljs-keyword">async</span> <span class="hljs-title function_">wallet</span>(<span class="hljs-params">{ page }, use</span>) {
    <span class="hljs-keyword">await</span> <span class="hljs-title function_">use</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">WalletFixture</span>({ page }));
  },
});</code></pre><p>The second file we’ll create is <code>tests/fixtures/index.ts</code> which will be a central module making fixtures and any other Playwright exports available to our tests:</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/fixtures/index.ts</span>
<span class="hljs-keyword">import</span> { mergeTests } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

<span class="hljs-keyword">import</span> { test <span class="hljs-keyword">as</span> walletTest } <span class="hljs-keyword">from</span> <span class="hljs-string">"./wallet"</span>;

<span class="hljs-comment">// Re-export anything from Playwright.</span>
<span class="hljs-keyword">export</span> * <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
<span class="hljs-comment">// Export our test function, extended with fixtures.</span>
<span class="hljs-comment">// It'll become useful when we have more fixtures to attach.</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> test = <span class="hljs-title function_">mergeTests</span>(walletTest);</code></pre><p>Now we can update our <code>tests/smoke.spec.ts</code> file to make use of this fixture:</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/smoke.spec.ts</span>
<span class="hljs-comment">// Notice how we're importing from './fixtures' now.</span>
<span class="hljs-keyword">import</span> { expect, test } <span class="hljs-keyword">from</span> <span class="hljs-string">"./fixtures"</span>; 
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">UserRejectedRequestError</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>; 

<span class="hljs-title function_">test</span>(<span class="hljs-string">"load the homepage"</span>, <span class="hljs-keyword">async</span> ({ page }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page).<span class="hljs-title function_">toHaveTitle</span>(<span class="hljs-string">"New Remix App"</span>);
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"connect wallet"</span>, <span class="hljs-keyword">async</span> ({ page, wallet }) =&gt; { 
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">await</span> wallet.<span class="hljs-title function_">connect</span>(<span class="hljs-string">"alice"</span>); 
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-string">`Connected: <span class="hljs-subst">${wallet.address}</span>`</span>)).<span class="hljs-title function_">toBeVisible</span>(); 
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"throw when wallet connect failure"</span>, <span class="hljs-keyword">async</span> ({ page, wallet }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>); 
  <span class="hljs-keyword">await</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">all</span>([ 
    page.<span class="hljs-title function_">waitForEvent</span>( 
      <span class="hljs-string">"pageerror"</span>, 
      <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> error.<span class="hljs-property">name</span> === <span class="hljs-string">"UserRejectedRequestError"</span>
    ), 
    wallet.<span class="hljs-title function_">connect</span>(<span class="hljs-string">"alice"</span>, { 
      <span class="hljs-attr">connectError</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserRejectedRequestError</span>( 
        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">"Connection failure."</span>) 
      ), 
    }), 
  ]); 
}); </code></pre><p>Make sure you have the Remix dev server and anvil node running. Then, <code>npm test</code> <span data-name="fingers_crossed" class="emoji" data-type="emoji">🤞</span></p><pre data-type="codeBlock"><code><span class="hljs-meta prompt_">&gt; </span><span class="bash"><span class="hljs-built_in">test</span></span>
<span class="hljs-meta prompt_">&gt; </span><span class="bash">playwright <span class="hljs-built_in">test</span></span>


Running 3 tests using 3 workers
  3 passed (1.5s)</code></pre><p>Amazing.</p><div class="relative header-and-anchor"><h3 id="h-it-travels-through-time">It travels through time</h3></div><p>With <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">Endgame</a> parts of our core functionality depend on time elapsed. Primarily, any rental has a rent duration which, well, dictates the amount of seconds a rental will be considered being actively rented. In order to test the temporal aspects of the protocol we need to travel forwards in time. This way, when a rental has “expired”, we can test functionality related to stopping rentals.</p><p>This kind of sucks, because we need to synchronize multiple time sources to reflect the same time. At the very least the chain’s block timestamp and the browser’s date.</p><p>In this bonus round we’ll implement a few Playwright fixtures which enables us to 1) mock the browser’s Date initializer and 2) synchronize our test RPC node so that the latest block timestamp reflects the same time.</p><p>First, lets add something to our UI so we can verify.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// app/_index.tsx</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">MetaFunction</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@remix-run/node"</span>;
<span class="hljs-keyword">import</span> {
  useAccount,
  useConnect,
  useDisconnect,
  useBlock, 
  useSwitchChain,
  useChainId,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">meta</span>: <span class="hljs-title class_">MetaFunction</span> = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> [
    { <span class="hljs-attr">title</span>: <span class="hljs-string">"New Remix App"</span> },
    { <span class="hljs-attr">name</span>: <span class="hljs-string">"description"</span>, <span class="hljs-attr">content</span>: <span class="hljs-string">"Welcome to Remix!"</span> },
  ];
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Index</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> { address, isConnected } = <span class="hljs-title function_">useAccount</span>();
  <span class="hljs-keyword">const</span> chainId = <span class="hljs-title function_">useChainId</span>();
  <span class="hljs-keyword">const</span> { connectAsync, connectors } = <span class="hljs-title function_">useConnect</span>();
  <span class="hljs-keyword">const</span> { disconnect } = <span class="hljs-title function_">useDisconnect</span>();
  <span class="hljs-keyword">const</span> { chains, switchChainAsync } = <span class="hljs-title function_">useSwitchChain</span>();

  <span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: block } = <span class="hljs-title function_">useBlock</span>(); 
  <span class="hljs-keyword">const</span> blockTime = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(<span class="hljs-title class_">Number</span>(block?.<span class="hljs-property">timestamp</span>) * <span class="hljs-number">1000</span>).<span class="hljs-title function_">toUTCString</span>(); 
  <span class="hljs-keyword">const</span> browserTime = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>().<span class="hljs-title function_">toUTCString</span>(); 

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">fontFamily:</span> "<span class="hljs-attr">system-ui</span>, <span class="hljs-attr">sans-serif</span>", <span class="hljs-attr">lineHeight:</span> "<span class="hljs-attr">1.8</span>" }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> <span class="hljs-attr">16</span>, <span class="hljs-attr">marginBottom:</span> <span class="hljs-attr">16</span>, <span class="hljs-attr">border:</span> "<span class="hljs-attr">solid</span> <span class="hljs-attr">1px</span>" }}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Block time: {blockTime}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Browser time: {browserTime}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> <span class="hljs-attr">16</span>, <span class="hljs-attr">border:</span> "<span class="hljs-attr">solid</span> <span class="hljs-attr">1px</span>" }}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Connected: {address ?? "no"}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> "<span class="hljs-attr">flex</span>", <span class="hljs-attr">gap:</span> <span class="hljs-attr">8</span> }}&gt;</span>
          Chain:
          {chains.map((chain) =&gt; (
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">key</span>=<span class="hljs-string">{chain.id}</span>
              <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> void switchChainAsync({ chainId: chain.id })}
              type="button"
            &gt;
              {chain.id === chainId &amp;&amp; "✅"} {chain.name} ({chain.id})
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          ))}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> "<span class="hljs-attr">flex</span>", <span class="hljs-attr">gap:</span> <span class="hljs-attr">8</span> }}&gt;</span>
          {isConnected ? (
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> disconnect()} type="button"&gt;
              Disconnect
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          ) : (
            connectors.map((connector) =&gt; (
              <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                <span class="hljs-attr">key</span>=<span class="hljs-string">{connector.id}</span>
                <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> void connectAsync({ connector })}
                type="button"
              &gt;
                {connector.name}
              <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            ))
          )}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}</code></pre><p>The result will look something like the screenshot below. When you boot up a new <code>anvil</code> instance these times will roughly correlate, but after a little while—refresh the page or force a rerender—these times will diverge more and more. Also note that <code>blockTime</code> relates to the currently selected chain. Switching between Ethereum and Foundry will reflect the latest block time on these respective chains.</p><img src="https://storage.googleapis.com/papyrus_images/6c86e9d0532679ac639d9b5cc84632b1.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAVCAIAAACor3u9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAEHUlEQVR4nN2V20/bZhyG3W0XSUhSmmnjNNKGxHZsJ45jOwlJCNBQQmihhgQGDOcAo+0SEQqt2AbTVK3VtAuoOAyK1qqbdjGYuOT/oZsIqmDk0DQjgcSZJxvWVZu6wqTd7NWjT/rd+PH3fvJnAAAAh8OCUxRN2P3+cDgcYfsEPB6GIFx/AcMIrRaDYUxYXyCMsFp9hLa6WvtOtba2FjpfDQIAAMgBOYVhWhiFQbPT6W1xM05Hm9PhRWC6skJTWVH3gop3NRfOIzBEQiABQ2YQNOsgEoLMEEhgiNWA2gyozWRyUoTTSjRQZidO1JdVVAAgCM7OLj56+P3S198sLqzMzS3PzS0tLz9cmF+anVmYnT1mZmZ+cXHF7x9Q1+pgyKjVYgjVhNDNOohA9LjBQJpMVsJkJQibATHTOGWjLBhqBCQSwO12x+PxQuEwk8k8fy6Q/TWbfpbmOI5/KUfjzMx9PYxSlBXHSZOJxFAjosdQxKCHURhGIUgPgnqNRqfVQXo9ZsBwlUolCDY3n2Sz2b2EkL29vVQqlUgkM5lMLpfP5fazYjKZDMdxc/MPmi92XGX6rnT0tnqYS61XWz0MTbtI2kVbGq22iwa83miy4+YGM9lAkg3Hgng8ns/n06l0KpVKp9OpVDKZTCYSiV/EFAoFjuMKh4c8zy8v3PVdJgd8lz7wu99nGru89YzXzrTZOlstjNfW5bV1t9d3tlIt3hYPw7S0uP4QbG2lcumftrd2dxNPn+5ub+/s7+eKxWJBzFFFpZJQ0eMHdyL94FTUeXvYMjli+/SGfWKInhiiY0HzeIi6GSInhi3jYWqwQzvYofN7NDU1ouDJ5s97ieSzTKZQKObzB/n8AceVeJ7/TUxJTLEomL5duRMdRCevuWIh63jYdvtDx60RR4SlPhogoyw9ytKxgCUyQAYYtI8x+9qxY8HOzg7P80dVcBxXKnHFv+Xw8IDn+e9Wvoixxo+vN94acXxyo2HymvNmyBplqcggFQtaRgN0LGQdZenhXryvE/O1QceCdDotliC89atSEiv64fHdSL9OfCgxFqTGhyyjLDEWoKIsORakxsL0eJgeZYlQF9zP4D2X0dMIOEHwaOWrkI8cG3bHhpqjQdf1AUe4xxry0QEfFfTTbDcV8FGhXhvbY/e341fcSI1KelIBJwru3fuyqkoNgggEIWp1XVV1bVWV+k+E8b3KmrpKjeECaMQou6qm5nSCqalp4XaRKyUSmVRaJpMpysqULyE/WmVSmUwqUyjPSqWn3MH09GcA8Ma5c28rleWvRaH4t4LycpVCcfYk/M8EnPCJvTIF8c4QD/mUgqamptPs4HMAeOuEh6xUlkskEsBkMq2urq6vr//4j1lbW9vY2Oju9p0586ZcrpDJ5K+jTCaTC7/M/zq/A22VlqGW5bwyAAAAAElFTkSuQmCC" nextheight="1056" nextwidth="1624" class="image-node embed"><p>It’s high time (ha!) to add some tooling to sync these up. We will create two more fixtures. One responsible for interfacing with <code>anvil</code> and another responsible for patching the <code>Date</code> constructor browser-side.</p><p>First up the <code>anvil</code> fixture:</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/fixtures/anvil.ts</span>
<span class="hljs-keyword">import</span> { test <span class="hljs-keyword">as</span> base } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
<span class="hljs-keyword">import</span> { createTestClient, http, publicActions, walletActions } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>;
<span class="hljs-keyword">import</span> { foundry } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem/chains"</span>;

<span class="hljs-keyword">const</span> anvil = <span class="hljs-title function_">createTestClient</span>({
  <span class="hljs-attr">chain</span>: foundry,
  <span class="hljs-attr">mode</span>: <span class="hljs-string">"anvil"</span>,
  <span class="hljs-attr">transport</span>: <span class="hljs-title function_">http</span>(),
})
  .<span class="hljs-title function_">extend</span>(publicActions)
  .<span class="hljs-title function_">extend</span>(walletActions)
  <span class="hljs-comment">// `client.extend()` is a very low-barrier utility, allowing you</span>
  <span class="hljs-comment">// to write custom methods for a viem client easily. It receives</span>
  <span class="hljs-comment">// a callback with the `client` as argument, returning an object</span>
  <span class="hljs-comment">// with any properties or methods you want to tack on.</span>
  <span class="hljs-comment">// We return an object with an `async syncDate(date)` method.</span>
  .<span class="hljs-title function_">extend</span>(<span class="hljs-function">(<span class="hljs-params">client</span>) =&gt;</span> ({
    <span class="hljs-keyword">async</span> <span class="hljs-title function_">syncDate</span>(<span class="hljs-params">date: <span class="hljs-built_in">Date</span></span>) {
      <span class="hljs-keyword">await</span> client.<span class="hljs-title function_">setNextBlockTimestamp</span>({
        <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> JavaScript Date.getTime returns milliseconds.</span>
        <span class="hljs-attr">timestamp</span>: <span class="hljs-title class_">BigInt</span>(<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">round</span>(date.<span class="hljs-title function_">getTime</span>() / <span class="hljs-number">1000</span>)),
      });
      <span class="hljs-comment">// We need to mine a block to commit its next timestamp.</span>
      <span class="hljs-keyword">return</span> client.<span class="hljs-title function_">mine</span>({ <span class="hljs-attr">blocks</span>: <span class="hljs-number">1</span> });
    },
  }));

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> test = base.<span class="hljs-property">extend</span>&lt;{ <span class="hljs-attr">anvil</span>: <span class="hljs-keyword">typeof</span> anvil }&gt;({
  <span class="hljs-keyword">async</span> <span class="hljs-title function_">anvil</span>(<span class="hljs-params">{}, use</span>) {
    <span class="hljs-keyword">await</span> <span class="hljs-title function_">use</span>(anvil);
  },
});</code></pre><p>Having an <code>anvil</code> fixture is generally useful as it allows you to query and interact with the test node inside of your tests.</p><p>The most pragmatic way to approach synchronizing date sources is to have the browser synchronize with block time. Flipping this approach—syncing a testnet node to the current system time—could be the better approach but aving block time be leading yields less and simpler code in the fixture.</p><p>We’re going to write a date fixture which will make use of our anvil fixture. In this date fixture we can use the <code>anvil</code> client to fetch the block time to synchronize the browser on. We will add two methods:</p><ol><li><p><code>addDays(n)</code> which will advance the current <code>date</code> <code>n</code> amount of days.</p></li><li><p><code>set(date)</code> which will attempt to synchronize the block timestamp and browser <code>Date</code> constructor with the passed <code>date</code>.</p></li></ol><p>Note the line which imports <code>test</code> from our <code>./anvil</code> fixture.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/fixtures/date.ts</span>
<span class="hljs-keyword">import</span> { test <span class="hljs-keyword">as</span> base } <span class="hljs-keyword">from</span> <span class="hljs-string">"./anvil"</span>; 

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> test = base.<span class="hljs-property">extend</span>&lt;{
  <span class="hljs-attr">date</span>: {
    <span class="hljs-attr">addDays</span>: <span class="hljs-function">(<span class="hljs-params">days: <span class="hljs-built_in">number</span></span>) =&gt;</span> <span class="hljs-title class_">Promise</span>&lt;<span class="hljs-title class_">Date</span>&gt;;
    <span class="hljs-attr">set</span>: <span class="hljs-function">(<span class="hljs-params">value: <span class="hljs-built_in">number</span> | <span class="hljs-built_in">string</span> | <span class="hljs-built_in">Date</span></span>) =&gt;</span> <span class="hljs-title class_">Promise</span>&lt;<span class="hljs-title class_">Date</span>&gt;;
  };
}&gt;({
  <span class="hljs-keyword">async</span> <span class="hljs-title function_">date</span>(<span class="hljs-params">{ anvil, page }, use</span>) {
    <span class="hljs-comment">// We want to keep around a cached reference to be used</span>
    <span class="hljs-comment">// by `addDays()` as opposed to getting the current date</span>
    <span class="hljs-comment">// anew each time we call `addDays()`.</span>
    <span class="hljs-keyword">let</span> date = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>();

    <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">addDays</span>(<span class="hljs-params">days: <span class="hljs-built_in">number</span></span>) {
      date = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(date.<span class="hljs-title function_">setDate</span>(date.<span class="hljs-title function_">getDate</span>() + days));
      <span class="hljs-keyword">await</span> <span class="hljs-title function_">set</span>(date);
    }

    <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">set</span>(<span class="hljs-params">value: <span class="hljs-built_in">number</span> | <span class="hljs-built_in">string</span> | <span class="hljs-built_in">Date</span></span>) {
      date = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(value);

      <span class="hljs-comment">// Attempt to synchronize our test node's block timestamp</span>
      <span class="hljs-comment">// with the provided `date`. We can't set dates in the past</span>
      <span class="hljs-comment">// or at the current time: it will throw a Timestamp error.</span>
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> anvil.<span class="hljs-title function_">syncDate</span>(date);
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(error);
      }

      <span class="hljs-comment">// Construct our patch to `window.Date`. Yes. We're</span>
      <span class="hljs-comment">// patching a global. Unfortunately this will mean React</span>
      <span class="hljs-comment">// will throw hydration warnings, but it will allow us</span>
      <span class="hljs-comment">// to test with mocked dates regardless.</span>
      <span class="hljs-keyword">const</span> dateInit = date.<span class="hljs-title function_">valueOf</span>();
      <span class="hljs-keyword">const</span> datePatch = <span class="hljs-string">`
      Date = class extends Date {
        constructor(...args) {
          super(...args.length ? args : [<span class="hljs-subst">${dateInit}</span>]);
        }

        now() {
          return super.now() + (<span class="hljs-subst">${dateInit}</span> - super.now());
        }
      };
      `</span>;

      <span class="hljs-comment">// Firstly we'll attach it as a `&lt;script&gt;` to the page</span>
      <span class="hljs-comment">// in Playwright. Whenever you `goto()` or `reload()` in</span>
      <span class="hljs-comment">// Playwright, the Date patch will be applied.</span>
      <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">addInitScript</span>(datePatch);
      <span class="hljs-comment">// Secondly, we evaluate the script directly within the</span>
      <span class="hljs-comment">// Playwright page context. Roughly this should allow us</span>
      <span class="hljs-comment">// to forgo any `goto()` or `reload()`—assuming any</span>
      <span class="hljs-comment">// component sensitive to `Date` is rerendered before</span>
      <span class="hljs-comment">// doing your test assertions.</span>
      <span class="hljs-keyword">await</span> page.evaluate(<span class="hljs-function">(<span class="hljs-params">datePatch</span>) =&gt;</span> {
        <span class="hljs-comment">// Look, mom! A valid use of `eval()`!</span>
        <span class="hljs-comment">// eslint-disable-next-line no-eval</span>
        <span class="hljs-built_in">eval</span>(datePatch);
      }, datePatch);

      <span class="hljs-keyword">return</span> date;
    }

    <span class="hljs-keyword">await</span> <span class="hljs-title function_">use</span>({ addDays, set });
  },
});</code></pre><p>One more thing…</p><p>We can update our <code>tests/fixtures/index.ts</code> file to chuck all our created fixtures onto a single <code>test()</code> function.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/fixtures/index.ts</span>
<span class="hljs-keyword">import</span> { mergeTests } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

<span class="hljs-keyword">import</span> { test <span class="hljs-keyword">as</span> anvilTest } <span class="hljs-keyword">from</span> <span class="hljs-string">"./anvil"</span>; 
<span class="hljs-keyword">import</span> { test <span class="hljs-keyword">as</span> dateTest } <span class="hljs-keyword">from</span> <span class="hljs-string">"./date"</span>; 
<span class="hljs-keyword">import</span> { test <span class="hljs-keyword">as</span> walletTest } <span class="hljs-keyword">from</span> <span class="hljs-string">"./wallet"</span>;

<span class="hljs-keyword">export</span> * <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> test = <span class="hljs-title function_">mergeTests</span>(anvilTest, dateTest, walletTest); </code></pre><p>We’re able to time travel now! Only forwards though.</p><p>Lets try it out and add a test.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/smoke.spec.ts</span>
<span class="hljs-keyword">import</span> { expect, test } <span class="hljs-keyword">from</span> <span class="hljs-string">"./fixtures"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">UserRejectedRequestError</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>;

<span class="hljs-title function_">test</span>(<span class="hljs-string">"load the homepage"</span>, <span class="hljs-keyword">async</span> ({ page }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page).<span class="hljs-title function_">toHaveTitle</span>(<span class="hljs-string">"New Remix App"</span>);
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"connect wallet"</span>, <span class="hljs-keyword">async</span> ({ page, wallet }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">await</span> wallet.<span class="hljs-title function_">connect</span>(<span class="hljs-string">"alice"</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-string">`Connected: <span class="hljs-subst">${wallet.address}</span>`</span>)).<span class="hljs-title function_">toBeVisible</span>();
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"throw when wallet connect failure"</span>, <span class="hljs-keyword">async</span> ({ page, wallet }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">const</span> [error] = <span class="hljs-keyword">await</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">all</span>([
    page.<span class="hljs-title function_">waitForEvent</span>(<span class="hljs-string">"pageerror"</span>),
    wallet.<span class="hljs-title function_">connect</span>(<span class="hljs-string">"alice"</span>, {
      <span class="hljs-attr">connectError</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserRejectedRequestError</span>(
        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">"Connection failure."</span>)
      ),
    }),
  ]);
  <span class="hljs-title function_">expect</span>(error.<span class="hljs-property">name</span>).<span class="hljs-title function_">toBe</span>(<span class="hljs-string">"UserRejectedRequestError"</span>);
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"synchronize times"</span>, <span class="hljs-keyword">async</span> ({ date, page }) =&gt; { 
  <span class="hljs-keyword">await</span> date.<span class="hljs-title function_">set</span>(<span class="hljs-string">"2069-04-20"</span>); 
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>); 
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">getByRole</span>(<span class="hljs-string">"button"</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/Foundry/</span> }).<span class="hljs-title function_">click</span>(); 
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-regexp">/Block time/</span>)).<span class="hljs-title function_">toHaveText</span>(<span class="hljs-regexp">/Sat, 20 Apr 2069/</span>); 
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-regexp">/Browser time/</span>)).<span class="hljs-title function_">toHaveText</span>(<span class="hljs-regexp">/Sat, 20 Apr 2069/</span>); 
  <span class="hljs-keyword">await</span> date.<span class="hljs-title function_">addDays</span>(<span class="hljs-number">69420</span>); 
  <span class="hljs-comment">// Because our demo app doesn't rerender after patching the</span>
  <span class="hljs-comment">// Date constructor we need a `goto()` or `reload()`.</span>
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">reload</span>(); 
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-regexp">/Block time/</span>)).<span class="hljs-title function_">toHaveText</span>(<span class="hljs-regexp">/Sun, 15 May 2259/</span>); 
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-regexp">/Browser time/</span>)).<span class="hljs-title function_">toHaveText</span>(<span class="hljs-regexp">/Sun, 15 May 2259/</span>); 
}); </code></pre><p>When you run <code>npm test</code> again, we should get 4 passed tests now. Running this command a second time however makes the <code>"synchronize times"</code> test fail. If you read the code in our date fixture carefully you may know why: we can’t set the time of our local testnet node to a date in the past. Only forwards.</p><p>The solution here is making a snapshot of our chain state before our tests fire and restore this snapshot after all tests are finished. We can leverage our <code>anvil</code> fixture for this.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// tests/smoke.spec.ts</span>
<span class="hljs-keyword">import</span> { expect, test } <span class="hljs-keyword">from</span> <span class="hljs-string">"./fixtures"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">UserRejectedRequestError</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>;

<span class="hljs-keyword">let</span> <span class="hljs-attr">id</span>: <span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span> | <span class="hljs-literal">undefined</span>; 

test.<span class="hljs-title function_">beforeAll</span>(<span class="hljs-keyword">async</span> ({ anvil }) =&gt; { 
  id = <span class="hljs-keyword">await</span> anvil.<span class="hljs-title function_">snapshot</span>(); 
}); 

test.<span class="hljs-title function_">afterAll</span>(<span class="hljs-keyword">async</span> ({ anvil }) =&gt; { 
  <span class="hljs-keyword">if</span> (!id) <span class="hljs-keyword">return</span>; 
  <span class="hljs-keyword">await</span> anvil.<span class="hljs-title function_">revert</span>({ id }); 
  id = <span class="hljs-literal">undefined</span>; 
}); 

<span class="hljs-title function_">test</span>(<span class="hljs-string">"load the homepage"</span>, <span class="hljs-keyword">async</span> ({ page }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page).<span class="hljs-title function_">toHaveTitle</span>(<span class="hljs-string">"New Remix App"</span>);
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"connect wallet"</span>, <span class="hljs-keyword">async</span> ({ page, wallet }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">await</span> wallet.<span class="hljs-title function_">connect</span>(<span class="hljs-string">"alice"</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-string">`Connected: <span class="hljs-subst">${wallet.address}</span>`</span>)).<span class="hljs-title function_">toBeVisible</span>();
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"throw when wallet connect failure"</span>, <span class="hljs-keyword">async</span> ({ page, wallet }) =&gt; {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">const</span> [error] = <span class="hljs-keyword">await</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">all</span>([
    page.<span class="hljs-title function_">waitForEvent</span>(<span class="hljs-string">"pageerror"</span>),
    wallet.<span class="hljs-title function_">connect</span>(<span class="hljs-string">"alice"</span>, {
      <span class="hljs-attr">connectError</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserRejectedRequestError</span>(
        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">"Connection failure."</span>)
      ),
    }),
  ]);
  <span class="hljs-title function_">expect</span>(error.<span class="hljs-property">name</span>).<span class="hljs-title function_">toBe</span>(<span class="hljs-string">"UserRejectedRequestError"</span>);
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">"synchronize times"</span>, <span class="hljs-keyword">async</span> ({ date, page }) =&gt; {
  <span class="hljs-keyword">await</span> date.<span class="hljs-title function_">set</span>(<span class="hljs-string">"2069-04-20"</span>);
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">"/"</span>);
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">getByRole</span>(<span class="hljs-string">"button"</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/Foundry/</span> }).<span class="hljs-title function_">click</span>();
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-regexp">/Block time/</span>)).<span class="hljs-title function_">toHaveText</span>(<span class="hljs-regexp">/Sat, 20 Apr 2069/</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-regexp">/Browser time/</span>)).<span class="hljs-title function_">toHaveText</span>(<span class="hljs-regexp">/Sat, 20 Apr 2069/</span>);
  <span class="hljs-keyword">await</span> date.<span class="hljs-title function_">addDays</span>(<span class="hljs-number">69420</span>);
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">reload</span>();
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-regexp">/Block time/</span>)).<span class="hljs-title function_">toHaveText</span>(<span class="hljs-regexp">/Sun, 15 May 2259/</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByText</span>(<span class="hljs-regexp">/Browser time/</span>)).<span class="hljs-title function_">toHaveText</span>(<span class="hljs-regexp">/Sun, 15 May 2259/</span>);
});</code></pre><p>Reboot your <code>anvil</code> node. Now you should be able to spam <code>npm test</code> as many times as you like—<em>assuming you set </em><code>fullyParallel: false</code> in you <code>playwright.config.ts</code>.</p><p>Time to add a cherry on top.</p><p>Maybe you caught the fact that the Playwright configuration has a <code>webServer</code> option. The cool thing about <code>webServer</code> is that it can be leveraged as a poor-man’s service orchestration. <code>webServer</code> can be turned into an array of <code>webServer</code> entries, meaning we can add our <code>anvil</code> initialization here as well.</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-comment">// playwright.config.ts</span>
  <span class="hljs-attr">webServer</span>: [ 
    { 
      <span class="hljs-attr">command</span>: <span class="hljs-string">"anvil"</span>, 
      <span class="hljs-attr">url</span>: <span class="hljs-string">"http://127.0.0.1:8545"</span>, 
      <span class="hljs-attr">reuseExistingServer</span>: !process.<span class="hljs-property">env</span>.<span class="hljs-property">CI</span>, 
    }, 
    { 
      <span class="hljs-attr">command</span>: <span class="hljs-string">"npm run start"</span>, 
      <span class="hljs-attr">url</span>: <span class="hljs-string">"http://127.0.0.1:3000"</span>, 
      <span class="hljs-attr">reuseExistingServer</span>: !process.<span class="hljs-property">env</span>.<span class="hljs-property">CI</span>, 
    }, 
  ], </code></pre><p>Check it out.</p><div data-type="youtube" videoid="I9kcIM3PRjI">
      <div class="youtube-player" data-id="I9kcIM3PRjI" style="background-image: url('https://i.ytimg.com/vi/I9kcIM3PRjI/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=I9kcIM3PRjI">
          <img src="https://paragraph.xyz/editor/youtube/play.png" class="play">
        </a>
      </div></div><div class="relative header-and-anchor"><h2 id="h-in-closing">In closing</h2></div><p>Woah. This was quite the trip. I hope this walkthrough is helpful for some of you. It was definitely fun to write. I also hope the peak behind the curtain on how we approached testing challenges for <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">Endgame</a> is as enjoyable to read as it is to share.</p><p>Be sure to check out <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/blockchain-web-app-e2e-testing-remix-wagmi">the example repository</a>, which should be straightforward to get up and running. I think it’s a decent web3 front end development springboard. The commits to the repository roughly corroborate to each of the sections in this walkthrough.</p><p>If you have any questions or remarks, be sure to <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/rombromz">hit me up on X</a>.</p><hr><p><strong>Note:</strong> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.rombrom.com/posts/testing-dapps-with-playwright-anvil-wagmi/">This article is co-published</a> on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://rombrom.com">rombrom.com</a>.</p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Rommert Zijlstra)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/8b4905c4e00214db2cac7aca888f242e.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Accumulating Dynamic Memory Arrays in Solidity with Endgame]]></title>
            <link>https://paragraph.com/@021/accumulating-dynamic-memory-arrays-in-solidity-with-endgame</link>
            <guid>qPXD9kZAi0PFLdMAgvYf</guid>
            <pubDate>Thu, 09 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[In my previous post, I discussed the high level architecture of the Endgame protocol, a non-custodial NFT rentals marketplace. Since Endgame adheres ...]]></description>
            <content:encoded><![CDATA[<p>In my previous post, I discussed the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@021/architecture-of-the-endgame-smart-contracts">high level architecture</a> of the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">Endgame protocol</a>, a non-custodial NFT rentals marketplace. Since Endgame adheres to the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/fullyallocated/Default">Default Framework specification</a>, the protocol's business logic is separated from its contract storage. By doing so, we afford the protocol a few key benefits, such as reducing nested contract dependence and providing a granular system for state-modifying code access.</p><p>However, this design choice comes with a key drawback that is not found in traditional monolithic contract designs: increased gas overhead due to an additional CALL opcode when making updates to storage.</p><p>In this post, i'll discuss how I found a gas-saving technique elsewhere in the protocol by accumulating elements in an array of structs with an unknown size. Let's dive in.</p><div class="relative header-and-anchor"><h3 id="h-the-problem">The Problem</h3></div><p>During the process of creating a rental, an <code>OrderParameters</code> struct is received from Seaport via the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Create.sol">Create Policy</a> and must be persisted in contract storage. This is so that the rental order can eventually be stopped. While the Create Policy processes this data, it must be able to know which items in a Seaport order are considered rentals and which are considered payments. </p><p>To demonstrate, I'll first show the <code>OrderParameters</code> struct in a Seaport order:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">OrderParameters</span> {
    <span class="hljs-keyword">address</span> offerer;
    <span class="hljs-keyword">address</span> zone;
    OfferItem[] offer;
    ConsiderationItem[] consideration;
    OrderType orderType;
    <span class="hljs-keyword">uint256</span> startTime;
    <span class="hljs-keyword">uint256</span> endTime;
    <span class="hljs-keyword">bytes32</span> zoneHash;
    <span class="hljs-keyword">uint256</span> salt;
    <span class="hljs-keyword">bytes32</span> conduitKey;
    <span class="hljs-keyword">uint256</span> totalOriginalConsiderationItems;
}</code></pre><p>An issue arises here because there exists an <code>OfferItem</code> array and a <code>ConsiderationItem</code> array which must be filtered into two separate arrays: one consisting of ERC721/ERC1155 rental items and one consisting of ERC20 payment items.</p><p>If we were using any other language, this would be easy. A single array could be filtered into two smaller arrays. Solidity, however, does not yet have support for dynamic memory arrays. So its not possible to use a struct array without first knowing how large the array will become.</p><p>To solve for this, I implemented the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/packages/Accumulator.sol">Accumulator Package</a> which can dynamically allocate structs in-memory using a custom intermediate array encoding. Then, when needed, it can be converted back into the standard Solidity array type since now we know the complete size of the data.</p><div class="relative header-and-anchor"><h3 id="h-accumulating-rentals">Accumulating Rentals</h3></div><p>As I stated before, the processing of creating a rental involves converting the <code>OfferItem</code> and <code>ConsiderationItem</code> arrays into buckets of rentals and payments. Before we get to this step, all the items in the Seaport order are first coalesced into one single <code>Item</code> array.</p><p>This process is done via <code>_convertToItems</code> in the Create Policy. You can view the source code <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Create.sol#L488">here</a>.</p><p>This conversion was used to make the data types easier to work with by providing a more general implementation that encapsulates both <code>OfferItem</code> and <code>ConsiderationItem</code> structs.</p><p>Here's how it looks:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">Item</span> {
    ItemType itemType;
    SettleTo settleTo;
    <span class="hljs-keyword">address</span> token;
    <span class="hljs-keyword">uint256</span> amount;
    <span class="hljs-keyword">uint256</span> identifier;
}</code></pre><p>With an <code>Item</code> array fully built up, the next step is to pick out the rental items (ERC721 and ERC1155 tokens) and build up a new array which can be sent over to the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/modules/Storage.sol">Storage Module</a> all in one batch.</p><p>To do this, the protocol invokes the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/packages/Accumulator.sol#L32">Accumulator Package</a>'s <code>_insert</code> function:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Create an accumulator which will hold all of the rental asset updates, </span>
<span class="hljs-comment">// consisting of IDs and the rented amount. From this point on, new memory </span>
<span class="hljs-comment">// cannot be safely allocated until the accumulator no longer needs to include elements.</span>
<span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> rentalAssetUpdates <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(<span class="hljs-number">0</span>);

<span class="hljs-comment">// Check if each item is a rental. If so, then generate the rental asset update.</span>
<span class="hljs-comment">// Memory will become safe again after this block.</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint256</span> i; i <span class="hljs-operator">&lt;</span> items.<span class="hljs-built_in">length</span>; <span class="hljs-operator">+</span><span class="hljs-operator">+</span>i) {
    <span class="hljs-keyword">if</span> (items[i].isRental()) {
        <span class="hljs-comment">// Insert the rental asset update into the dynamic array.</span>
        _insert(
            rentalAssetUpdates,
            items[i].toRentalId(payload.fulfillment.recipient),
            items[i].amount
        );
    }
}

<span class="hljs-comment">// ... arbitrary logic executes here</span>

<span class="hljs-comment">// Add rentals to storage before any other processing</span>
STORE.addRentals(orderHash, _convertToStatic(rentalAssetUpdates));</code></pre><p>We start by instantiating a strip of bytes in memory called <code>rentalAssetUpdates</code>. This will contain an append-only encoding of the rental assets. </p><p>Since the accumulator requires a custom memory encoding, it will be manually altering the Solidity free memory pointer. Because of this, no new memory can be allocated automatically by Solidity until the accumulator has finished writing to memory.</p><p>Once the accumulator has completed writing to memory, the Solidity free memory pointer can be incremented automatically as usual.</p><p>Finally, the <code>rentalAssetUpdate</code> bytes variables is directly converted into a standard Solidity struct array so that it can be sent to the Storage Module.</p><p>This is a lot to unpack, so let's first turn to how the <code>_insert</code> function is implemented.</p><div class="relative header-and-anchor"><h3 id="h-inserting-a-new-rental-in-memory">Inserting a New Rental in Memory</h3></div><p>At a high level, the <code>_insert</code> function will take a <code>bytes</code> value (the accumulation) and continually append elements to it until it is done accumulating data. This can be considered the intermediate representation of a Solidity <code>RentalAssetUpdate[]</code> array.</p><p>Each time <code>_insert</code> is called, it will append a new series of <code>bytes</code> data to the current accumulator in a specific encoding. The definition of the accumulator encoding can be summed up as follows:</p><pre data-type="codeBlock"><code>[<span class="hljs-symbol">0x00:0x20</span>]: <span class="hljs-link">Length of the intermediate representation bytes data, in bytes</span>
[<span class="hljs-symbol">0x20:0x40</span>]: <span class="hljs-link">Number of `RentalAssetUpdate` elements stored</span>
[<span class="hljs-symbol">0x40:0x60</span>]: <span class="hljs-link">`rentalId` of the first element</span>
[<span class="hljs-symbol">0x60:0x80</span>]: <span class="hljs-link">`amount` of the first element</span>
[<span class="hljs-symbol">0x80:0xa0</span>]: <span class="hljs-link">`rentalId` of the second element</span>
[<span class="hljs-symbol">0xa0:0xc0</span>]: <span class="hljs-link">`amount` of the second element</span>
[<span class="hljs-symbol">0xc0:0xe0</span>]: <span class="hljs-link">...</span></code></pre><p>We can start with the definition for <code>_insert</code>. It accepts the <code>bytes</code> accumulator which contains all the elements that been accumulated so far in the encoding mentioned above. It also accepts a <code>bytes32</code> and <code>uint256</code> which make up a <code>RentalAssetUpdate</code> struct.</p><pre data-type="codeBlock"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_insert</span>(<span class="hljs-params">
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> rentalAssets,
    <span class="hljs-keyword">bytes32</span> rentalId,
    <span class="hljs-keyword">uint256</span> rentalAssetAmount
</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> </span>{
    <span class="hljs-comment">// ... logic goes here</span>
}</code></pre><p>As an initial check, the function decides if <code>rentalAssets</code> has no length. If it's empty, then it is given an initial length of 32 bytes along with a single zeroed-out 32 byte element which is added to the <code>rentalAssets</code> value.</p><p>This code block will only execute when <code>rentalAssets</code> is empty so that it can be prepared to accumulate elements.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-keyword">assembly</span> {
    <span class="hljs-comment">// This is the first time inserting into this bytes data.</span>
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">eq</span>(<span class="hljs-built_in">mload</span>(rentalAssets), <span class="hljs-number">0</span>) {
        <span class="hljs-comment">// Create some space for the initial element length word.</span>
        <span class="hljs-built_in">mstore</span>(rentalAssets, <span class="hljs-number">0x20</span>)

        <span class="hljs-comment">// Zero out the number of elements.</span>
        <span class="hljs-built_in">mstore</span>(<span class="hljs-built_in">add</span>(<span class="hljs-number">0x20</span>, rentalAssets), <span class="hljs-number">0x00</span>)
    }
}</code></pre><p>At this point, the memory encoding of the <code>rentalAssets</code> would look like this:</p><pre data-type="codeBlock"><code>[<span class="hljs-symbol">0x00:0x20</span>]: <span class="hljs-link">0x0000000000000000000000000000000000000000000000000000000000000020</span>
[<span class="hljs-symbol">0x20:0x40</span>]: <span class="hljs-link">0x0000000000000000000000000000000000000000000000000000000000000000</span></code></pre><p>Next, we can turn to the logic that will execute each time <code>_insert</code> is called.</p><p>A <code>RentalAssetUpdate</code> struct has a total length of 64 bytes (or <code>0x40</code>). So, on a call to <code>_insert</code>, the total length of the <code>rentalAssets</code> bytes data must increase by <code>0x40</code>, along with incrementing the total number of elements being held within <code>rentalAssets</code>.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// ... logic previously</span>

<span class="hljs-comment">// Calculate the new size of the bytes data by adding</span>
<span class="hljs-comment">// the size of a `RentalAssetUpdate` struct.</span>
let newByteDataSize :<span class="hljs-operator">=</span> add(mload(rentalAssets), <span class="hljs-number">0x40</span>)

<span class="hljs-comment">// Get the pointer for where the element data begins.</span>
let rentalAssetElementPtr :<span class="hljs-operator">=</span> add(rentalAssets, <span class="hljs-number">0x20</span>)

<span class="hljs-comment">// Increase the number of rental elements by one.</span>
let elements :<span class="hljs-operator">=</span> add(mload(rentalAssetElementPtr), <span class="hljs-number">1</span>)</code></pre><p>Once we know how many elements will now be contained within <code>rentalAssets</code>, we need to calculate the exact offset for the new <code>RentalAssetUpdate</code> struct. By multiplying the total number of elements contained in the <code>bytes</code> data by <code>0x40</code>, we can calculate where to place the new element:</p><pre data-type="codeBlock"><code><span class="hljs-comment">// Calculate the position for the new rental ID.</span>
<span class="hljs-comment">// To do this, calculate the total length of the element portion, then</span>
<span class="hljs-comment">// subtract by the initial offset. In this case, the offset is the 32-byte</span>
<span class="hljs-comment">// word (0x20) which contains the length of the array.</span>
let newItemPosition := <span class="hljs-built_in">add</span>(
    rentalAssetElementPtr,
    <span class="hljs-built_in">sub</span>(<span class="hljs-built_in">mul</span>(elements, <span class="hljs-number">0</span>x40), <span class="hljs-number">0</span>x20)
)</code></pre><p>With all calculations completed, all that needs to be done is to store the newly calculated values into memory:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Store the new byte data size</span>
mstore(rentalAssets, newByteDataSize)

<span class="hljs-comment">// Store the new number of elements</span>
mstore(rentalAssetElementPtr, elements)

<span class="hljs-comment">// Store the rental ID</span>
mstore(newItemPosition, _rentalId)

<span class="hljs-comment">// Store the amount in the adjacent 32-byte word</span>
mstore(add(newItemPosition, <span class="hljs-number">0x20</span>), rentalAssetAmount)</code></pre><p>Finally, update the Solidity free memory pointer manually so that once we are done accumulating, we can hand memory management back to Solidity automatically:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Update the free memory pointer so that memory is safe</span>
<span class="hljs-comment">// once we stop doing dynamic memory array inserts</span>
mstore(<span class="hljs-number">0x40</span>, add(newItemPosition, <span class="hljs-number">0x40</span>))</code></pre><p>And that's it! There's nothing to return since all updates were made directly in memory. The next few sections will focus on how this intermediate encoding can be used to get the data back into an array encoding that Solidity can naturally parse.</p><div class="relative header-and-anchor"><h3 id="h-a-not-so-quick-aside-solidity-struct-arrays">A (Not So) Quick Aside: Solidity Struct Arrays</h3></div><p>Recall the function call to the Storage Module that contains all the rentals that were provided by the <code>Item</code> array: </p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Add rentals to storage before any other processing</span>
STORE.addRentals(orderHash, _convertToStatic(rentalAssetUpdates));</code></pre><p>We can now look at how the custom encoding of rentals can be converted back into a standard Solidity array with a type of <code>RentalAssetUpdate[]</code>. But first, it's worth going over how standard Solidity arrays are stored in memory. </p><p>Solidity memory allocation for arrays is not well-documented since it follows a different encoding than what is found in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.soliditylang.org/en/latest/abi-spec.html">Contract ABI Specification</a>. So for the most part, we'll be flying on our own here.</p><p>Lets first take a look at the default memory layout before any memory has been allocated so that we can get our bearings. We can do this using <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/chisel/">Chisel</a>, with its <code>!memdump</code> and <code>!stackdump</code> commands. By running <code>!memdump</code>, we can see the initialized memory that all Solidity function calls begin with:</p><pre data-type="codeBlock"><code>[<span class="hljs-symbol">0x00:0x20</span>]: <span class="hljs-link">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-symbol">0x20:0x40</span>]: <span class="hljs-link">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-symbol">0x40:0x60</span>]: <span class="hljs-link">0x0000000000000000000000000000000000000000000000000000000000000080</span>
[<span class="hljs-symbol">0x60:0x80</span>]: <span class="hljs-link">0x0000000000000000000000000000000000000000000000000000000000000000</span></code></pre><p>According to the memory layout <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html">docs</a>, the slot definitions are as follows:</p><ul><li><p><code>0x00 - 0x3f</code> (64 bytes): scratch space for hashing methods</p></li><li><p><code>0x40 - 0x5f</code> (32 bytes): currently allocated memory size (aka. free memory pointer)</p></li><li><p><code>0x60 - 0x7f</code> (32 bytes): zero slot - this is used as an initial pointer value for memory arrays that have not been initialized with a size yet</p></li></ul><p>We can see how the zero slot is utilized once we declare an empty <code>RentalAssetUpdate</code> array in Chisel:</p><pre data-type="codeBlock" language="solidity"><code>RentalAssetUpdate[] <span class="hljs-keyword">memory</span> assets;</code></pre><p>Now, if we were to peek at our stack with <code>!stackdump</code>, we would see this element on top: </p><pre data-type="codeBlock"><code>[<span class="hljs-symbol">0</span>]: <span class="hljs-link">0x0000000000000000000000000000000000000000000000000000000000000060</span>
[<span class="hljs-symbol">1</span>]: <span class="hljs-link">...</span>
[<span class="hljs-symbol">2</span>]: <span class="hljs-link">...</span></code></pre><p>This indicates that the <code>assets</code> variable is pointing to address <code>0x60</code> in memory, which is the zero slot. To see how this changes, we'll instantiate our array next:</p><pre data-type="codeBlock" language="solidity"><code>assets <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> RentalAssetUpdate[](<span class="hljs-number">2</span>);</code></pre><p>First, let's take a look at the stack to see if anything is different: </p><pre data-type="codeBlock"><code>[<span class="hljs-symbol">0</span>]: <span class="hljs-link">0x0000000000000000000000000000000000000000000000000000000000000080</span>
[<span class="hljs-symbol">1</span>]: <span class="hljs-link">...</span>
[<span class="hljs-symbol">2</span>]: <span class="hljs-link">...</span></code></pre><p>Interestingly, the element pointing to the <code>assets</code> variable has now changed from <code>0x60</code> to <code>0x80</code>. Let's <code>!memdump</code> the memory so we can see what's going on:</p><pre data-type="codeBlock" language="yaml"><code>[<span class="hljs-number">0x00</span><span class="hljs-string">:0x20</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x20</span><span class="hljs-string">:0x40</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x40</span><span class="hljs-string">:0x60</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000160</span>
[<span class="hljs-number">0x60</span><span class="hljs-string">:0x80</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
<span class="hljs-string">---------------------------------------------------------------------------------</span>
[<span class="hljs-number">0x80</span><span class="hljs-string">:0xa0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000002</span>
[<span class="hljs-number">0xa0</span><span class="hljs-string">:0xc0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x00000000000000000000000000000000000000000000000000000000000000e0</span>
[<span class="hljs-number">0xc0</span><span class="hljs-string">:0xe0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000120</span>
<span class="hljs-string">---------------------------------------------------------------------------------</span>
[<span class="hljs-number">0xe0</span><span class="hljs-string">:0x100</span>]<span class="hljs-string">:</span>  <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x100</span><span class="hljs-string">:0x120</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x120</span><span class="hljs-string">:0x140</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x140</span><span class="hljs-string">:0x160</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span></code></pre><p>We can see that a whole bunch of things have changed. I've added in lines so that its easier to view the clear segments of the memory.</p><p>In location <code>0x40</code>, you'll notice that the free memory pointer was updated to <code>0x160</code>. This intuitively makes sense because we have allocated memory so far up to address <code>0x160</code>.</p><p>Lets turn to location <code>0x80</code> which is where the <code>assets</code> variable is now pointing to. You'll see that the value stored there is <code>0x02</code>. This value defines the length of the array.</p><p>After the length definition, you'll see two pointers located at <code>0xa0</code> and <code>0xc0</code> which contain the values <code>0xe0</code> and <code>0x120</code>, respectively. These are considered the <code>head</code> values of the array, and are locations that contain pointers in memory to the actual location of where the data is stored. Recall that each <code>RentalAssetUpdate</code> contains a <code>bytes32</code> and a <code>uint256</code>, totaling 64 bytes. We can do the math here and see that the difference between <code>0x120</code> and <code>0xe0</code> is <code>0x40</code>, or 64 bytes.</p><p>Looking at location <code>0xe0</code>, we can see that it contains an empty word, followed by another empty word. This is the same case at location <code>0x120</code>. These are spots in memory which have been reserved but not allocated to just yet. These are considered the <code>tail</code> values in the array and contain the actual data of the elements being stored in the array. We'll see how those get populated once we add some elements to the <code>assets</code> array. Lets do that now:</p><pre data-type="codeBlock" language="solidity"><code>assets[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> RentalAssetUpdate(<span class="hljs-keyword">bytes32</span>(<span class="hljs-keyword">uint256</span>(<span class="hljs-number">5</span>)), <span class="hljs-keyword">uint256</span>(<span class="hljs-number">6</span>));</code></pre><p>We can now <code>!memdump</code> the memory again to see what changed:</p><pre data-type="codeBlock" language="yaml"><code>[<span class="hljs-number">0x00</span><span class="hljs-string">:0x20</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x20</span><span class="hljs-string">:0x40</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x40</span><span class="hljs-string">:0x60</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x00000000000000000000000000000000000000000000000000000000000001a0</span>
[<span class="hljs-number">0x60</span><span class="hljs-string">:0x80</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
<span class="hljs-string">---------------------------------------------------------------------------------</span>
[<span class="hljs-number">0x80</span><span class="hljs-string">:0xa0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000002</span>
[<span class="hljs-number">0xa0</span><span class="hljs-string">:0xc0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000160</span>
[<span class="hljs-number">0xc0</span><span class="hljs-string">:0xe0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000120</span>
<span class="hljs-string">---------------------------------------------------------------------------------</span>
[<span class="hljs-number">0xe0</span><span class="hljs-string">:0x100</span>]<span class="hljs-string">:</span>  <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x100</span><span class="hljs-string">:0x120</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x120</span><span class="hljs-string">:0x140</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x140</span><span class="hljs-string">:0x160</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
<span class="hljs-string">---------------------------------------------------------------------------------</span>
[<span class="hljs-number">0x160</span><span class="hljs-string">:0x180</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000005</span>
[<span class="hljs-number">0x180</span><span class="hljs-string">:0x1a0</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000006</span></code></pre><p>This is a bit unexpected. The zero values located at memory regions <code>0xe0</code> and <code>0x120</code> were not updated. They were completely left alone. Instead a <code>RentalAssetUpdate</code> element was added starting at location <code>0x160</code>. We can verify that this is indeed what happened because we can look to the head of the array and see that in location <code>0xa0</code>, it switched from holding a value of <code>0xe0</code> to now have a value of <code>0x160</code>. It's also worth noting that since the second element in the array is still unallocated, location <code>0xc0</code> remains pointing to the zero value located at <code>0x120</code>.</p><p>Let's try adding one more element to the array to see if a pattern emerges:</p><pre data-type="codeBlock"><code>assets<span class="hljs-selector-attr">[1]</span> = <span class="hljs-built_in">RentalAssetUpdate</span>(bytes32(uint256(<span class="hljs-number">7</span>)), <span class="hljs-built_in">uint256</span>(<span class="hljs-number">8</span>));</code></pre><p>Now, dumping the memory we get:</p><pre data-type="codeBlock" language="yaml"><code>[<span class="hljs-number">0x00</span><span class="hljs-string">:0x20</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x20</span><span class="hljs-string">:0x40</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x40</span><span class="hljs-string">:0x60</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x00000000000000000000000000000000000000000000000000000000000001e0</span>
[<span class="hljs-number">0x60</span><span class="hljs-string">:0x80</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
<span class="hljs-string">---------------------------------------------------------------------------------</span>
[<span class="hljs-number">0x80</span><span class="hljs-string">:0xa0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000002</span>
[<span class="hljs-number">0xa0</span><span class="hljs-string">:0xc0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000160</span>
[<span class="hljs-number">0xc0</span><span class="hljs-string">:0xe0</span>]<span class="hljs-string">:</span>   <span class="hljs-number">0x00000000000000000000000000000000000000000000000000000000000001a0</span>
<span class="hljs-string">---------------------------------------------------------------------------------</span>
[<span class="hljs-number">0xe0</span><span class="hljs-string">:0x100</span>]<span class="hljs-string">:</span>  <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x100</span><span class="hljs-string">:0x120</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x120</span><span class="hljs-string">:0x140</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
[<span class="hljs-number">0x140</span><span class="hljs-string">:0x160</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
<span class="hljs-string">---------------------------------------------------------------------------------</span>
[<span class="hljs-number">0x160</span><span class="hljs-string">:0x180</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000005</span>
[<span class="hljs-number">0x180</span><span class="hljs-string">:0x1a0</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000006</span>
[<span class="hljs-number">0x1a0</span><span class="hljs-string">:0x1c0</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000007</span>
[<span class="hljs-number">0x1c0</span><span class="hljs-string">:0x1e0</span>]<span class="hljs-string">:</span> <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000008</span></code></pre><p>Interesting. This time it allocated the new element exactly where it should have gone, which is directly after element 0 in the array. Its also worth mentioning that the <code>head</code> pointer for element 1, located at <code>0xc0</code>, is now pointing away from the zero value stored at <code>0x120</code>, and toward the actual data that we stored, which starts at <code>0x1a0</code>.</p><p>So what are these 4 consecutive zero words doing in memory from <code>0xe0</code> to <code>0x140</code>? These regions seem to be used as scratch space for each element for some operations in Solidity. The most clarity that be found is located in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html">Layout in Memory</a> section of the Solidity documentation that reads:</p><blockquote><p>There are some operations in Solidity that need a temporary memory area larger than 64 bytes and therefore will not fit into the scratch space. They will be placed where the free memory points to, but given their short lifetime, the pointer is not updated. The memory may or may not be zeroed out. Because of this, one should not expect the free memory to point to zeroed out memory.</p></blockquote><p>Because of situations like this where definitions of the Solidity memory layout are a bit vague, I felt that it was safer to design an intermediate encoding of an array and then manually convert it back to a Solidity array rather than attempting to mimic Solidity's process of memory management.</p><p>We can now turn to how I implemented this conversion from the intermediate encoding into a standard Solidity memory array.</p><div class="relative header-and-anchor"><h3 id="h-getting-out-of-the-intermediate-representation">Getting Out of the Intermediate Representation</h3></div><p>Now armed with the knowledge of how Solidity Struct arrays are encoded, it should be a breeze to cover how we convert the custom intermediate representation back into a Solidity array.</p><p>We'll start with the definition of <code>_convertToStatic</code>. It accepts only the intermediate encoded <code>rentalAssetUpdates</code> parameter and returns a native Solidity <code>RentalAssetUpdate[]</code> array.</p><pre data-type="codeBlock"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_convertToStatic</span>(<span class="hljs-params">
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> rentalAssetUpdates
</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">RentalAssetUpdate[] <span class="hljs-keyword">memory</span> updates</span>) </span>{
    <span class="hljs-comment">// ... logic continues on </span>
}</code></pre><p>For this implementation, there is a hybrid Solidity/Yul approach to extract the values. The return values are defined in solidity, but the extraction of the elements are defined using Yul.</p><p>The conversion begins by getting pointers to the start of the encoded elements and the total number of elements in the encoding. Then, the Solidity <code>RentalAssetUpdate[]</code> array can be instantiated with the exact number of elements specified in the encoding:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Pointer to the rental asset update data.</span>
<span class="hljs-keyword">bytes32</span> rentalAssetUpdatePointer;

<span class="hljs-comment">// Load the length of the rental asset update items.</span>
<span class="hljs-keyword">uint256</span> rentalAssetUpdateLength;
<span class="hljs-keyword">assembly</span> {
    <span class="hljs-comment">// Get a pointer to the number of elements in the bytes data.</span>
    <span class="hljs-comment">// With the 0x20 offset, we would be loading the length of the entire</span>
    <span class="hljs-comment">// byte string, but we want the element length which starts one</span>
    <span class="hljs-comment">// word to the right.</span>
    rentalAssetUpdatePointer <span class="hljs-operator">:=</span> <span class="hljs-built_in">add</span>(<span class="hljs-number">0x20</span>, rentalAssetUpdates)

    <span class="hljs-comment">// Load the number of elements.</span>
    rentalAssetUpdateLength <span class="hljs-operator">:=</span> <span class="hljs-built_in">mload</span>(rentalAssetUpdatePointer)
}

<span class="hljs-comment">// Instantiate the update array.</span>
updates <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> RentalAssetUpdate[](rentalAssetUpdateLength);</code></pre><p>From here, each element is looped through by calculating its offset in memory since we know that each <code>RentalAssetUpdate</code> struct is exactly <code>0x40</code> bytes long.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Iterate through each item in the byte data, and add it as</span>
<span class="hljs-comment">// an entry to the array.</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint256</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&lt;</span> rentalAssetUpdateLength; <span class="hljs-operator">+</span><span class="hljs-operator">+</span>i) {
    <span class="hljs-comment">// Define the placeholders.</span>
    RentalId rentalId;
    <span class="hljs-keyword">uint256</span> amount;

    <span class="hljs-comment">// Extract the current element from the byte data.</span>
    <span class="hljs-keyword">assembly</span> {
        <span class="hljs-comment">// Determine element offset by multiplying the length of a</span>
        <span class="hljs-comment">// RentalAssetUpdate struct (0x40) by the current index, then</span>
        <span class="hljs-comment">// add a word to make sure the next word is accessed because the</span>
        <span class="hljs-comment">// offset defaults to being set to the length pointer.</span>
        <span class="hljs-keyword">let</span> currentElementOffset <span class="hljs-operator">:=</span> <span class="hljs-built_in">add</span>(<span class="hljs-number">0x20</span>, <span class="hljs-built_in">mul</span>(i, <span class="hljs-number">0x40</span>))

        <span class="hljs-comment">// Load the rental ID starting at the data pointer.</span>
        rentalId <span class="hljs-operator">:=</span> <span class="hljs-built_in">mload</span>(<span class="hljs-built_in">add</span>(rentalAssetUpdatePointer, currentElementOffset))

        <span class="hljs-comment">// Load the amount at the data pointer adjacent to it.</span>
        amount <span class="hljs-operator">:=</span> <span class="hljs-built_in">mload</span>(
            <span class="hljs-built_in">add</span>(<span class="hljs-number">0x20</span>, <span class="hljs-built_in">add</span>(rentalAssetUpdatePointer, currentElementOffset))
        )
    }

    <span class="hljs-comment">// Set the items</span>
    updates[i] <span class="hljs-operator">=</span> RentalAssetUpdate(rentalId, amount);
}</code></pre><p>Once the loop completes, the <code>updates</code> array can be returned and used as normal in other areas of the Solidity code.</p><div class="relative header-and-anchor"><h3 id="h-conclusion">Conclusion</h3></div><p>At its core, the Accumulator is a technique that takes a number of elements in Solidity and can append them to an array of unknown sizing. This package fills a wide gap in the Solidity language since there still doesn't exist a way to work with dynamically allocated arrays in memory.</p><p>I would not have been able to create this implementation without a heavy reliance on one of my favorite tools, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/chisel/">Chisel</a>. I encourage you to use Chisel with the examples in this post to see how Solidity allocates array memory for yourself.</p><p>You can view the implementation of the Accumulator Package <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/packages/Accumulator.sol#L32">here</a>, and if you have any further questions on how the accumulator works, feel free to reach out to myself at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="mailto:alec@021.gg">alec@021.gg</a> or on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/alecdifederico">x.com</a>.  </p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Zero To One)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/b128a45aecd429b24fcc484bea2dcbe4.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Architecture of the Endgame Smart Contracts]]></title>
            <link>https://paragraph.com/@021/architecture-of-the-endgame-smart-contracts</link>
            <guid>OOUJLta3271E8ZQUUpaZ</guid>
            <pubDate>Wed, 01 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[In my previous posts, I have been writing some technical deep dives into core aspects of the Endgame protocol, such as hook implementations and renta...]]></description>
            <content:encoded><![CDATA[<p>In my previous posts, I have been writing some technical deep dives into core aspects of the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">Endgame</a> protocol, such as <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@021/revenue-sharing-how-to-supercharge-nft-rentals">hook implementations</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@021/anatomy-of-a-rental-transaction">rental transactions</a>. Now, I want to take a step back and give an overview of the novel smart contract architecture that the whole protocol is based on. </p><div class="relative header-and-anchor"><h3 id="h-motivation">Motivation</h3></div><p>There is no one design that works best for a smart contract protocol. For Endgame, I had a few considerations in mind before any actual code was written. </p><p>Knowing that the protocol would have to integrate with both <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ProjectOpenSea/seaport">Seaport</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account">Safe</a> smart contracts, I wanted a design that would reduce the surface area for bugs as much as possible, given the difficulty of testing a protocol that integrates with another. For me, this meant a few things: an airtight admin privilege design, well-divided contracts each with as few responsibilities as possible, and writing well-commented code.</p><p>Additionally, as with any protocol, it was hard to tell how Endgame was going to evolve over time. Given that smart contracts can't be afforded the rapid release cycles than an API or a front-end can, it was important that the protocol architecture be flexible and modular. </p><p>Finally, a given requirement was that the protocol would be deployed to ETH mainnet. This meant the protocol would have to be considerate of gas consumption so as to not price-out users from interacting with the protocol due to excessive gas costs.</p><div class="relative header-and-anchor"><h3 id="h-the-framework">The Framework</h3></div><p>I found everything I was looking for, and then some, in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/fullyallocated/Default">Default Framework</a> design. This protocol architecture sets itself apart by shifting the focus of a protocol away from organizing contracts around processes, and toward organizing contracts around data models.</p><p>As stated in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/fullyallocated/Default">Default Framework</a> repository, the goal is to take a protocol from looking something like this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a7e5552efa0257d0fab321e4041e2bb1.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAUCAIAAABj86gYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGKklEQVR4nI1V728a9xn/Su2b7eUmTetebFIVaS/6YtKyTdOkaNuLad1UVZPWtVM7qWkVd3MkoqWpW5yyFY04NHE8kuLa6y6Bxm6we6lxhm1kMhtjYnMUCgFiDgzcnX0Hx6+7L3CY48fBfScg2fqim/b5A57P5/k8z/N5AHoERVHQ/4dOq40Q+gn43sWT+la3K8JqVWqIsAIhrNSkqtQQqrVWt7u6eY8IRoCiKLIsD6srj/CFdZWe0mw3EULykYwQehJ8ZfSHIwihRr0htzsNWYYQ1utHDbnZ7nQQQviqa8NLgFKpxPM8SZJ6vT4QCPA8zzDM/+6m02ohhL4NwJlfPq2oapbjSo9QqdUghPl8QVFV17aPCEeA3W73er2yLHu93lQqdcByq2uuVCpFkmRf7ADtTrvX7U3vXH/u49eGBA1F+ToAp350Qup0WIaBEDZkmaIPxk+eXltyFCtVRVGmrR/d9e4CHMctFovT6XQ4HAghvlz6i/lqgiQ929uD5nIsy+aL+Vaj9eLFlx9/4zsIobIo+Hd2mMPDNEVlOa5YKrEsS3G5XDYPAJgzzSiq6t0hGDaHEAI2m81oNGq1Wp1OdxvH9xn2mVdGEVL/bUi705GafdNf//v4Dwy/QAhFQiGpWmWZPnqq2lWUGxg2/rZubXn5GwBYLl9N0lQ0GvMQwVgiBSwWy9jYmEajwTBMhJCjmXc0526vuiamMbF6lBcqFEXX63VZkn9z8SWgP15mizVRECHEcdxut5PR2LpjZcvl8m9t4AuLAADNuCFDUUQwfP7K9BXMCubm5gwGg06nczqdCKFqpWqb/yhFMeG9RDxOHjAHm/cjwHQ1WyjgFpvpT5djkXBNkiYnJw0Gg9FofO3UqUQ0elb751/9bsSztQ0AMFoWg6EwvvyPvFB5aJFer7fZbENDSoXiu/qJntq3KBKN0Pv78y4XOPnqg0j0PkHsej0NWc4dHLz/nlmn0w043qX3YuDY9wEAkUhoy3lhfWs1lqASyeSwYH/Ier1+KB8hJFb4S9NnFaWLECqWSmmK3g2FwLeecDkc8WCQjMZ2XHeDbs/se+aZAWy2BaFcPqpLnaa8veUBXz32qnYCIdRT1aHKvkVjY2NarbYhNxBCFORfuW4IhB7ccriiiTTHcbuxGDj2zctnX79rW/Stu8jAZzCfv2m1mkym92dmrmNYKk4m9uK5Q5YgCADAuQuXhqvxsIO5uTmNRqPVajEMk6o1tsi/eeWdzXv+GdsnaYajaeZTkgRfBm89/1vv0h2/6y61F89lqA9v3DAN8Kj1vthWs+HCzyTj98TqkSTV/mORTqebmZlhWRYhRHPcr/8w+vl02iAI8LUv3SeIOoStZpNLZxq12q35+cnJSZPJtLiwWBNh7pCtiZBKp89pfhYJbQlQSiSTlZrU7nT6l2w0Gs1mM4Zhsx/8berClXPPatY9O5aPl4c0ZDp95tLFer3+5u//+NKzL8qynM9mb2CY2dwfw9S16WAo4nF7vLv+eXxZe/qZWWw2msjsM+zZCZPtzmr/DjQajcViqdfrgw1h5qcmrlltp3UTbUVhczzLstVKtdXt/vj4Tx8Dj5VFgaZps9lsMpkuT01ZrVYoCLFYzL6ytrKy/vOnTiwtLGdYNrGfjqcZAcL+DEZHR8Ph8FBvZm9v/IXnAzHS6fb2VFWEfeTzhZ6qzt9cGHl5NMtx9Xrd6XRarda52Q923ZupRNLhWLu9dIcIhv1+4jCbZbIFCOHDGdjtdoPB4PP5htHG5vhYIvnf0rTZbH0WDKUpiiCIJE2B8+evra7F95Kubd+m27u14T7x3MmrlnmapmVZ/vT+gxTFPDy0QCAwnCqEFZJMfCFBV1G6itJsNEqFQiqVEWDtuP7CciCUPsj5w5FiqcyyhwA8eW3BIUFRKJenP1ywOzeAz+fDcTyVSrED7CcSLEWtbnpv2ld6qio15CNZLg5+hgDh0SC7M+nME999/C2THhj/On4L57hCWRSFsiBJNXnwdio1CSH0ydqmezcAPr+RwwtECAUje063t60oQ4JyWeB5XoSVPoHcKgjiyBsvbBD/HFladkajgwAXhw+H5/lSqSTCiqKqTrfXH478C8tv3lkmMwb1AAAAAElFTkSuQmCC" nextheight="3139" nextwidth="5100" class="image-node embed"><figcaption htmlattributes="[object Object]" class="">MakerDAO architecture</figcaption></figure><p>and turn it into something to like this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/92b3835734446c0a0f344e916a4aa749.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAcCAIAAACPoCp1AAAACXBIWXMAABYlAAAWJQFJUiTwAAAFkUlEQVR4nJ1WTUwbVxDeQw495JATtxwtpFZKo0qRcmhE1JMPufjQi6GVkg1IxV2kSiFbUalkXYXAQtM0rC0qAs+QJvYzMQn2GptgPxxj4sUxlRetnZbajygNrDc91d47FTvUMi6iP6PV05v1N2/ezDcza8YwDEppY21Is0qtvSzLGGNqyZGwlqNgZZr1YywxxoQQSqkkSS2wv1sdckAp1TStsTakWdU0DWPMWIItMQzjGKvm9V85oJQihBwOh81mwxjLsny81SEHx6eIWmKapqIodrvdZrMhhJrB/5yiYxyAapomxpjneZvN1tbWxrKsKIrNmP/joPniHMcJgoAQEkWREAJ7lmUxxqZpNsj4bySDmSiKTqcTDmJZVpZlwzBYlgUkz/McxxFCmkM5juTmjSiKLMtKkmQYxt7e3jdud093NxyEEHI6nXB3QgjLsj3d3eBGs+QIkg3DiC3GQg/8JBZbS6WEgYG2U6fmg8Hk8vJyNLqWSr3X3i4MDKQJmfMHMiT5DsM89PmWo1ElnX66tPTh+fOXu7oKudxyNCqHQnP+wK6uH+LAMIzFcKRULGqaxnEcQghjzLLsWjYriqIgCLIscxwnSZKmaZA0CNE0zV6XC/C9LhfkcD4YbOWAUvrg/o+apsmyLFoCNgghSZLAB7IESOZ5XpZlQRA0TRMsATw0Obo33coBpXTOHygVi+Cgw5KGA57nGYYRBAGmhSAIJ0+edDgcoihCIXR0dJw+fVqSJIQQHHUEB4/nH1NKFUW51t9fUFVCyLX+/uVE4uvBQYxxQVVvDQ9LklRQ1V6Xq6Cqy4nE5StX/qjVel2uJ+FwQVUhDtM05/yB1j4ADoDP+wj1uVwXL1wYuTW8GI6EMB6/c+f8uXNf8fycP/B0aenu2Nglu/1Tp3NqcnJDUb7/7s7HDsclu/0HSYouLKQJ8c/MtjqoVnVNK2YzmWwms10uRxcW3IODWz//kkmnNxRF39npc7mymcxPGxtrqxn9zW8jQ0NBv//N69fZTOZl6eXU5OTI0JC+s1NU1Wwms76eO7rRfi2XC6q6q+tPwuFr/f27ul5QVXjT63IpirKr65TSXV2/Oz7+0O8HwK6u35uaujU83MAf0WiU0tmZmW/HxqBUBEFwOp3SX4KsOQoDQxAEKFCe56EEEEIcx8HkALwois1te8ABVNieJaZpEkKgLTVNUxRlP9ImQQgpitIAwIcIDPf29hpfpEMkQ4HCFXiedzgcsB8d3Q/LbrfDr7Cy7BWO4zweL2AgApiGAGgefAccVKs6PPV6LZlM3rgxWK/XINJ6vXb9+vXNzU0oBwg3EgnX6zXDMOr1WiDgl6Txer0GJ7R87A4caFoxFkvEYolSaevRo8dffNFfKm3FYolUarVU2urp+SwWS7x4UVhZeZbPqwMDgxMT9/J5NRZL5PPq7dvjbvfNUmkrlVoNhSKp1GqlUj7EQbWqx+PJVGo1lVr1+WYvXvyIYRivd2Jl5dnMzAO3+ybDMF1dnywuLuVy+bGx2wxz4uzZD3y+2WLxpc83297+LsOc8HonADA/Lx/RB+n0c8MwFCULcwbGWTKZFMWRQMAP6vT01ObmJsdxkUg4Eglz3OeQvenpKaAwEPDX67V4PHmEg3g8ub39SpajCCEoFXCDMbbZbAzDnDnzPiFEURSWZQHg8XhhPoIqSRL822iNgFJaqZRjsQSlFC4CwxkhFAj4RXGEEMLzPJS5FdN+H2CMBUGoVMqNzpBlWZLGq1U9FIq0klyplEOhSC6XVxTl6tVuQXB7PN7Ozi5CUqOjY319faB6PN719ZzT6fR4vILg7uzsevv2d47jeP5Lj8fLsmwwGNzeftXqAFL04kUhHk/G48l0Wsnn1XT6ufUooML7w+rBr9azD7asFDikOUV/AlGtjunBvBsEAAAAAElFTkSuQmCC" nextheight="1774" nextwidth="2000" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>The key change needed to design protocol like this revolves around the idea that some contracts should be external-facing (stateless policies) and some should be internal-facing (stateful modules).</p><p>This design limits the maximum number of nested dependencies for a contract to 1 while also providing guarantees on which contracts are allowed to modify specific storage.</p><p>I won't go over all the details of the Default Framework here but for more information, you can read about it on their <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/fullyallocated/Default">Github</a>. For the rest of this post, I will be covering how the Endgame architecture was designed while using this framework as a guide.</p><div class="relative header-and-anchor"><h3 id="h-kernel-layer">Kernel Layer</h3></div><p>The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/Kernel.sol">Kernel</a> is a contract whose responsibility is to orchestrate the interaction between user-facing policy contracts and stateful module contracts. It maintains the bookkeeping needed to ensure that modules can only be accessed by registered policies. Additionally, the Kernel will manage admin roles for operations such as adding and removing policies or modules from the protocol.</p><p>The key portion of the kernel's administrative logic lies within the <code>permissioned</code> modifier.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">permissioned</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>kernel.modulePermissions(KEYCODE(), Policy(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>), <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sig</span>)) {
        <span class="hljs-keyword">revert</span> Errors.Module_PolicyNotAuthorized(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
    }
    <span class="hljs-keyword">_</span>;
}</code></pre><p>The Kernel keeps track of each module using a unique key-code assigned to it. Using this key-code, it can check if the <code>msg.sender</code> has previously registered with the protocol in relation to the function selector that it is trying to access. This process is the key to maintaining the separation of business logic and contract storage. As such, each function that modifies storage on a module <em>must</em> implement this modifier to function properly.</p><div class="relative header-and-anchor"><h3 id="h-modules">Modules</h3></div><p>Modules are considered the "back-end" of the protocol. They hold the global storage of the protocol and cannot be directly interacted with by any EOA or contract outside of the protocol itself. For any contract to be allowed to modify storage on a module, they must be explicitly allowed to access a particular function selector on the module. This design makes storage access an opt-in feature, and prevents any unintended side-effect where contracts can modify storage that was unintended.</p><p>Endgame uses two modules: a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/modules/Storage.sol">Storage Module</a> which contains data relating to rentals that are active in the protocol, and an <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/modules/PaymentEscrow.sol">Escrow Module</a> which holds ERC20 token payments until they are ready to be collected.</p><p>For example, a function on the Storage Module looks like this: </p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addRentals</span>(<span class="hljs-params">
    <span class="hljs-keyword">bytes32</span> orderHash,
    RentalAssetUpdate[] <span class="hljs-keyword">memory</span> rentalAssetUpdates
</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyByProxy</span> <span class="hljs-title">permissioned</span> </span>{
    <span class="hljs-comment">// ... logic goes here</span>
}</code></pre><p>As stated before, each function that modifies storage on the module will have a <code>permissioned</code> modifier. This modifier makes a call to the kernel to see if the address calling this function has registered its intent with the protocol to add rentals to storage. </p><div class="relative header-and-anchor"><h3 id="h-policies">Policies</h3></div><p>Policies are user-facing contracts which do not maintain storage of their own. Their main purpose is to act as windows into the protocol. For example, to create a rental with Endgame, a user would interact with the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Create.sol">Create Policy</a>. In reality, it's a bit more complicated than that since Seaport is the entity that interacts with the Create policy but the idea is the same. For stopping a rental, the user interacts with the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Stop.sol">Stop Policy</a>. For changes to the protocol itself such as adding or removing another policy, the user would interact with the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Admin.sol">Admin policy</a> (if they are an admin, of course).</p><p>A key benefit here is the ability to slice up the protocol into clear sections of business logic. The Stop Policy is not concerned with how the Create Policy operates, and vice-versa. They each only care about interfacing with the data models that are needed to carry about their own policy business logic.</p><p>We can now turn to how a policy interacts with a module contract. We have seen that a state-modifying module function cannot be accessed unless the caller has previously registered with the kernel about the function selectors it intends to use. So, how does a policy contract register its intent?</p><p>Continuing our example from before, we can show how the Create Policy intends to update protocol storage about new rentals that have been processed.</p><pre data-type="codeBlock"><code><span class="hljs-comment">/**
 * @notice Upon policy activation, permissions are requested from the kernel to access
 *         particular keycode &lt;&gt; function selector pairs. Once these permissions are
 *         granted, they do not change and can only be revoked when the policy is
 *         deactivated by the kernel.
 *
 * @return requests Array of keycode &lt;&gt; function selector pairs which represent
 *                  permissions for the policy.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">requestPermissions</span>(<span class="hljs-params"></span>)
    <span class="hljs-title"><span class="hljs-keyword">external</span></span>
    <span class="hljs-title"><span class="hljs-keyword">view</span></span>
    <span class="hljs-title"><span class="hljs-keyword">override</span></span>
    <span class="hljs-title">onlyKernel</span>
    <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">Permissions[] <span class="hljs-keyword">memory</span> requests</span>)
</span>{
    requests <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Permissions[](<span class="hljs-number">2</span>);
    requests[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> Permissions(toKeycode(<span class="hljs-string">"STORE"</span>), STORE.addRentals.<span class="hljs-built_in">selector</span>);
    <span class="hljs-comment">// ... requests continue on </span>
}</code></pre><p>Each policy contract inherits an abstract <code>Policy</code> class and must implement the <code>requestPermissions</code> function if it wishes to modify protocol storage. An array of permissions is built up which specifies the key-code of the module contract and the function selector to access.</p><p>When a new policy is added to the protocol, the Kernel will execute <code>requestPermissions</code> on the policy and store the permissions which will be used in the future when validating calls from the policy to a module.</p><div class="relative header-and-anchor"><h3 id="h-packages">Packages</h3></div><p>By splitting up the protocol business logic into their own policy contracts, it's inevitable that at some point the policy contracts would want to share logic or access the same helper functions. </p><p>For this, the protocol utilizes a series of packages. Succinctly put, these are sharable and inheritable abstract contracts with immutable state that can enhance the functionality of a policy contract. </p><p>For example, the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/packages/Signer.sol">Signer Package</a> contains logic related to signed payloads and signature verification when creating or stopping rentals. The functions in this package are needed by both the Create Policy and the Stop Policy, so rather than duplicate the functionality, each policy can inherit this package contract and use its functions that way.</p><div class="relative header-and-anchor"><h3 id="h-reducing-storage-costs">Reducing Storage Costs</h3></div><p>Putting all the data in contract storage needed to keep track of a single rental is not cheap. Each token involved in the order, all its hook data, and then the rental metadata itself need to be tracked. It would take up a lot of storage slots to store a single <code>RentalOrder</code> struct:</p><pre data-type="codeBlock"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">RentalOrder</span> {
    <span class="hljs-keyword">bytes32</span> seaportOrderHash;
    Item[] items;
    Hook[] hooks;
    OrderType orderType;
    <span class="hljs-keyword">address</span> lender;
    <span class="hljs-keyword">address</span> renter;
    <span class="hljs-keyword">address</span> rentalWallet;
    <span class="hljs-keyword">uint256</span> startTimestamp;
    <span class="hljs-keyword">uint256</span> endTimestamp;
}

<span class="hljs-comment">// A rental order has an array of hooks</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">Hook</span> {
    <span class="hljs-comment">// The hook contract.</span>
    <span class="hljs-keyword">address</span> target;
    <span class="hljs-comment">// Index of the item in the order to apply the hook to.</span>
    <span class="hljs-keyword">uint256</span> itemIndex;
    <span class="hljs-comment">// Any extra data that the hook will need.</span>
    <span class="hljs-keyword">bytes</span> extraData;
}
 
<span class="hljs-comment">// A rental order has an array of items</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">Item</span> {
    ItemType itemType;
    SettleTo settleTo;
    <span class="hljs-keyword">address</span> token;
    <span class="hljs-keyword">uint256</span> amount;
    <span class="hljs-keyword">uint256</span> identifier;
}</code></pre><p>Thats a lot of SSTOREs!</p><p>To avoid all this overhead when it comes to storing rental data, the protocol opts for hashing the <code>RentalOrder</code> struct and storing that instead. This brings the cost of storing a rental order down to a single SSTORE. </p><p>As with any design, there are always trade-offs. The issue this brings is that by storing the hash, you lose access to the data that the rental order was supposed to store in the first place. To remedy this, upon each rental the protocol will emit a <code>RentalOrderStarted</code> event with all the data necessary to reconstruct a <code>RentalOrder</code>. That way, the data doesn't need to be put in protocol storage but can still be accessible.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">RentalOrderStarted</span>(<span class="hljs-params">
    <span class="hljs-keyword">bytes32</span> orderHash,
    <span class="hljs-keyword">bytes</span> emittedExtraData,
    <span class="hljs-keyword">bytes32</span> seaportOrderHash,
    Item[] items,
    Hook[] hooks,
    OrderType orderType,
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> lender,
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> renter,
    <span class="hljs-keyword">address</span> rentalWallet,
    <span class="hljs-keyword">uint256</span> startTimestamp,
    <span class="hljs-keyword">uint256</span> endTimestamp
</span>)</span>;</code></pre><div class="relative header-and-anchor"><h3 id="h-conclusion">Conclusion</h3></div><p>Having now gone through an audit using <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://code4rena.com/audits/2024-01-renft">Code4rena</a>, I feel confident that the data model methodology for designing a smart contract protocol was the correct approach to take. </p><p>During our audit, there wasn't a single reported issue related to execution of an unauthorized function because of some misconfiguration of admin privileges. Additionally, the design lends itself to being highly configurable, able to adapt to any new contracts that we may want to fold into the protocol later on.  </p><p>Endgame is still in its early stages, and I am excited to see how the protocol evolves from here. You can view the implementation of the protocol <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts">here</a>, and if you have any questions on the protocol architecture, feel free to reach out to myself at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="mailto:alec@021.gg">alec@021.gg</a> or on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/alecdifederico">x.com</a>. </p><p></p><p></p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Zero To One)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/3b6f3d0bec5904fa1fc654665c017875.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Endgame Front End Deep Dive: Layers and Data]]></title>
            <link>https://paragraph.com/@021/endgame-front-end-deep-dive-layers-and-data</link>
            <guid>9PIjb7zp550aRRCGEMmV</guid>
            <pubDate>Mon, 29 Apr 2024 10:13:23 GMT</pubDate>
            <description><![CDATA[  We often paralyze ourselves by giving up-front decisions an unhealty amount of weight. While deep-diving Endgame's data-fetching logic, we provide insight in how we deal with this.]]></description>
            <content:encoded><![CDATA[<p>It's strange. When we think about code it oftentimes evokes a sense of rigidness. A rule-bound universe where processes, inputs, outputs, are predictable things. The same input produces the same output. Paradoxically, the act of coding is anything but rigid. It's an extremely creative and relatively unbounded process. I think the architecture and module structure of a codebase is where this paradox causes the most clashes. This is evidenced partly by how many opinions and articles about code organization float around on the net. Another thing I noticed, as a person who frequents (web) dev related Subreddits, is how often questions on folder structure pop up.</p><p>I think I understand the perceived importance of structuring upfront. As humans we like patterns, structure and the clarity these things provide. "If I think really really hard about how my project needs to be structured up front, I'll win back velocity once I'll actually start coding because I won't have to think about where to place things."</p><p>Structuring a project upfront is a fantastically useless navel-gazing distraction. It distracts you from the actual work with its feigned importance. It's a very inconspicuous form premature optimization and, quite frankly, busywork. What's more, structuring upfront runs directly counter to coding's actual craft and distances you from the material and landscape. The map is not the territory.</p><p>Just get started. Do the work.</p><div class="relative header-and-anchor"><h2 id="h-three-tiered-onions">Three-Tiered Onions</h2></div><p>Oooh, onions! We're definitely cookin'.</p><p>There's no shortage of metaphors when it comes to coding. One of the most long-lived ones is the tiered "onion" architecture. Like an onion, it has a core being enveloped by other layers with differing responsibilities. The three tier architecture being a prime example. It separates presentation, application (logic) and data into separate layers. Data placed firmly at the core, enveloped by the application layer, enveloped by the presentation layer.</p><p>Let me cycle back to how <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@021/a-tour-of-an-nft-rentals-front-end-at-cruising-altitude#h-remix">we swapped the engine at 2/3rd of the project</a>. The primary reason we were confident about swapping Next.js with Remix is because we were quite diligent in keeping apart the architectural layers of our front end. These layers do not corroborate 1-on-1 to three tier architecture. We're dealing with a front end here and not a complete stack. Still, the principle of layering can be applied somewhat recursively to fit this slice of our stack.</p><p>Roughly speaking we discern the following:</p><ol><li><p>Service layer, consisting of configuration, API interfacing, chain &amp; wallet interfacing, and rental protocol interfacing.</p></li><li><p>User Interface and Browser/React utilities.</p></li><li><p>Engine, providing SSR and route endpoints.</p></li></ol><p>Now, these layers do not necessarily correlate with our module structure. Generally speaking though, each layer can consume any preceding layer. Notice how the engine is an outer layer. Our core user interface components are separate from it, as is our core logic and service interfacing. The engine mostly provides an harness to fit our core logic and UI onto. It provides ways to expose our user interface through routes and ways to optimize initial data fetching to get those fast initial renders.</p><p>There are some parts which remain somewhat tightly coupled, because they're pretty much dependent on engine specifics. This makes them inherently hard to decouple. Take cookies and sessions for example. Next.js and Remix both provide APIs to handle these. There's some overlap but consuming cookies (ha!) generally wasn't worth abstracting. In these cases we do try to keep data model and consumers of cookie data in our service and UI layers.</p><p>If there's anything to take away from this section, it's that having a clear idea about layering responsibilities will guide decisions on structure and organization.</p><div class="relative header-and-anchor"><h3 id="h-a-practical-example">A practical example</h3></div><p>So far I've been all talk. It's high time for some actual code. Lets implement the way we fetch data with Tanstack Query.</p><p><strong>Note:</strong> there is a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/rombrom/remix-tanstack-query-hydration-demo">demo repository</a> you can check out which contains all code below.</p><pre data-type="codeBlock" language="sh"><code><span class="hljs-comment"># Create a Remix project, install Tanstack Query</span>
<span class="hljs-comment"># and run dev server</span>

npx create-remix@latest remix-tanstack-query
<span class="hljs-built_in">cd</span> remix-tanstack-query
npm i --save-exact @tanstack/react-query
npm run dev</code></pre><p>Lets do some scaffolding first. We need some fake data to fetch. We'll simulate this as well as possible network latency and error cases.</p><pre data-type="codeBlock" language="ts"><code><span class="hljs-comment">// File: app/getData.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getData</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> num = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>();
  <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">ok</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(ok, <span class="hljs-number">1000</span> * num));
  <span class="hljs-keyword">if</span> (num &gt; <span class="hljs-number">0.5</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">`Bad number: <span class="hljs-subst">${num}</span>`</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-string">`Good number: <span class="hljs-subst">${num}</span>`</span>;
}</code></pre><p>We also need to add a <code>&lt;QueryClientProvider /&gt;</code> to our root layout.</p><pre data-type="codeBlock" language="tsx"><code><span class="hljs-comment">// File: app/root.tsx</span>

<span class="hljs-keyword">import</span> {
  <span class="hljs-title class_">QueryClient</span>,
  <span class="hljs-title class_">QueryClientProvider</span>
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Layout</span>(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) {
  <span class="hljs-comment">// The reason we want to initialze `QueryClient` in a state</span>
  <span class="hljs-comment">// is because we need the client to be unique for each user.</span>
  <span class="hljs-keyword">const</span> [queryClient] = <span class="hljs-title function_">useState</span>(
    <span class="hljs-keyword">new</span> <span class="hljs-title class_">QueryClient</span>({
      <span class="hljs-attr">defaultOptions</span>: {
        <span class="hljs-comment">// No retries in testing, else we'll never see errors.</span>
        <span class="hljs-attr">queries</span>: { <span class="hljs-attr">retry</span>: <span class="hljs-literal">false</span> },
      },
    })
  );
  <span class="hljs-comment">// ...</span>

  <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">// ...</span>
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">QueryClientProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{queryClient}</span>&gt;</span>
          {children}
        <span class="hljs-tag">&lt;/<span class="hljs-name">QueryClientProvider</span>&gt;</span></span>
    <span class="hljs-comment">// ...</span>
  );
}
<span class="hljs-comment">// ...</span></code></pre><p>Lastly, lets implement this in our <code>app/routes/_index.tsx</code> route.</p><pre data-type="codeBlock" language="tsx"><code><span class="hljs-comment">// File: app/routes/_index.tsx</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> { useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> { getData } <span class="hljs-keyword">from</span> <span class="hljs-string">'~/getData'</span>;
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Index</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> { data, error, isLoading } = <span class="hljs-title function_">useQuery</span>({
    <span class="hljs-attr">queryFn</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">getData</span>(),
    <span class="hljs-attr">queryKey</span>: [<span class="hljs-string">'num'</span>],
  });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
      <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">color:</span> <span class="hljs-attr">isLoading</span> ? '<span class="hljs-attr">blue</span>' <span class="hljs-attr">:</span> <span class="hljs-attr">error</span> ? '<span class="hljs-attr">red</span>' <span class="hljs-attr">:</span> '<span class="hljs-attr">green</span>',
        <span class="hljs-attr">fontFamily:</span> '<span class="hljs-attr">system-ui</span>, <span class="hljs-attr">sans-serif</span>',
        <span class="hljs-attr">lineHeight:</span> '<span class="hljs-attr">1.8</span>',
      }}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Data: {data}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Error: {error?.message}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}</code></pre><p>Big whoop. This is Tanstack Query 101. Lets get that SSR going. Tanstack Query offers some flexibility here. The <code>initialState</code> parameter on <code>useQuery</code> and friends could be used but will lead to prop drilling. The most robust and portable method is leveraging its <code>HydrationBoundary</code> and <code>dehyrate()</code> APIs. You can implement these per route, but Remix allows you to write even less boilerplate.</p><p>When you visit a route on Remix, it will fetch data from all loaders in the route cascade. If you pair this with Remix's powerful <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://remix.run/docs/en/main/hooks/use-matches">useMatches</a> [1] hook, you can combine dehydrated data from all matching loaders without having to resort to nested <code>HydrationBoundary</code> components. The only requirement is introducing a convention on how (prefetched) loader data is returned. So lets agree here and now that any loader returning JSON which includes a <code>dehydratedState</code> property has state we want to accumulate.</p><pre data-type="codeBlock" language="ts"><code><span class="hljs-comment">// File: app/useMatches.ts</span>

<span class="hljs-keyword">import</span> { useMatches } <span class="hljs-keyword">from</span> <span class="hljs-string">'@remix-run/react'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">DehydratedState</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">useDehydratedState</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> matches = <span class="hljs-title function_">useMatches</span>();
  <span class="hljs-keyword">return</span> (
    matches
      <span class="hljs-comment">// @ts-expect-error in all cases the following will resolve</span>
      <span class="hljs-comment">// to undefined if `dehydratedState` isn't available.</span>
      .<span class="hljs-title function_">flatMap</span>(<span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> (data?.<span class="hljs-property">dehydratedState</span> <span class="hljs-keyword">as</span> <span class="hljs-title class_">DehydratedState</span>) ?? [])
      .<span class="hljs-property">reduce</span>&lt;<span class="hljs-title class_">DehydratedState</span>&gt;(
        <span class="hljs-function">(<span class="hljs-params">result, current</span>) =&gt;</span> ({
          <span class="hljs-attr">queries</span>: [...result.<span class="hljs-property">queries</span>, ...current.<span class="hljs-property">queries</span>],
          <span class="hljs-attr">mutations</span>: [...result.<span class="hljs-property">mutations</span>, ...current.<span class="hljs-property">mutations</span>],
        }),
        { <span class="hljs-attr">queries</span>: [], <span class="hljs-attr">mutations</span>: [] }
      )
  );
}</code></pre><p>Now lets add the hydration setup to our root layout. Also note the addition of <code>staleTime</code> to our client side <code>QueryClient</code>. Our dehydrated client already fetched and without adding at least a bit of <code>staleTime</code>, our queries would be executed again on the client.</p><pre data-type="codeBlock" language="tsx"><code><span class="hljs-comment">// File: app/root.tsx</span>

<span class="hljs-keyword">import</span> {
  <span class="hljs-title class_">HydrationBoundary</span>,
  <span class="hljs-title class_">QueryClient</span>,
  <span class="hljs-title class_">QueryClientProvider</span>
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { useDehydratedState } <span class="hljs-keyword">from</span> <span class="hljs-string">'./useDehydratedState'</span>;
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Layout</span>(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) {
  <span class="hljs-comment">// The reason we want to initialze `QueryClient` in a state</span>
  <span class="hljs-comment">// is because we need the client to be unique for each user.</span>
  <span class="hljs-keyword">const</span> [queryClient] = <span class="hljs-title function_">useState</span>(
    <span class="hljs-keyword">new</span> <span class="hljs-title class_">QueryClient</span>({
      <span class="hljs-attr">defaultOptions</span>: {
        <span class="hljs-comment">// No retries in testing, else we'll never see errors.</span>
        <span class="hljs-attr">queries</span>: { <span class="hljs-attr">retry</span>: <span class="hljs-literal">false</span> },
        <span class="hljs-attr">staleTime</span>: <span class="hljs-number">5000</span>,
      },
    })
  );
  <span class="hljs-keyword">const</span> dehydratedState = <span class="hljs-title function_">useDehydratedState</span>();
  <span class="hljs-comment">// ...</span>

  <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">// ...</span>
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">HydrationBoundary</span> <span class="hljs-attr">state</span>=<span class="hljs-string">{dehydratedState}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">QueryClientProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{queryClient}</span>&gt;</span>
            {children}
          <span class="hljs-tag">&lt;/<span class="hljs-name">QueryClientProvider</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">HydrationBoundary</span>&gt;</span></span>
    <span class="hljs-comment">// ...</span>
  );
}
<span class="hljs-comment">// ...</span></code></pre><p>And hook up a loader, making use of <code>dehydrate()</code>, to the index route.</p><pre data-type="codeBlock" language="tsx"><code><span class="hljs-comment">// File: app/routes/_index.tsx</span>
<span class="hljs-keyword">import</span> { json, <span class="hljs-keyword">type</span> <span class="hljs-title class_">LoaderFunction</span>, <span class="hljs-keyword">type</span> <span class="hljs-title class_">MetaFunction</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@remix-run/node'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">QueryClient</span>, dehydrate, useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> { getData } <span class="hljs-keyword">from</span> <span class="hljs-string">'~/getData'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">loader</span>: <span class="hljs-title class_">LoaderFunction</span> = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> <span class="hljs-title class_">QueryClient</span>();
  <span class="hljs-keyword">await</span> queryClient.<span class="hljs-title function_">prefetchQuery</span>({
    <span class="hljs-attr">queryFn</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">getData</span>(),
    <span class="hljs-attr">queryKey</span>: [<span class="hljs-string">'num'</span>],
  });
  <span class="hljs-keyword">return</span> <span class="hljs-title function_">json</span>({ <span class="hljs-attr">dehydratedState</span>: <span class="hljs-title function_">dehydrate</span>(queryClient) });
};
<span class="hljs-comment">// ...</span></code></pre><p>And fin.</p><p>This is basically the setup we use for anything data-fetching related. We have some modules exporting ready-to-use functions like <code>getData()</code> from our service layer. These are used in our UI components directly on <code>useQuery()</code>. Our engine also imports these so they can be hooked up easily to prefetching. This setup has been unchanged since we implemented it in Next.js. The prefetching logic, instead of being housed in a loader, was just part of a React Server Component. Moving over the logic to a loader was a breeze.</p><div class="relative header-and-anchor"><h3 id="h-you-said-onions">You said onions?</h3></div><p>Right.</p><p>The example we worked through is a simple one. Still, it informs us about layers. The way Tanstack Query integrates naturally separates data fetching from presentation and allows you separate prefetching into a separate layer as well. I guess you could add an extra abstraction which wraps setting up and dehydrating a client, but trust me when I say the ROI isn't great. You're not swapping engines every day.</p><p>The layers in the example aren't that clearly demarcated as there's some overlap in code location. The index route, for example, contains code concerning the UI layer and the prefetching layer. Layers aren't necessarily related to code location, folders and files. It's got more to do with what responsibility and/or domain the code envelops.</p><p>The beauty of code is that it's easily rearranged. Moving the UI code into a separate component is trivial. So is moving or abstracting loader (pre)fetching. When our example application grows you could very well imagine UI components being split off into <code>~/components</code>. At some point it might make sense to create a <code>~/loaders</code> module which houses recurring loader patterns. <code>getData()</code> could grow into a slew of modules housed in <code>~/api</code>. Who knows.</p><p>The key thing here is learning to recognizing these layers. And understanding—nay, grokking—that moving, abstracting, and refactoring code are inexpensive operations. Start inline and work your way outwards. The code will tell you when it needs changing.</p><div class="relative header-and-anchor"><h2 id="h-a-digression-on-botany">A digression on botany</h2></div><p>Andy Hunt and David Thomas, authors of "The Pragmatic Programmer," quite aptly captured the meandering aspects of the craft in their <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.artima.com/articles/programming-is-gardening-not-engineering">garden metaphor</a>. They recognized the paradoxical sentiments around programming. One the one hand the discourse touts coding an engineering profession: plan, execute, deliver. All according to spec. On the other hand, the act of coding itself often yields new discoveries orthogonal to the initial plan.</p><blockquote><p>The garden doesn't quite come up the way you drew the picture. This plant gets a lot bigger than you thought it would. You've got to prune it. You've got to split it. You've got to move it around the garden. This big plant in the back died. You've got to dig it up and throw it into the compost pile. [2]</p></blockquote><p>When we started building Endgame we didn't start with any specific structure in mind. The first commit is literally the result from <code>npx create-next-app@latest</code> and we cruised on this structure for our initial scaffolding. We just wrote a lot of things inline. When we added WalletConnect support about 12 commits in, the <code>@/components</code> folder was added. It's a central place for UI components. Initially we had everything related to our WalletConnect integration housed in <code>@/components/wallet</code>. Much later we had split this up into <code>@/wallet</code> and <code>@/components/wallet</code>. The former being responsible for configuration and generic initialization, the latter specifically catered to UI. When we worked on getting GraphQL interfacing set up, we added <code>@/graphql</code>, housing code generation artifacts from GraphQL Codegen, our GraphQL client initialization and some utilities. We shoehorned our API interfacing layer into this module which we later extracted into <code>@/services/api</code>. We had a <code>@/config</code> folder which later turned into a simpler <code>@/config.ts</code> file. [3]</p><p>Anyway. What I'm trying to get at is that we just started doing the work. It didn't make sense worrying about structure before we'd have a solid grasp of how the thing we were building could be best structured. We did, however, have a good sense on how to layer things. Because, frankly, this wasn't our first rodeo.</p><p>Coding is just as much a process of discovering the codified as it is codifying the discovered. One of the keys to becoming better is trying to make this feedback loop as tight as possible. Because many iterations through this loop will offset the practice more and more to codifying the discovered.</p><p>Just get started. Do the work.</p><hr><ol><li><p>Not related to fire starting equipment.</p></li><li><p>See <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.artima.com/articles/programming-is-gardening-not-engineering">Programming is Gardening, not Engineering: A Conversation with Andy Hunt and Dave Thomas, Part VII</a>. Quite some concepts from The Pragmatic Programmer make the rounds here.</p></li><li><p>We're leveraging <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.typescriptlang.org/tsconfig/#paths">TypeScript's </a><code>paths</code><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.typescriptlang.org/tsconfig/#paths"> option</a> as a poor-man's monorepo: <code>@/* › src/*</code>, <code>~/* › app/*</code>. Aside from freeing us from walking up paths, it's more subtle effect is that you tend to folders in <code>src/*</code> and <code>app/*</code> as proper, separate modules.</p></li></ol><hr><p><strong>Note:</strong> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.rombrom.com/posts/021-endgame-data-fetching/">This article is co-published</a> on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://rombrom.com">rombrom.com</a>.</p><p></p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Rommert Zijlstra)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/eac8febcb74e418277672d3e3161983f.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Anatomy of a Rental Transaction]]></title>
            <link>https://paragraph.com/@021/anatomy-of-a-rental-transaction</link>
            <guid>P3UWZybGvN1k0bgynLRT</guid>
            <pubDate>Mon, 29 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Endgame, a brand-new NFT rentals marketplace, is 021's latest product that facilitates non-custodial rentals. We accomplished this by building the pr...]]></description>
            <content:encoded><![CDATA[<p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">Endgame</a>, a brand-new NFT rentals marketplace, is 021's latest product that facilitates non-custodial rentals. We accomplished this by building the protocol on top of some of the industry's biggest giants, namely, Seaport and Safe.</p><p>In this post, I will be focusing on how we use <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ProjectOpenSea/seaport-core">Seaport</a> as an order fulfillment engine so that Endgame can focus on just the rental logic. I'll walk through the process of creating, fulfilling, and stopping an order to give a full overview of how a rental flows through the protocol. </p><p>Let's get started!</p><div class="relative header-and-anchor"><h3 id="h-creating-a-rental-order">Creating a Rental Order</h3></div><p>All new rental orders must first be created by a lender. The lender is allowed to craft the exact terms they wish to see carried out by the order. They get to specify: items to lend, payment items to receive, any hooks that will apply to the rented items (see my post <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@021/hooks-a-powerful-primitive-for-nft-rentals">here</a> if you are unfamiliar with hooks), and rental-specific conditions such as the duration of the rental.</p><p>To start, creating a rental order involves constructing and signing a valid Seaport order with a special data payload so that the protocol's <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Create.sol#L47">zone contract</a> is invoked during the order fulfillment. This step is crucial because without the zone contract, the order fulfillment would be permanent, leaving no way for the lender to get their assets back.</p><p>For each new rental order, the lender must sign a Seaport <code>OrderComponents</code> struct. This contains all the parameters for the Seaport order, and is completely agnostic to the fact that we will be using it for rentals.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">/**
 * @dev An order contains eleven components: an offerer, a zone (or account that
 *      can cancel the order or restrict who can fulfill the order depending on
 *      the type), the order type (specifying partial fill support as well as
 *      restricted order status), the start and end time, a hash that will be
 *      provided to the zone when validating restricted orders, a salt, a key
 *      corresponding to a given conduit, a counter, and an arbitrary number of
 *      offer items that can be spent along with consideration items that must
 *      be received by their respective recipient.
 */</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">OrderComponents</span> {
    <span class="hljs-keyword">address</span> offerer;
    <span class="hljs-keyword">address</span> zone;
    OfferItem[] offer;
    ConsiderationItem[] consideration;
    OrderType orderType;
    <span class="hljs-keyword">uint256</span> startTime;
    <span class="hljs-keyword">uint256</span> endTime;
    <span class="hljs-keyword">bytes32</span> zoneHash;
    <span class="hljs-keyword">uint256</span> salt;
    <span class="hljs-keyword">bytes32</span> conduitKey;
    <span class="hljs-keyword">uint256</span> counter;
}</code></pre><p>The items to pay particular attention to are the <code>zone</code> and <code>zoneHash</code> parameters. In a traditional Seaport order, the <code>zone</code> parameter is typically left blank. In our case, the zone address for all rental orders is the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Create.sol#L47">Create Policy</a> contract, which handles creation of rental orders. </p><p>By specifying a <code>zone</code> address in the <code>OrderComponents</code> struct, Seaport will hand the flow of execution to the <code>zone</code> contract after it has fulfilled the order. This is precisely the mechanism that Endgame uses to ensure that a fulfilled Seaport order is logged and processed, so that it can eventually be stopped in the future. </p><p>The <code>zoneHash</code> parameter is a hashed value of data that contains all of the rental terms and conditions that the lender has specified for a specific order. This data includes the type of rental order, the rental duration, and any hooks for the rentals. After this order has been signed, a counterparty (the fulfiller) will pass the unhashed data that makes up the <code>zoneHash</code> into a Seaport fulfillment function to prove to the protocol that both the lender and the renter of the order have agreed on the same rental terms.</p><div class="relative header-and-anchor"><h3 id="h-constructing-the-zone-hash">Constructing the Zone Hash</h3></div><p>A zone hash is the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/EIPs/blob/fed0965fc8c4a5714e2631d4f76735471af9f684/EIPS/eip-712.md">EIP-712</a> hashed version of the following struct:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">/**
 * @dev Order metadata contains all the details supplied by the offerer when they 
 * 		sign an order. These items include the type of rental order, how long the 
 * 		rental will be active, any hooks associated with the order, and any data that 
 * 		should be emitted when the rental starts.
 */</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">OrderMetadata</span> {
    <span class="hljs-comment">// Type of order being created.</span>
    OrderType orderType;
    <span class="hljs-comment">// Duration of the rental in seconds.</span>
    <span class="hljs-keyword">uint256</span> rentDuration;
    <span class="hljs-comment">// Hooks that will act as middleware for the items in the order.</span>
    Hook[] hooks;
    <span class="hljs-comment">// Any extra data to be emitted upon order fulfillment.</span>
    <span class="hljs-keyword">bytes</span> emittedExtraData;
}</code></pre><p>A bit of information about each parameter in the <code>OrderMetadata</code> struct:</p><ul><li><p><code>orderType</code>: When a lender wants to create a rental, they must choose an order type. There are three order types supported by the protocol, but only 2 are external and can be used by lenders.</p><ul><li><p><code>BASE</code>: This order type describes a rental order in which the lender will construct a seaport order that contains at least one ERC721 or ERC1155 offer item, and at least one ERC20 consideration item. A lender would choose this order when they want to be paid by a renter in exchange for lending out their asset(s) for a specific amount of time. </p></li><li><p><code>PAY</code>: This order type describes an order in which the lender wishes to pay the renter for renting out their asset. This order must contain at least one ERC721 or ERC1155 offer item and at least one ERC20 offer item. It must contain 0 consideration items. This may sound counter-intuitive but the rationale is that some lenders may get benefit (tokens, rewards, etc) from allowing others to interact with contracts (on-chain games, etc) with their assets to extract some type of value from the lended asset.</p></li><li><p><code>PAYEE</code>: This order type cannot be specified by a lender and should result in a revert if specified. <code>PAYEE</code> orders act as mirror images of a <code>PAY</code> order, and they are used by the protocol to match them up with <code>PAY</code> orders. In other words, a <code>PAYEE</code> order has 0 offer items, and should specify the offer items of the target <code>PAY</code> order as its own consideration items, with the proper recipient addresses.</p></li></ul></li><li><p><code>rentDuration</code>: The total duration of the rental in seconds.</p></li><li><p><code>hooks</code>: The hooks which will be specified for the rental.</p></li><li><p><code>emittedExtraData</code>: This is any extra data that the lender wishes to emit once a rental has been fulfilled. This can be useful for integrations with Endgame that occur off-chain.</p></li></ul><p>After the <code>OrderMetadata</code> has been constructed, its hash can be constructed using a convenience function which exists on the Create Policy. This will then be the value that is used for the <code>zoneHash</code>.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">/**
 * @notice Derives the order metadata EIP-712 compliant hash from an `OrderMetadata`.
 *
 * @param metadata Order metadata converted to a hash.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOrderMetadataHash</span>(<span class="hljs-params">
    OrderMetadata <span class="hljs-keyword">memory</span> metadata
</span>) <span class="hljs-title"><span class="hljs-keyword">external</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">return</span> _deriveOrderMetadataHash(metadata);
}</code></pre><p>To see how an <code>OrderMetadata</code> struct is converted into a EIP-712 typehash, check out <code>_deriveOrderMetadataHash</code> in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/packages/Signer.sol">Signer Package</a>.</p><p>Next, the <code>OrderComponents</code> struct can be hashed and then signed by the lender. This happens off-chain but you can see how its done using our <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/test/fixtures/engine/OrderCreator.sol#L286C1-L290C84">test engine</a>:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// generate the order hash</span>
orderHash <span class="hljs-operator">=</span> seaport.getOrderHash(orderComponents);

<span class="hljs-comment">// generate the signature for the order components</span>
<span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> signature <span class="hljs-operator">=</span> _signSeaportOrder(_offerer.privateKey, orderHash);</code></pre><p>With the signature and the <code>OrderComponents</code> generated, they can be combined into a Seaport <code>Order</code> struct which will be used to fulfill the order. </p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">/**
 * @dev Orders require a signature in addition to the other order parameters.
 */</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">Order</span> {
    OrderParameters parameters;
    <span class="hljs-keyword">bytes</span> signature;
}</code></pre><div class="relative header-and-anchor"><h3 id="h-the-protocol-signer">The Protocol Signer</h3></div><p>Before an order can be fulfilled, it must first be submitted to the Endgame backend to receive a signature from the protocol signer. The protocol signer is an EOA managed by 021 which will tell the protocol if an order is still fulfillable. Without this signature, the order will be considered invalid to fulfill.</p><p>Some responsibilities of the protocol signer include: </p><ul><li><p>Signing off on orders that have not been canceled.</p></li><li><p>Ensuring the wallet address that requested the fulfillment is the actual address which will execute the fulfillment.</p></li><li><p>Tying together the rental order with the correct <code>OrderMetadata</code> struct.</p></li></ul><p>The protocol signer will generate a signature from a <code>RentPayload</code> struct by invoking <code>getRentPayloadHash</code> on the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Create.sol#L145C14-L145C32">Create Policy</a> contract.</p><p>A <code>RentPayload</code> struct looks like this:</p><pre data-type="codeBlock"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">RentPayload</span> {
    <span class="hljs-comment">// Hash of the order being fulfilled</span>
    <span class="hljs-keyword">bytes32</span> orderHash; 
    <span class="hljs-comment">// contains the rental wallet address that will receive the rented assets </span>
    OrderFulfillment fulfillment;
    <span class="hljs-comment">// Metadata of the order to fulfill.</span>
    OrderMetadata metadata;
    <span class="hljs-comment">// Timestamp that the protocol signer's signature will expire</span>
    <span class="hljs-keyword">uint256</span> expiration;
    <span class="hljs-comment">// EOA expected to initiate the order fulfillment</span>
    <span class="hljs-keyword">address</span> intendedFulfiller;
}</code></pre><div class="relative header-and-anchor"><h3 id="h-fulfilling-a-rental-order">Fulfilling a Rental Order</h3></div><p>With a <code>RentPayload</code> struct acquired, along with a properly constructed and signed Seaport <code>Order</code>, we can invoke one of Seaport's fulfillment functions to process a signed order.</p><p>Signed orders can be fulfilled by the protocol in a few ways depending on how many orders are being filled at once, and what type of orders are being fulfilled. All Seaport fulfillment functions can be found <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/seaport-core/blob/3bccb8e1da43cbd9925e97cf59cb17c25d1eaf95/src/lib/Consideration.sol">here</a>, but the ones specifically used by the protocol include: </p><ul><li><p><code>fulfillAdvancedOrder</code></p></li><li><p><code>fulfillAvailableAdvancedOrders</code></p></li><li><p><code>matchAdvancedOrders</code></p></li></ul><p>For the purposes of this post, I will show the fulfillment of a single order using <code>fulfillAdvancedOrder</code>, but example usage for all of these fulfillment methods can be found in the protocol test engine code <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/test/fixtures/engine/OrderFulfiller.sol">here</a>.</p><p>To use <code>fulfillAdvancedOrder</code>, an <code>AdvancedOrder</code> struct must be created from a standard Seaport <code>Order</code> struct:</p><pre data-type="codeBlock"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">AdvancedOrder</span> {
    <span class="hljs-comment">// Order parameters that were signed</span>
    OrderParameters parameters;
    <span class="hljs-comment">// Don't worry about this</span>
    <span class="hljs-keyword">uint120</span> numerator;
    <span class="hljs-comment">// Don't worry about this</span>
    <span class="hljs-keyword">uint120</span> denominator;
    <span class="hljs-comment">// Signature of the order parameters</span>
    <span class="hljs-keyword">bytes</span> signature;
    <span class="hljs-comment">// This data will be passed to the zone contract</span>
    <span class="hljs-keyword">bytes</span> extraData;
}</code></pre><p>For the <code>extraData</code> field, it is constructed as a concatenation of the <code>RentPayload</code> struct and the signature of that struct from the protocol signer:</p><pre data-type="codeBlock"><code><span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> extraData <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(rentPayload, rentPayloadSignature);</code></pre><p>With the <code>AdvancedOrder</code> created, we now have everything we need to fulfill the order and create a rental:</p><pre data-type="codeBlock"><code>seaport.fulfillAdvancedOrder(
    advancedOrder,             <span class="hljs-comment">// The order being fulfilled</span>
    <span class="hljs-keyword">new</span> CriteriaResolver[](<span class="hljs-number">0</span>), <span class="hljs-comment">// Dont worry about this</span>
    conduitKey,                <span class="hljs-comment">// Key of the conduit address to interact with seaport</span>
    recipientOfOfferItems      <span class="hljs-comment">// Fulfiller decides who to send received items to </span>
);</code></pre><p>Once the order is fulfilled, the items offered up by the lender will now be sitting inside the rental wallet of the owner that invoked the fulfillment. Meanwhile, the payment for the order will be held in an escrow contract until the rental is stopped and assets are returned to the lender.</p><div class="relative header-and-anchor"><h3 id="h-stopping-a-rental-order">Stopping a Rental Order</h3></div><p>The function signature for stopping a rental order is very simple, accepting a <code>RentalOrder</code> struct as its sole parameter: </p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stopRent</span>(<span class="hljs-params">rentalOrder <span class="hljs-keyword">memory</span> order</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

<span class="hljs-keyword">struct</span> <span class="hljs-title">rentalOrder</span> {
    <span class="hljs-keyword">bytes32</span> seaportOrderHash;
    item[] items;
    hook[] hooks;
    ordertype orderType;
    <span class="hljs-keyword">address</span> lender;
    <span class="hljs-keyword">address</span> renter;
    <span class="hljs-keyword">address</span> rentalWallet;
    <span class="hljs-keyword">uint256</span> startTimestamp;
    <span class="hljs-keyword">uint256</span> endTimestamp;
}</code></pre><p>One way that the protocol saves gas during rental creation is by writing data to storage as infrequently as possible. So, rather than store each parameter of the <code>RentalOrder</code> struct, only its hash is saved in protocol storage. For the rest of the parameters, they can be retrieved from the <code>RentalOrderStarted</code> event which is emitted on each successful rental order fulfillment.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">/**
 * @dev Emitted when a new rental order is started. PAYEE orders are excluded from
 *      emitting this event.
 *
 * @param orderHash        Hash of the rental order struct.
 * @param emittedExtraData Data passed to the order to be emitted as an event.
 * @param seaportOrderHash Order hash of the seaport order struct.
 * @param items            Items in the rental order.
 * @param hooks            Hooks defined for the rental order.
 * @param orderType        Order type of the rental.
 * @param lender           Lender EOA of the assets in the order.
 * @param renter           Renter EOA of the assets in the order.
 * @param rentalWallet     Wallet contract which holds the rented assets.
 * @param startTimestamp   Timestamp which marks the start of the rental.
 * @param endTimestamp     Timestamp which marks the end of the rental.
*/</span>
<span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">RentalOrderStarted</span>(<span class="hljs-params">
    <span class="hljs-keyword">bytes32</span> orderHash,
    <span class="hljs-keyword">bytes</span> emittedExtraData,
    <span class="hljs-keyword">bytes32</span> seaportOrderHash,
    Item[] items,
    Hook[] hooks,
    OrderType orderType,
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> lender,
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> renter,
    <span class="hljs-keyword">address</span> rentalWallet,
    <span class="hljs-keyword">uint256</span> startTimestamp,
    <span class="hljs-keyword">uint256</span> endTimestamp
</span>)</span>;</code></pre><p>Using this data, the <code>RentalOrder</code> can be properly reconstructed. Then, during the call to <code>stopRent</code>, the order will be hashed and compared against the hashed orders that exist in storage.</p><p>When a match is found, the protocol will return the assets back to their original owner, and award a payout from the escrow contract. Thus, completing the life cycle of a rental.</p><div class="relative header-and-anchor"><h3 id="h-conclusion">Conclusion</h3></div><p>There you have it! The complete breakdown of how rental transactions are processed by Endgame in tandem with Seaport. By offloading the fulfillment logic to Seaport, the Endgame protocol is able to focus on just the logic related to rental construction and processing. If you want to dig deeper into Endgame, you can view the protocol contract repository <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts">here</a>.</p><p>And, if you have any questions on rental fulfillments, feel free to reach out to myself at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="mailto:alec@021.gg">alec@021.gg</a>.</p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Zero To One)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/c62b8ba5bb65f62050efeb77f57de175.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Revenue Sharing: How to Supercharge NFT Rentals]]></title>
            <link>https://paragraph.com/@021/revenue-sharing-how-to-supercharge-nft-rentals</link>
            <guid>MUb3URuuZNMozuFAr5WZ</guid>
            <pubDate>Wed, 24 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[As I mentioned in my hook overview post here, hooks are custom-built contracts that can act as middleware during the rental lifecycle of one or more ...]]></description>
            <content:encoded><![CDATA[<p>As I mentioned in my hook overview post <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://paragraph.xyz/@021/hooks-a-powerful-primitive-for-nft-rentals">here</a>, hooks are custom-built contracts that can act as middleware during the rental lifecycle of one or more NFT collections. </p><p>In this post, I will go over an implementation for how to create one of these hooks. The hook I'll be walking through is a reward share hook, which can provide additional incentives on top of a standard rental order. A reward share hook allows a renter and a lender of an asset to accrue rewards in another token during an active rental. Via the terms of the rental, the lender is able to define the exact percentage of how the rewards will be split (e.g., 30% for the renter and 70% for the lender). After the rental has concluded, the accrued assets can then be claimed. </p><p>Let's take a look at the basic structure of a hook.</p><div class="relative header-and-anchor"><h3 id="h-scaffolding-for-a-hook">Scaffolding for a Hook</h3></div><p>For the reward share hook, we need a contract that registers an active rental upon its creation, and then, unregisters the rental once it has been stopped. Using those start and stop timestamps, an accrued reward can later be claimed directly by the renter. To do this, we'll need to leverage the hook's <code>onStart()</code> and <code>onStop()</code> functions.</p><p>To start, let's look at the structure of the hook contract without getting too bogged down with function implementations just yet. </p><pre data-type="codeBlock"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ERC20RewardHook</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IHook</span> </span>{
    <span class="hljs-comment">// This is the protocol contract which will call the hook on rental start </span>
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> createPolicy;

    <span class="hljs-comment">// This is the protocol contract which will call the hook on rental stop</span>
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> stopPolicy;

    <span class="hljs-comment">// The rentable token that is compatible with this hook</span>
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> gameToken;

    <span class="hljs-comment">// The token which will be distributed as a reward to renters</span>
    IERC20 <span class="hljs-keyword">public</span> rewardToken;

    <span class="hljs-comment">// award 1 gwei of reward token per block</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">immutable</span> rewardPerBlock <span class="hljs-operator">=</span> <span class="hljs-number">1e9</span>;

    <span class="hljs-comment">// holds info about an asset</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">bytes32</span> assetHash <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> RentInfo rentInfo) <span class="hljs-keyword">public</span> rentInfo;

    <span class="hljs-comment">// holds info about accrued rewards</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> rewardedAddress <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span> rewards) <span class="hljs-keyword">public</span> accruedRewards;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> _createPolicy,
        <span class="hljs-keyword">address</span> _stopPolicy,
        <span class="hljs-keyword">address</span> _gameToken,
        <span class="hljs-keyword">address</span> _rewardToken
    </span>) </span>{
        createPolicy <span class="hljs-operator">=</span> _createPolicy;
        stopPolicy <span class="hljs-operator">=</span> _stopPolicy;
        gameToken <span class="hljs-operator">=</span> _gameToken;
        rewardToken <span class="hljs-operator">=</span> IERC20(_rewardToken);
    }

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyCreatePolicy</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> createPolicy, <span class="hljs-string">"not callable unless create policy"</span>);
        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyStopPolicy</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> stopPolicy, <span class="hljs-string">"not callable unless stop policy"</span>);
        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlySupportedTokens</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> token</span>) </span>{
        <span class="hljs-built_in">require</span>(token <span class="hljs-operator">=</span><span class="hljs-operator">=</span> gameToken, <span class="hljs-string">"token is not supported"</span>);
        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-comment">// ... Hook function implementations will go here</span>
}</code></pre><p>There are two important modifiers ( <code>onlyCreatePolicy</code> and <code>onlyStopPolicy</code>) which have been implemented to prevent the hook from being misused. </p><p>Because this hook will be tracking accrued rewards for active rentals and distributing tokens, it must prevent any address except the rental protocol from interacting with the hook, and it also must prevent a rental order which uses some random token from also being able to cash in on this hook. </p><p>There are also mappings for the rented assets and the rewards that have been accrued:</p><pre data-type="codeBlock"><code><span class="hljs-comment">// holds info about an asset</span>
<span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">bytes32</span> assetHash <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> RentInfo rentInfo) <span class="hljs-keyword">public</span> rentInfo;

<span class="hljs-comment">// holds info about accrued rewards</span>
<span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> rewardedAddress <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span> rewards) <span class="hljs-keyword">public</span> accruedRewards;</code></pre><p>Their structs are defined as follows:</p><pre data-type="codeBlock"><code><span class="hljs-comment">// Info stored about each rental</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">RentInfo</span> {
    <span class="hljs-keyword">uint256</span> amount;
    <span class="hljs-keyword">uint256</span> lastRewardBlock;
}

<span class="hljs-comment">// Info about the revenue share</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">RevenueShare</span> {
    <span class="hljs-keyword">address</span> lender;
    <span class="hljs-keyword">uint256</span> lenderShare;
}</code></pre><p>The <code>RevenueShare</code> struct is important because this defines the terms for the revenue split. On rental creation, the lender crafts a <code>RevenueShare</code> struct defining what percentage of the rewards they want to receive from the hook. More on how this works in the next section.</p><div class="relative header-and-anchor"><h3 id="h-registering-an-active-rental">Registering an Active Rental</h3></div><p>Next, we'll handle the functionality for registering a new rental order with the hook contract. This function has a few responsibilities:</p><ul><li><p>enforce proper function access using modifiers</p></li><li><p>calculate rewards (if any) that have already accrued for the renter</p></li><li><p>update storage with the asset and timestamp of the new rental</p></li></ul><p>Let's begin with the <code>onStart</code> definition along with its modifiers:</p><pre data-type="codeBlock"><code><span class="hljs-comment">// hook handler for when a rental has started</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onStart</span>(<span class="hljs-params">
    <span class="hljs-keyword">address</span> safe,
    <span class="hljs-keyword">address</span> token,
    <span class="hljs-keyword">uint256</span> identifier,
    <span class="hljs-keyword">uint256</span> amount,
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data
</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyCreatePolicy</span> <span class="hljs-title">onlySupportedTokens</span>(<span class="hljs-params">token</span>) </span>{
    <span class="hljs-comment">// Decode the revenue split data</span>
    RevenueShare <span class="hljs-keyword">memory</span> revenueShare <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (RevenueShare));

    <span class="hljs-comment">// ... Implementation continues on </span>
}</code></pre><p><code>onStart</code> modifiers make sure that only expected rentals and expected senders can interact with the hook. Next, the function accepts the rental safe address, the token, its identifier, the amount of tokens in the rental order, and any extra data that the hook may need. The <code>data</code> is passed in by the lender when they sign a new order to lend, which allows for implementation flexibility since not all uses for hooks can be known upfront. For our example, this is a <code>RevenueShare</code> struct which tells the hook how to split the rewards between the renter and lender.</p><p>Now, we'll move on to calculating previous rewards. Since a single renter can have multiple orders that interact with the hook, we must be certain that they accrue rewards for each of their rentals. So, each time a new rental is added, all accrued rewards from active rentals are calculated so they do not get overwritten by a new rental coming in.</p><pre data-type="codeBlock"><code><span class="hljs-comment">// ... Previous code</span>

<span class="hljs-comment">// get the last block that the rewards were accrued</span>
<span class="hljs-keyword">uint256</span> lastBlock <span class="hljs-operator">=</span> rentInfo[assetHash].lastRewardBlock;

<span class="hljs-comment">// get the amount currently stored</span>
<span class="hljs-keyword">uint256</span> currentAmount <span class="hljs-operator">=</span> rentInfo[assetHash].amount;

<span class="hljs-comment">// if the last block that a reward was accrued exists and the amount is nonzero,</span>
<span class="hljs-comment">// calculate the latest reward. Otherwise, this is a first-time deposit so</span>
<span class="hljs-comment">// there are no rewards earned.</span>
<span class="hljs-keyword">if</span> (lastBlock <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> currentAmount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) {
    <span class="hljs-comment">// The amount of blocks to reward</span>
    <span class="hljs-keyword">uint256</span> blocksToReward <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span> <span class="hljs-operator">-</span> lastBlock;

    <span class="hljs-comment">// since the last time reward were accrued, the reward is distributed per </span>
	<span class="hljs-comment">// block per token stored. Divide by 1e18 to account for token decimals</span>
    <span class="hljs-keyword">uint256</span> latestAccruedRewards <span class="hljs-operator">=</span> (blocksToReward <span class="hljs-operator">*</span> rewardPerBlock <span class="hljs-operator">*</span> 
		currentAmount) <span class="hljs-operator">/</span> <span class="hljs-number">1e18</span>;

    <span class="hljs-comment">// determine the split of the rewards for the lender</span>
    <span class="hljs-keyword">uint256</span> lenderAccruedRewards <span class="hljs-operator">=</span> (latestAccruedRewards <span class="hljs-operator">*</span> 
		revenueShare.lenderShare) <span class="hljs-operator">/</span> <span class="hljs-number">100</span>;

    <span class="hljs-comment">// determine the split of the rewards for the renter</span>
    <span class="hljs-keyword">uint256</span> renterAccruedRewards <span class="hljs-operator">=</span> latestAccruedRewards <span class="hljs-operator">-</span> lenderAccruedRewards;

    <span class="hljs-comment">// Effect: accrue rewards to the lender</span>
    accruedRewards[revenueShare.lender] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> lenderAccruedRewards;

    <span class="hljs-comment">// Effect: accrue rewards to the safe/renter</span>
    accruedRewards[safe] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> renterAccruedRewards;
}

<span class="hljs-comment">// ... Implementation continues on</span></code></pre><p>The last block used to calculate an accrual combined with the current block creates a time range. This range is then multiplied by the preconfigured reward per block which determines the total token reward for the range. </p><p>Then, those tokens are credited to the renter's and lender's accounts, which will be transferred once the tokens are claimed from the hook.</p><div class="relative header-and-anchor"><h3 id="h-ending-an-active-rental">Ending an Active Rental</h3></div><p>The function definition for <code>onStop</code> is very similar to what we have seen previously. The only difference is that the modifier now checks that the Stop Policy is the one that calls the hook.</p><pre data-type="codeBlock"><code><span class="hljs-comment">// handler for when a rental has stopped</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onStop</span>(<span class="hljs-params">
    <span class="hljs-keyword">address</span> safe,
    <span class="hljs-keyword">address</span> token,
    <span class="hljs-keyword">uint256</span> identifier,
    <span class="hljs-keyword">uint256</span> amount,
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data
</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyStopPolicy</span> <span class="hljs-title">onlySupportedTokens</span>(<span class="hljs-params">token</span>) </span>{
    <span class="hljs-comment">// ...Implementation will go here</span>
}</code></pre><p>For the body of the function, the hook will again behave very similar to <code>onStop</code>. Rewards will be accrued in storage, and the total number of rented assets for the renter will be decremented by the <code>amount</code> field.</p><pre data-type="codeBlock"><code><span class="hljs-comment">// ... Previous code</span>

<span class="hljs-comment">// get the last block that the rewards were accrued</span>
<span class="hljs-keyword">uint256</span> lastBlock <span class="hljs-operator">=</span> rentInfo[assetHash].lastRewardBlock;

<span class="hljs-comment">// get the amount currently stored</span>
<span class="hljs-keyword">uint256</span> currentAmount <span class="hljs-operator">=</span> rentInfo[assetHash].amount;

<span class="hljs-comment">// The amount of blocks to reward</span>
<span class="hljs-keyword">uint256</span> blocksToReward <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span> <span class="hljs-operator">-</span> lastBlock;

<span class="hljs-comment">// since the last time reward were accrued, the reward is distributed per block per token stored.</span>
<span class="hljs-comment">// Divide by 1e18 to account for token decimals</span>
<span class="hljs-keyword">uint256</span> latestAccruedRewards <span class="hljs-operator">=</span> (blocksToReward <span class="hljs-operator">*</span> rewardPerBlock <span class="hljs-operator">*</span> currentAmount) <span class="hljs-operator">/</span> <span class="hljs-number">1e18</span>;

<span class="hljs-comment">// determine the split of the rewards for the lender</span>
<span class="hljs-keyword">uint256</span> lenderAccruedRewards <span class="hljs-operator">=</span> (latestAccruedRewards <span class="hljs-operator">*</span> revenueShare.lenderShare) <span class="hljs-operator">/</span> <span class="hljs-number">100</span>;

<span class="hljs-comment">// determine the split of the rewards for the renter</span>
<span class="hljs-keyword">uint256</span> renterAccruedRewards <span class="hljs-operator">=</span> latestAccruedRewards <span class="hljs-operator">-</span> lenderAccruedRewards;

<span class="hljs-comment">// Effect: accrue rewards to the lender and renter</span>
accruedRewards[revenueShare.lender] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> lenderAccruedRewards;
accruedRewards[safe] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> renterAccruedRewards;

<span class="hljs-comment">// Effect: update the amount of tokens currently rented and the latest block that rewards accrued</span>
rentInfo[assetHash].amount <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amount;
rentInfo[assetHash].lastRewardBlock <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span>;

<span class="hljs-comment">// ... Implementation continues on</span></code></pre><div class="relative header-and-anchor"><h3 id="h-claiming-accrued-rewards">Claiming Accrued Rewards</h3></div><p>Once a rental has ended, this hook contract supports claiming the reward tokens directly from the rental hook. Either the lender, renter EOA, or the rental wallet can claim their share of the tokens.</p><pre data-type="codeBlock"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">claimRewards</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> rewardedAddress</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
    <span class="hljs-comment">// check if the caller is the lender or is a rental safe</span>
    <span class="hljs-keyword">bool</span> isClaimer <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> rewardedAddress;

    <span class="hljs-comment">// make sure the caller is the claimer, or they are the owner</span>
    <span class="hljs-comment">// of the safe</span>
    <span class="hljs-built_in">require</span>(
        isClaimer <span class="hljs-operator">|</span><span class="hljs-operator">|</span> ISafe(rewardedAddress).isOwner(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>),
        <span class="hljs-string">"not allowed to access rewards for this safe"</span>
    );

    <span class="hljs-comment">// store the amount to withdraw</span>
    <span class="hljs-keyword">uint256</span> withdrawAmount <span class="hljs-operator">=</span> accruedRewards[rewardedAddress];

    <span class="hljs-comment">// Effect: update the accrued rewards</span>
    accruedRewards[rewardedAddress] <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;

    <span class="hljs-comment">// Interaction: Transfer the accrued rewards</span>
    rewardToken.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, withdrawAmount);
}</code></pre><div class="relative header-and-anchor"><h3 id="h-running-a-foundry-test">Running a Foundry Test</h3></div><p>To kick off the test, we'll start with the setup function. The contracts will be deployed, the hook will be registered with the protocol, and tokens will be minted to the hook contract so that it can payout rewards.</p><pre data-type="codeBlock"><code><span class="hljs-comment">// ... Previous code</span>

<span class="hljs-comment">// deploy contracts needed for hook</span>
rewardToken <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> MockERC20();
hook <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ERC20RewardHook(
    <span class="hljs-keyword">address</span>(create),
    <span class="hljs-keyword">address</span>(stop),
    <span class="hljs-keyword">address</span>(gameToken),
    <span class="hljs-keyword">address</span>(rewardToken)
);

<span class="hljs-comment">// admin enables the hook. Use binary 00000110 so that the hook</span>
<span class="hljs-comment">// is enabled for `onStart` and `onStop` calls</span>
vm.prank(deployer.addr);
guard.updateHookStatus(<span class="hljs-keyword">address</span>(hook), <span class="hljs-keyword">uint8</span>(<span class="hljs-number">6</span>));

<span class="hljs-comment">// fund the hook contract with some reward tokens</span>
rewardToken.mint(<span class="hljs-keyword">address</span>(hook), <span class="hljs-number">100e18</span>);

<span class="hljs-comment">// ... Implementation continues on</span></code></pre><p>Next, we can kick off a test that will perform a rental that will execute the <code>onStart</code> function of the hook contract. Then, we'll roll forward in time and stop the rental so that the <code>onStop</code> function is called. </p><p>Finally, the rewards can be claimed by both the lender and the renter.</p><pre data-type="codeBlock"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test_Success_RewardShare</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    <span class="hljs-comment">// start the rental. This should activate the hook and begin</span>
    <span class="hljs-comment">// accruing rewards while the rental is active.</span>
    RentalOrder <span class="hljs-keyword">memory</span> rentalOrder <span class="hljs-operator">=</span> _startRentalWithGameToken();

    <span class="hljs-comment">// roll ahead by 100 blocks so that rewards can accrue</span>
    vm.roll(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span> <span class="hljs-operator">+</span> <span class="hljs-number">100</span>);

    <span class="hljs-comment">// speed up in time past the rental expiration</span>
    vm.warp(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">+</span> <span class="hljs-number">750</span>);

    <span class="hljs-comment">// stop the rental</span>
    vm.prank(alice.addr);
    stop.stopRent(rentalOrder);

    <span class="hljs-comment">// owner of the safe can claim tokens</span>
    vm.prank(bob.addr);
    hook.claimRewards(<span class="hljs-keyword">address</span>(bob.safe));

    <span class="hljs-comment">// lender of the rental can claim tokens</span>
    vm.prank(alice.addr);
    hook.claimRewards(alice.addr);

    <span class="hljs-comment">// earned rewards should be 100 blocks * 1 gwei reward per block * 1e18 token, </span>
	<span class="hljs-comment">// which is 100 gwei</span>
    assertEq(rewardToken.balanceOf(bob.addr), <span class="hljs-number">30000000000</span>); <span class="hljs-comment">// 30 gwei</span>
    assertEq(rewardToken.balanceOf(alice.addr), <span class="hljs-number">70000000000</span>); <span class="hljs-comment">// 70 gwei</span>
}</code></pre><div class="relative header-and-anchor"><h3 id="h-conclusion">Conclusion</h3></div><p>Hopefully this concrete example of a hook implementation is enough to show how powerful the hook architecture can be when it comes to extending rental functionality. </p><p>This tutorial left out portions of code in the implementation that weren't relevant for discussion. To view the full source, you can take a look at the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/examples/revenue-share/ERC20RewardHook.sol">contract implementation</a> and the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/test/hooks/revenue-share/ERC20RewardHook.t.sol">test file</a>.</p><p>If you have any questions on how hooks work, feel free to reach out to myself at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="mailto:alec@021.gg">alec@021.gg</a>. And, if you want to integrate hooks into your own ERC721 or ERC1155 project, you can contact <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="mailto:naz@021.gg">naz@021.gg</a> for more details.</p><p></p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Zero To One)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/505d57cef1546bffcb94133193409637.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Hooks: A Powerful Primitive for NFT Rentals]]></title>
            <link>https://paragraph.com/@021/hooks-a-powerful-primitive-for-nft-rentals</link>
            <guid>shajbapqEwqFAADidojN</guid>
            <pubDate>Tue, 23 Apr 2024 13:36:43 GMT</pubDate>
            <description><![CDATA[For the past 8 months, I have been working on the smart contract protocol for 021's latest product, Endgame, which is an NFT rentals marketplace powe...]]></description>
            <content:encoded><![CDATA[<p>For the past 8 months, I have been working on the smart contract protocol for 021's latest product, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">Endgame</a>, which is an NFT rentals marketplace powered by Seaport and Safe. Endgame is designed to be a highly extensible protocol with a focus on allowing any project to extend its rental functionality. </p><p>One of the most powerful features of Endgame is its ability to extend rental functionality through the use of hooks. These are custom contracts attached to an order by a lender. Once the order is fulfilled, these hooks can operate at the start of a rental, at the end of a rental, or alongside transactions that originate from rental wallets, allowing customized rental flows on a per-collection basis.</p><div class="relative header-and-anchor"><h2 id="h-hook-lifecycles">Hook Lifecycles</h2></div><p>To understand how hooks fit into the protocol, we'll first look at the <code>IHook</code> interface, which must be implemented to construct a valid hook contract.</p><p>The fully commented source code can be found <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/bc35afe8746ac72cdf083817398b5724d09f1c24/src/interfaces/IHook.sol">here</a>.</p><pre data-type="codeBlock"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IHook</span> </span>{
    <span class="hljs-comment">// Triggers this hook call during a transaction involving a rented asset with</span>
    <span class="hljs-comment">// an active hook address attached to its metadata.</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onTransaction</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> safe,
        <span class="hljs-keyword">address</span> to,
        <span class="hljs-keyword">uint256</span> value,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">// Triggers this hook call when a rental has been started.</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onStart</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> safe,
        <span class="hljs-keyword">address</span> token,
        <span class="hljs-keyword">uint256</span> identifier,
        <span class="hljs-keyword">uint256</span> amount,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> extraData
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">// Triggers this hook call when a rental has been stopped.</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onStop</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> safe,
        <span class="hljs-keyword">address</span> token,
        <span class="hljs-keyword">uint256</span> identifier,
        <span class="hljs-keyword">uint256</span> amount,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> extraData
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;
}  </code></pre><p>At different points in the lifecycle of a rental, the hook contract can be invoked by the protocol, so not all functions in this interface have to be implemented to get the desired behavior.</p><div class="relative header-and-anchor"><h3 id="h-ontransaction-hook">OnTransaction Hook</h3></div><p>After an asset has been rented, the owner of the rental safe is now allowed to begin making transactions that originate from the rental wallet. If the hook in the order implements the <code>onTransaction</code> function, it will first be associated with a target contract. This can be any contract that the rental safe may interact with while renting the asset. If a transaction specifies this target contract in its <code>to</code> field of a transaction, then the hook contract will be called first to act as a middleware between the rental safe and the target contract.</p><p>A good example for wanting to use this hook is for an asset that might have a single-use function that can only be called once, which would want to be reserved for the original owner of the asset to call, and not the renter.</p><div class="relative header-and-anchor"><h3 id="h-onstart-hook">OnStart Hook</h3></div><p>During the fulfillment process of a rental, the <code>onStart</code> hook can be invoked to perform any actions at the very start of the rental. At the time that the <code>onStart</code> hook is invoked, the asset will have just been sent to the rental wallet. </p><div class="relative header-and-anchor"><h3 id="h-onstop-hook">OnStop Hook</h3></div><p>During the process of stopping a rental, the <code>onStop</code> hook can be invoked to perform any actions at the very end of the rental. At the time that the <code>onStop</code> hook is invoked, the asset will have already been removed from the rental wallet.</p><div class="relative header-and-anchor"><h2 id="h-registering-a-hook-with-the-protocol">Registering a Hook with the Protocol</h2></div><p>Hooks are not permission-less. Since hook contracts are middleware by nature, it is important that the use of them be limited to a well-vetted, whitelisted selection to prevent protocol exploits. Any hooks deemed fit for use can be added via the protocol's hook updating functions:</p><pre data-type="codeBlock"><code><span class="hljs-comment">/**
 * <span class="hljs-doctag">@notice</span> Connects a target contract to a hook.
 *
 * <span class="hljs-doctag">@param</span> to   The destination contract of a call.
 * <span class="hljs-doctag">@param</span> hook The hook middleware contract to sit between the call
 *             and the destination.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateHookPath</span>(<span class="hljs-params">address to, address hook</span>) <span class="hljs-title">external</span></span>; 

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@notice</span> Toggle the status of a hook contract, which defines the functionality
 *         that the hook supports.
 *
 * <span class="hljs-doctag">@param</span> hook The hook contract address.
 * <span class="hljs-doctag">@param</span> bitmap Bitmap of the status.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateHookStatus</span>(<span class="hljs-params">address hook, uint8 bitmap</span>) <span class="hljs-title">external</span></span>; </code></pre><p>For a hook to be added to the protocol, both of these functions must be invoked by an admin:</p><ul><li><p><strong>UpdateHookPath</strong>: This is used to point a hook contract at an intended target address. This will ensure that this hook contract can only be invoked when a rental wallet creates a transaction with a specific <code>to</code> address.</p></li><li><p><strong>UpdateHookStatus: </strong>This function determines at which point in the lifecycle of the rental that the hook will activate. It can be set to be invoked in any combination of: on rental start, on rental end, and during rental transactions.</p></li></ul><p>When updating a hook's status, the protocol uses a bitmap to track the exact configuration of the hook. This allows specifying any combination of hook functions as a single <code>uint8</code>. Our current implementation is as follows:</p><ul><li><p><code>00000001</code> or <code>1</code>: enables hook activation during a transaction with an active rental </p></li><li><p><code>00000010</code> or <code>2</code>: enables hook activation on rental start</p></li><li><p><code>00000100</code> or <code>4</code>: enables hook activation on renal stop</p></li></ul><p>For example, updating a hook status for both <code>onStart</code> and <code>onTransaction</code> might look like this:</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Use binary 00000011 so that the hook is enabled for onStart </span>
<span class="hljs-comment">// and onTransaction calls only</span>
guard.updateHookStatus(<span class="hljs-keyword">address</span>(hook), <span class="hljs-keyword">uint8</span>(<span class="hljs-number">3</span>));</code></pre><div class="relative header-and-anchor"><h2 id="h-using-hooks-in-rental-orders">Using Hooks in Rental Orders</h2></div><p>With a hook contract deployed and activated on the protocol, a lender will be able to specify assets to lend alongside any hooks to apply to those assets. This gives the lender full control over how their assets will be used throughout the lifecycle of the rental. </p><p>Adding a hook to a rental order requires passing in an array of hooks to the order before signing it. An example hook might look something like this:</p><pre data-type="codeBlock"><code><span class="hljs-comment">// Define the hook for the rental</span>
Hook[] <span class="hljs-keyword">memory</span> hooks <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Hook[](<span class="hljs-number">1</span>);
hooks[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> Hook({
    <span class="hljs-comment">// the hook contract to target</span>
    target: <span class="hljs-keyword">address</span>(hook),
    <span class="hljs-comment">// index of the item in the order to apply the hook to</span>
    itemIndex: <span class="hljs-number">0</span>,
    <span class="hljs-comment">// any extra data that the hook will need.</span>
    extraData: <span class="hljs-string">""</span>
});</code></pre><p>Lets break down each of these fields:</p><ul><li><p><strong>Target</strong>: The address of the hook contract being invoked. Control flow will pass to this contract during the rental process to execute additional functionality.</p></li><li><p><strong>ItemIndex</strong>: Since we use seaport to power our token swaps, the asset for which you want the hook to be activated will be the item index of the seaport <code>OfferItem</code> array in the order.</p></li><li><p><strong>ExtraData</strong>: This is any extra data that the hook contract may want when it is invoked. This allows for maximum flexibility when designing hooks since we cannot predict all possible use-cases.</p></li></ul><p>The hooks will then be passed into an <code>OrderMetadata</code> struct. Each rental order will contain a single metadata struct that defines the terms for the rental. It is crucial that both the lender and the renter have agreed upon the same terms. To guarantee this, the <code>OrderMetadata</code> struct is hashed and then placed in the <code>zoneHash</code> field of a seaport <code>OrderComponents</code> struct: </p><pre data-type="codeBlock"><code><span class="hljs-comment">// Endgame order metadata for a rental order</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">OrderMetadata</span> {
	<span class="hljs-comment">// Type of order being created.</span>
	OrderType orderType;
	<span class="hljs-comment">// Duration of the rental in seconds.</span>
	<span class="hljs-keyword">uint256</span> rentDuration;
	<span class="hljs-comment">// Hooks that will act as middleware for the items in the order.</span>
	Hook[] hooks;
	<span class="hljs-comment">// Any extra data to be emitted upon order fulfillment.</span>
	<span class="hljs-keyword">bytes</span> emittedExtraData;
}

<span class="hljs-comment">// Seaport order components</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">OrderComponents</span> {
    <span class="hljs-keyword">address</span> offerer;
    <span class="hljs-keyword">address</span> zone;
    OfferItem[] offer;
    ConsiderationItem[] consideration;
    OrderType orderType;
    <span class="hljs-keyword">uint256</span> startTime;
    <span class="hljs-keyword">uint256</span> endTime;
    <span class="hljs-keyword">bytes32</span> zoneHash; <span class="hljs-comment">// &lt;-- the one we care about</span>
    <span class="hljs-keyword">uint256</span> salt;
    <span class="hljs-keyword">bytes32</span> conduitKey;
    <span class="hljs-keyword">uint256</span> counter;
}</code></pre><p>From here, the rental order is executed with seaport. To ensure that the fulfiller has agreed to the same order metadata as the lender, they will need to include the exact same <code>OrderMetadata</code> struct into their order as <code>extraData</code>. But for this post, I wont go into how exactly a rental order is constructed. Once the order has been fulfilled, the hooks will reach Endgame's contract and will be extracted and decoded. More on this in the next section.</p><div class="relative header-and-anchor"><h2 id="h-hooks-in-action">Hooks in Action</h2></div><p>Lets look at how a hook will behave in each of the three different ways that it can be invoked during the process of a rental order.</p><div class="relative header-and-anchor"><h3 id="h-at-the-start-of-a-rental">At the Start of a Rental</h3></div><p>A hook starts its journey once it is decoded by a seaport zone contract after an order has been fulfilled. A zone contract allows you to extend the behavior of a traditional seaport order fulfillment. In our case, we perform the order fulfillment and introduce extra bookkeeping to create rentals instead of permanent trades. </p><p>During rental creation, a call to <code>_addHooks</code> is made in the Create policy <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Create.sol#L705">contract</a> that, in turn, will reach out to the hook contract to execute. It passes the rental wallet address, token, token ID, amount, and any extra data provided. There is no limit to how many hooks can be executed on a single order, so each one will be looped through and called during the rental creation process.</p><pre data-type="codeBlock" language="solidity"><code><span class="hljs-comment">// Check that the hook is reNFT-approved to execute on rental start. This checks to see </span>
<span class="hljs-comment">// if the 0x00000010 bit is active on the hooks's status bitmap in storage</span>
<span class="hljs-keyword">if</span> (STORE.hookOnStart(target)) {

    <span class="hljs-comment">// ... Do some stuff</span>

    <span class="hljs-comment">// Call the hook with data about the rented item.</span>
    IHook(target).onStart(
         rentalWallet,
         offer.token,
         offer.identifier,
         offer.amount,
         hooks[i].extraData
    );

    <span class="hljs-comment">// ... Do some stuff</span>
}</code></pre><p>From here, the contract will execute normally and allow for any extension of functionality that the implementor has introduced. </p><div class="relative header-and-anchor"><h3 id="h-at-the-end-of-a-rental">At the End of a Rental</h3></div><p>The process of executing a hook at the end of the rental is very similar to how it behaves at the start of the rental. During a rental stop, all data that was used to create the rental order must also be given to the Stop policy contract to end the order. This includes the original hook entries that were present on the order.</p><p>A call to <code>_removeHooks</code> is made in the Stop policy <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Stop.sol#L200">contract</a> that, in turn, will reach out to the same hook contracts which were passed in during the start of the order. They will also receive the same parameters from before.</p><pre data-type="codeBlock"><code><span class="hljs-comment">// Check that the hook is reNFT-approved to execute on rental stop. This checks to see </span>
<span class="hljs-comment">// if the 0x00000100 bit is active on the hooks's status bitmap in storage</span>
<span class="hljs-keyword">if</span> (STORE.hookOnStop(target)) {

    <span class="hljs-comment">// ... Do some stuff</span>

    <span class="hljs-comment">// Call the hook with data about the rented item.</span>
    IHook(target).onStop(
        rentalWallet,
        item.token,
        item.identifier,
        item.amount,
        hooks[i].extraData
    )

    <span class="hljs-comment">// ... Do some stuff</span>
}</code></pre><p>After this, the stopping of the rental will wrap up as usual. </p><div class="relative header-and-anchor"><h3 id="h-during-the-middle-of-a-rental">During the middle of a Rental</h3></div><p>Hooks can additionally be invoked throughout the duration of the rental, depending on what kinds of transactions the renter makes from their rental wallet. By default, the rental wallet disables any transactions originating from it that involve transferring or burning a rented asset.</p><p>For everything else, it leaves it up to the hooks associated with the target address in the transaction to handle whether it should either reject the transaction or, in other cases, provide additional functionality by acting as middleware.</p><p>Endgame uses <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://safe.global/wallet">Safe</a> wallets as the default rental wallet for the protocol. Any transaction coming from the rental wallet requires a signed transaction from the owner which is then passed through a custom <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Guard.sol">Guard</a> contract.</p><p>Within the guard, there is logic that determines if the target for the transaction has a hook contract associated with it. This association is set by an admin like this: </p><pre data-type="codeBlock"><code><span class="hljs-comment">// admin enables the hook path to point to the game contract</span>
guard.updateHookPath(<span class="hljs-keyword">address</span>(game), <span class="hljs-keyword">address</span>(hook));</code></pre><p>By doing this, any time the guard contract detects a transaction to <code>address(game)</code>, it will check storage for the hook contract associated with that address. Once it has the hook, it then needs to check if the hook has been activated for <code>onTransaction</code> hooks: </p><pre data-type="codeBlock"><code><span class="hljs-comment">// Checks if the 0x00000001 bit is active on the hooks's status bitmap in storage</span>
<span class="hljs-keyword">bool</span> hookIsActive <span class="hljs-operator">=</span> STORE.hookOnTransaction(hook);</code></pre><p>If the address being called in the transaction both has an associated hook contract <em>and</em><strong> </strong>the hook contract is enabled for <code>onTransaction</code>, then control flow will be passed to the hook contract with <code>_forwardToHook</code> found <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/blob/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/policies/Guard.sol#L197">here</a>.</p><pre data-type="codeBlock"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_forwardToHook</span>(<span class="hljs-params">
    <span class="hljs-keyword">address</span> hook,
    <span class="hljs-keyword">address</span> safe,
    <span class="hljs-keyword">address</span> to,
    <span class="hljs-keyword">uint256</span> value,
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data
</span>) <span class="hljs-title"><span class="hljs-keyword">private</span></span> </span>{

    <span class="hljs-comment">// Call the onTransaction hook function.</span>
    IHook(hook).onTransaction(safe, to, value, data); 

    <span class="hljs-comment">// ... Do some stuff</span>
}</code></pre><p>With this construction, the hook will get the final say on whether the transaction should be prevented. This is a great use-case for hooks that wish to prevent custom function selectors on a contract from being executed by a renter. For example, you could imagine some type of ERC721 with a function that allows combining two assets together to make a third and destroying the original two in the process.</p><p>A hook contract would allow the original owner of the asset to prevent the rented ERC721 from being able to participate in this function by defining a custom rule that will revert if any asset tries to use the function selector associated with that functionality.</p><div class="relative header-and-anchor"><h2 id="h-conclusion">Conclusion</h2></div><p>The possibilities for how hooks can be used truly have no limit. They were designed with flexibility in mind so that each project using a hook could define the use-case that works best for them. For some examples of implemented hooks, you can find them all <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/re-nft/smart-contracts/tree/59d398dbf26ec16fe37ca78609e7b9bf3f058909/src/examples">here</a>.</p><p>If you have any questions on how hooks work, feel free to reach out to myself at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="mailto:alec@021.gg">alec@021.gg</a>. And, if you want to integrate hooks into your own ERC721 or ERC1155 project, you can contact <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="mailto:naz@021.gg">naz@021.gg</a> for more details.</p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Zero To One)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/b3bc944a41e1bb5fc2db521102e9fead.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[A Tour of an NFT Rentals Front End at Cruising Altitude]]></title>
            <link>https://paragraph.com/@021/a-tour-of-an-nft-rentals-front-end-at-cruising-altitude</link>
            <guid>5Yipux3dlyjX2R04YfG8</guid>
            <pubDate>Mon, 22 Apr 2024 13:44:54 GMT</pubDate>
            <description><![CDATA[Gm all! Now that we’ve released our 021 Endgame rentals protocol into the wild we figured it was high time to get some word out concerning some of th...]]></description>
            <content:encoded><![CDATA[<p>Gm all! Now that we’ve released our <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://endgame.021.gg/">021 Endgame</a> rentals protocol into the wild we figured it was high time to get some word out concerning some of the choices and trade-offs we made while building it. This article will focus in on some of these choices and trade-offs we made for our front end tech stack. I’ll get right to the juice.</p><div class="relative header-and-anchor"><h2 id="h-the-gist">The Gist</h2></div><p>We use <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://remix.run/">Remix</a> as our front end framework. As it’s powered by <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://react.dev/">React</a> it offers us lots of goodies available in the React ecosystem. Things like <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://tanstack.com/query/latest">Tanstack Query</a> for handling any (asynchronous) state with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/jasonkuhrt/graphql-request">GraphQL Request</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wagmi.sh/">Wagmi</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://viem.sh/">Viem</a> for handling any onchain/wallet interactions, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.radix-ui.com/primitives">Radix Primitives</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://tailwindcss.com/">TailwindCSS</a> for anything UI related.</p><p>For testing our application we leverage <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://storybook.js.org/docs/writing-tests/test-runner">Storybook’s Test Runner</a> as a component testing framework and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://playwright.dev/">Playwright</a> for the heavier end-to-end testing. Both are fantastic tools to keep core functionality in check.</p><p>Everything is written in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.typescriptlang.org/">TypeScript</a>. Most of these choices were locked-in from the onset.</p><p>Now, if there’s any guiding principle in our development team, it would be: “be pragmatic”. This captures a slew of software development principles into one overarching creed. Think: YAGNI, KISS, “premature optimization is the root of all evil”, principle of least power, and probably some others. The thing with code, when you’ve worked with it long enough, is that at some point you discover the code itself has a voice. Code can’t be simply reduced to expressions, variables and operations cast from the void of the programmer’s mind. This stance will deafen you to this voice. Code whispers when you find yourself repeating things. Code speaks when you struggle to find elegance. Code shouts when you’re trying to bend an implementation to its breaking point. Pragmatism appoints code itself a powerful advisor.</p><div class="relative header-and-anchor"><h2 id="h-back-end">Back End</h2></div><p>Our back end exposes a GraphQL API to fetch and mutate data. As such we had a rich amount of choices for handling back end interfacing but opted for simplicity first. We leverage GraphQL Request in tandem with Tanstack Query.</p><p>Tanstack Query especially exposes a delightful API to handle a wide variety of data fetching (while not being limited to only data fetching) scenarios. Things like TTL, invalidation, “infinite” (e.g. paginated) queries, loading and error states, mutations, etc. are all made available in a relatively light-weight package. GraphQL Request, in tandem with GraphQL Codegen, allows us to easily and flexibly define the data we need for certain views. We try to colocate GraphQL queries where it's sensible and leverage fragments to reduce duplication. GraphQL Codegen provides tooling like <code>FragmentType</code> and <code>useFragment()</code> to handle actual types and properties returned by queries—as opposed to using GraphQL schema types as props directly. For more info, read up on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen">"fragment masking"</a>.</p><p>One fantastic benefit of using Tanstack Query is that it allows you to introduce another layer of separation between the UI and framework (like Remix). Essentially, it theoretically allows us to eject from a server framework completely and still have fully functioning views. The server side prefetching is just extra. This approach proved immensely useful later.</p><div class="relative header-and-anchor"><h2 id="h-chains-wallets-and-protocol">Chains, Wallets and Protocol</h2></div><p>A few months after Wagmi was released we integrated it into our v2 front end to simplify a lot of logic concerning wallet sessions. Similarly, to simplify internals and interaction between our v2 front end and protocol, we added support for Viem in our v2 SDK. These libraries, in our opinion, are an absolute joy to work with and provide simple yet powerful foundations of interacting with chains and wallets. This experience made it an obvious choice for Endgame as well.</p><p>We leverage WalletConnect’s Web3 Modal for wallet connections. While Wagmi does provide the option to handle the injected connector alongside, we figured presenting a unified, familiar interface regardless of connector was easiest to maintain while providing a consistent UX.</p><p>For Endgame protocol interfacing we wrote a small library wrapping parts of our back end interface, Wagmi, and—since our protocol leverages Seaport—SeaportJS with Tanstack Query. The library exposes a set of React hooks which provide us data about chain configuration, permissions, rental status, and relevant rental and safe account actions. In honesty, SeaportJS we might eject from at some later point, as it provides a lot of tooling to interface with the Seaport protocol, whereas we just use a small subset. For velocity's sake though, having SeaportJS check and ask for approvals before initiating a rental transaction is pretty nice to have.</p><p>We found Wagmi’s <code>MockConnector</code> one of its biggest boons. It allows us to, well, mock a connected wallet. We have written a small harness around the <code>MockConnector</code>, allowing us to easily connect a test wallet for our end-to-end tests, or to mimic/impersonate any wallet for manual testing and debugging purposes. In the future we might write a more in-depth article on our approach here.</p><div class="relative header-and-anchor"><h2 id="h-components-and-ui">Components and UI</h2></div><p>The danger with picking a full-fledged, off-the-shelf component library is that, at some point, you will need to fight the framework or accept some nauseating compromise. Most of these libraries (e.g. Material UI, Ant Design, Bootstrap, Chakra) design their components and interfaces, understandably, for the most broad cases. Many of these libraries also ship their own way of extending or overwriting their themes. Some have strong opinions on which custom styling solution works best. Some even provide their own styling solutions. The key thing is, with any UI library, you often find yourself writing your own wrappers anyway.</p><p>A “headless UI” library like Radix Primitives enables a very flexible approach to reusable components. It provides accessible primitives with a lot of hooks (not the React ones) to handle custom behavior. Additionally, a headless UI library is agnostic as to the preferred styling solution. You can use plain ol' CSS, Styled Components, Emotion, stylex—pick your poison, amirite? We choose Tailwind.</p><p>I’ll be honest. I love CSS. When you grok the cascade it allows for a very powerful and extensible paradigm to style user interfaces with. Recent advancements in the CSS specifications (and browsers actually implementing these) can make CSS a very fun logic playground. I love shipping things more though.</p><p>TailwindCSS allows us to mark up and style components <em>fast</em>, while retaining the ability to use plain ol’ CSS when the use-case calls for it. A specific example here would be styling third-party components. Sure, you can leverage Tailwind’s selector engine to apply styles to any child element, but seeing as regular Tailwind is hardly a feast for the eyes, overloading a <code>className</code> to style elements of a third party component will positively make your eyes bleed. Eject to a CSS file.</p><div class="relative header-and-anchor"><h2 id="h-remix">Remix?</h2></div><p>Remember how we said it’s important to listen to code?</p><p>We actually started developing Endgame on top of Next.js. We use this framework for our v2 application and thereby accrued quite some experience with it. We knew Next.js has its faults and foot guns but an experienced programmer would agree reusing a stack you have experience in, for a comparable project, is a very sensible choice.</p><p>The tech landscape—especially the front end ecosystem—is ever shifting however, and while developing Endgame we found ourselves in the midst of Next.js releasing v13 (and later v14). The v13 release added the powerful App Router, leveraging React Server Components (RSC). Initial adoption took some getting used to and we had to restructure a few modules but things chugged along fine. Over the months we would grow increasingly disenchanted by Next.js’ offering however. Partly because RSC exposed, in our opinion, a very misguided approach: that it makes sense to shoehorn a client side UI library into—<em>checks notes</em>—a server-side templating language. Partly because we kept discovering Next.js has <em>very strong</em> opinions on how to handle some web platform core APIs. We had to write a quite some indirections to deal with these (looking at you, <code>useSearchParams()</code>).</p><p>The code started shouting.</p><p>At this point, about 6 months in, we made the decision to migrate framework. We had been eying Remix for some time because of its sensible approach to expose Web APIs rather than abstract them. Moreover, much of our logic was required on the client anyway so the reduced bundle payloads were hardly worth the effort. The key thing to migrate over was routing. The rest pretty much worked out of the box because of our decoupled architecture. The core engine swap essentially took about 2-3 days. Rewriting some of the indirections took about the same time. Bells and whistles, another two days or so. The result, however, was code tranquility and a big boost to our velocity.</p><p>Remix offered us vastly more transparency and control over its layers.</p><div class="relative header-and-anchor"><h2 id="h-endgame">Endgame</h2></div><p>We’re very excited to have launched Endgame and we’re grateful we can start to share our journey with everyone. It has taught us a lot. We hope to have given you an interesting cursory look at how we architected our front end. Now, each component in our architecture could merit its own article but we wanted to provide a comprehensive overview first. Stay tuned for some more in-depth content!</p><p><strong>Note:</strong> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.rombrom.com/posts/endgame-front-end-2024/">This is co-published</a> on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://rombrom.com">rombrom.com</a>.</p>]]></content:encoded>
            <author>021@newsletter.paragraph.com (Rommert Zijlstra)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/f1b0125a92ab952e2a109657e9c6aedc.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>