<?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>0xDanki ( Tin Erispe )</title>
        <link>https://paragraph.com/@tinerispe</link>
        <description>A Friendly Donkey</description>
        <lastBuildDate>Sat, 22 Aug 2026 17:43:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>0xDanki ( Tin Erispe )</title>
            <url>https://storage.googleapis.com/papyrus_images/836b6173738d220b46f0dca4d8830ded72d1fd500ce0b99e1a69fc1880e6c83c.jpg</url>
            <link>https://paragraph.com/@tinerispe</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Cryptographic Agility]]></title>
            <link>https://paragraph.com/@tinerispe/cryptographic-agility</link>
            <guid>UegAGnj9hKPsiOG0OeVH</guid>
            <pubDate>Fri, 24 Apr 2026 02:19:21 GMT</pubDate>
            <description><![CDATA[Cryptographic agility: some systems have it, some don't. Here's what that means for ZK rollups and Ethereum. Let's go!]]></description>
            <content:encoded><![CDATA[<p><em>This post is the readable version. The full paper with the 9-dimension framework and 27 citations, lives </em><a target="_blank" rel="noopener noreferrer nofollow" class="dont-break-out graf markup--anchor markup--anchor-readOnly" href="https://zenodo.org/records/19698658"><strong><em>here</em></strong></a><em>.</em></p><p>Ok picture this.</p><p>You live in a nice, secure house. The locks are good. Best locks as a matter of fact.</p><p>Then one day a locksmith knocks on your door and says: "We found a way to pick them last week. You should replace them ASAP."</p><p>Yer normal person would respond: "Ok, replace the locks. Let's make it our weekend project."</p><p>Blockchain response: "Uhhhh so the locks are actually load-bearing. And also they're welded onto the door. And replacing them requires a governance vote across 10,000 independent node operators, three security audits, a six-month transition period, and we still haven't figured out what to do with the 400 million doors that were already opened using the old lock model. We'll get back to you."</p><p>This is the world we live in. And it's about to matter a lot.</p><p>+++++++++++++++++++++++</p><h2 id="h-cryptographic-primitives-have-expiration-dates" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Cryptographic primitives have expiration dates</strong></h2><p>MD5 was broken in 2004 but it was still in production certificates until 2012. Eight years of running around with passwords that can be decrypted with the right algorithm.</p><p>SHA-1 was theoretically dead in 2005. Then practically dead in 2017 when researchers actually demonstrated a collision. It took years of coordination across browsers, certificate authorities, and server operators to fully remove.</p><p>RSA-1024 was deprecated by NIST in 2011. But it was still running in enterprise systems a decade later.</p><p>This isn't negligence on the part of cryptographers and engineers, but simple migration friction. Knowing your primitive is broken is the easy part. Actually being capable of swapping it out without burning everything down… now that's the hard part. And da danki haz observed that different systems have wildly different abilities to do this.</p><p>The technical term for this ability is <strong>cryptographic agility</strong>. Some systems have a lot of it. And some really good, currently secure systems have very little of it.</p><p>The problem is that blockchain and ZK systems are built differently. And when you try to migrate the cryptography in an immutable ledger system, you run into a class of problems that didn't exist before.</p><p><strong>TLS has set the bar since 1999</strong></p><p>TLS is the protocol behind every HTTPS connection you've ever made. It is ancient by internet standards. It has been upgraded approximately one million times.</p><p>The reason it survives repeated upgrades is embarrassingly simple: the algorithm selection happens during the handshake. Before any actual communication, client and server have a little chat: "hey what ciphers do you support?" "oh I support these five, which ones do you have?" "cool, let's use this one."</p><p>And believe it or not, that exchange is the whole game. If you need to retire a broken algorithm, you just stop advertising it in the handshake, periodt. If you need to add a new one, you add it to the list. The rest of the protocol doesn't care. It just does whatever the handshake decided.</p><p>The algorithm is a mere parameter at the negotiation layer.</p><p>TLS 1.3 dropped over 300 weak cipher suite options. Got rid of RC4, 3DES, export ciphers. Hardened the handshake so that an attacker can't force you onto a weaker option — which yes, people tried. POODLE downgraded connections to SSL 3.0. FREAK forced export-grade RSA. LOGJAM targeted weak Diffie-Hellman.&nbsp;</p><p>The whole history of TLS attacks is basically "here's a way to abuse the flexibility we gave you" and TLS's response is always "ok we made the flexibility less flexible in that specific way."</p><p>Its high agility made hundreds of migrations painless. It's simple. But by God, it works.</p><p><strong>“Hold my beer” - ZK rollups</strong></p><p>A ZK rollup works like this: transactions happen offchain, a cryptographic proof is generated proving the transactions were valid, that proof gets submitted to an onchain verifier contract, then the verifier contract checks the proof and finalizes everything.</p><p>The security of the whole system rests on that verifier contract. All roads lead to the verifier.</p><p>Now the verifier contract is deployed onchain. It implements a specific proof verification system, be it Groth16, PLONK, or STARK, at the bytecode level. It is designed to be immutable, which is good for protocol security, but what if a component of your proving system needs to change?</p><p>Well, here's what that will look like:</p><ol><li><p>Design and implement the new proof system</p></li><li><p>Get a security audit on the new verifier circuit (several months, $200K–$500K)</p></li><li><p>If you're Groth16-based, you need a new trusted setup ceremony. So, weeks of coordination across multiple independent parties who each generate and then destroy secret randomness (but this probably won’t happen in the next migration, but that’s for another post)</p></li><li><p>Deploy the new verifier contract to L1</p></li><li><p>Run a governance process to update the bridge to recognize the new verifier</p></li><li><p>Migrate all your prover infrastructure to produce proofs under the new system</p></li><li><p>Figure out what to do with every proof ever submitted under the old system, because the new verifier cannot verify them and you kind of need to be able to validate historical state??</p></li><li><p>Pray</p></li></ol><p>No major ZK rollup has completed a full proof system migration in production. As in, it has never happened. We're not talking about something that's been done a few times slowly but something that has never been done once.</p><p>The algorithm in this case, lies in the contract, which is in the execution layer. Unfortunately the execution layer was designed to be immutable.</p><p><strong>But should you move verifiers outside contracts?</strong></p><p>The reason ZK rollups ended up like this is not stupidity. It's tradeoffs.</p><p>You want onchain verification because it gives you trustless finality. Any full node can verify the proof independently. Nobody has to trust the rollup operator, and that’s the whole point. Moving verification offchain or behind an abstraction layer reintroduces trust assumptions that ZK rollups were specifically designed to eliminate.</p><p>The cost of that decision is that the algorithm is baked in. It seemed worth paying at the time, and honestly, for the goals of the system, it kind of was? They were solving a scalability problem, not planning for a 30-year cryptographic lifecycle.</p><p>But when you look closely at why ZK circuits are so hard to migrate, there are actually two different problems bundled together. So don't mix them up or it will lead to bad conclusions.</p><p><strong>Problem one: architectural rigidity.</strong> This is fixable.</p><p>The verifier contract being the migration bottleneck is a design choice. A danki suggestion: build a verifier registry onchain that maps proof system identifiers to verifier contract addresses, and have the bridge call the appropriate verifier per batch. This separates what is being verified from which algorithm does the verifying. You keep trustless onchain verification but add algorithmic flexibility above it.</p><p>Now the absence of a specified transition protocol for historical proofs is also just a thing nobody has written down yet. When you migrate from Verifier A to Verifier B, what happens to proofs submitted under A? How long does the old verifier stay callable? What's the sunset timeline? The problem is known in the engineering community. The specification does not exist. But I believe it's fixable.</p><p><strong>Problem two: mathematical rigidity.</strong> This is not fixable with engineering.</p><p>A ZK circuit is a constraint system defined over a specific finite field. The field is not a parameter. It is present in every single constraint in the circuit <span data-name="face_holding_back_tears" class="emoji" data-type="emoji">🥹</span>A circuit built for the curve BN254 like the ones used in Groth16 and PLONK, does not call BN254. It exists in the algebraic universe defined by BN254's scalar field.</p><p>Every field arithmetic operation is an expression in that universe. Every hash gadget is optimized for that field. Every elliptic curve operation is defined by that curve.</p><p>If you want to migrate to a different algebraic universe say, one that isn't broken by Shor's algorithm. You're gonna have to rewrite every constraint in the circuit from scratch. The field size determines the size of every arithmetic operation. The hash function determines the structure of every Merkle proof gadget. Dis, you can’t parameterize.</p><p>Making ZK circuits algebraically agnostic isn't an engineering sprint. It's an open research problem. The answer to "how do you fix ZK's mathematical rigidity" is not at all "better refactoring", it's "what would a proof system designed for agility even look like." Nobody has built that in production. It's an active research area.</p><p>As I’ve said, this distinction matters: if you're a rollup team and you conflate the two, you either get false confidence ("we can just upgrade the contract, it'll be fine") or false despair ("cryptographic migration is impossible for us"). Neither is true. The architectural stuff is fixable now. The mathematical stuff requires a different kind of work.</p><p><strong>Actually the scarier problem is your Ethereum address</strong></p><p>Your Ethereum address is computed like this:</p><p>address = keccak256(pubkey)[12:]</p><p>Take not that your address is a hash of your public key. The identity is derived from the cryptographic primitive.</p><p>In TLS, your identity lives in a certificate. Your certificate gets reissued when you migrate to a new algorithm but your organizational identity stays the same. The primitive merely attests to the identity but it doesn't constitute the identity.</p><p>In Ethereum, the primitive is the identity. If you migrate from ECDSA to a post-quantum signature scheme, you generate a new keypair, which gives you a new address. That new address is a clean slate without your old token balances, history, binded identity, nothing.</p><p>For a regular user this is already bad. For a smart contract it's worse: a contract's address is derived from its deployment transaction. You literally cannot change it without redeploying, and redeployment does not inherit state.</p><p>And the migration paths that currently exist for Ethereum's quantum vulnerability:</p><p><strong>Account abstraction (EIP-7702)</strong> lets your account verify post-quantum signatures. Great! But your address is still derived from an ECDSA key. The quantum vulnerability in the identity layer is untouched. I don't have a proposed solution for this yet, but give da danki some time.</p><p><strong>New precompiles for ML-DSA / SLH-DSA</strong> lets smart contracts call PQC verification natively. Also great! But the address is still ECDSA-derived. So, also unresolved.</p><p><strong>Protocol-level hard fork replacing ECDSA</strong> is the only option that actually solves it. If addresses derive from PQC keys, the identity coupling problem is gone.</p><p>But a caveat: this would be the most expensive migration event in Ethereum's history. Because you’d be coordinating a validator-level hard fork that affects every single Ethereum user. And there is no ratified specification for how the transition would work. If Ethereum picks this path they need to answer questions like:&nbsp;</p><ul><li><p>What's the dual-acceptance window?&nbsp;</p></li><li><p>What's the ECDSA sunset timeline?&nbsp;</p></li><li><p>What does account migration look like?&nbsp;</p></li></ul><p>And if you're choosing this path, the time to specify the transition protocol is now, even though execution is years away. A specification is cheap. A specification during a crisis with a shortened quantum timeline is not. If quantum computers arrive faster than expected and the spec doesn't exist, the coordination problem becomes very difficult under time pressure.</p><p><strong>So what do you actually do</strong></p><p>If you are building something that needs to survive longer than three years:</p><p><strong>Don't put your cryptographic primitive at the execution layer.</strong> Put it where negotiation happens. If you're building a ZK protocol, think about the verifier registry early. Way easier to design in than to bolt on after the fact.</p><p><strong>Write down your state continuity protocol before you need it.</strong> What happens to your historical cryptographic artifacts when you migrate? Where does the old verifier live? What's the sunset? These things are easier to specify in advance but it could f- things up to retrofit during a migration event.</p><p><strong>Know what primitives your system depends on and where.</strong> In ZK circuits specifically, the dependencies are often invisible at the API level and pervasive at the constraint level. This is the kind of thing you'd want to enumerate before you start planning a migration, not during one. Tooling for this barely exists, so if you're working on ZK you might want to think about it now.</p><p><strong>Take harvest-now-decrypt-later seriously.</strong> Danki was hacked last week. By a RAT. And… nothing happened. Which made me think about schemes like this. Have they copied my hot wallet vault? Well it doesn't matter coz I'm comparatively too broke for this effort. But for escrows, governance, vesting contracts… it will be very worth it.&nbsp;</p><p><strong>Know whether your problem is architectural or mathematical.</strong> Because the response to each is completely different. One is a sprint. One is a research program.</p><p>+++++++++++++++++++++++</p><p>The thing that keeps me up about this — and I promise I have other things that keep me up, I'm not that far gone — is that the infrastructure we've been building for the last five years was optimized for the right things. Correctness. Performance. Trustlessness. Those are genuinely the right things to optimize for.</p><p>But there’s tradeoff between "the system is maximally correct right now" and "the system can survive being wrong about what correct means in ten years." And that tradeoff was often not made explicitly. It was made by default, so maybe it’s time to be explicit about this.</p><p>After all, you don't find out how agile your system is when everything works. You find out the moment you have to change it.</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <category>cryptography</category>
            <category>cryptographic-agility</category>
            <category>post-quantum-cryptography</category>
            <category>ethereum</category>
            <category>research</category>
            <category>blockchain</category>
            <category>protocol-engineering</category>
            <category>zero-knowledge-proofs</category>
            <category>zkp</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/851a1a4321c011bb8c2a974b5fa4817a63439df3ad3eb95d555aa4212e75eff9.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Part 3 Tutorial: Tokenizing Using ERC-3643]]></title>
            <link>https://paragraph.com/@tinerispe/part-3-tutorial-tokenizing-using-erc-3643</link>
            <guid>URZa264YrRm6YAJudqkU</guid>
            <pubDate>Tue, 07 Apr 2026 09:07:34 GMT</pubDate>
            <description><![CDATA[We cover the gaps between a working demo and a production system. Security fixes and the additional infrastructure, compliance, and tokenomics you need before even thinking about deploying this with real assets.]]></description>
            <content:encoded><![CDATA[<p>In Part 1, we built a token that understands identity and compliance. In Part 2, we connected that system to the real world through oracles and offchain integrations.</p><p>At that point, we have something that works. But working is not the same as surviving.</p><p>So in this part, I'm gonna talk about what's missing what else you need to actually tokenize a local asset. </p><h3 id="h-security-fixes-you-need-to-know-about" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Security Fixes You Need to Know About</h3><p>While building this, I found (and fixed) several critical issues that would have ruined your day in production:</p><p>1. Bank Run Prevention</p><p><code>finalizeRedemption()</code> could credit withdrawals without checking if the contract has sufficient USDC.</p><p><strong>The attack:</strong></p><ol><li><p>Oracle finalizes 100 redemptions for $1M total</p></li><li><p>Contract only has $100k USDC</p></li><li><p>First 10 users withdraw successfully</p></li><li><p>Next 90 users get <code>transfer failed</code></p></li><li><p>Their tokens are already burned</p></li><li><p>They're rugged</p></li></ol><p>fix:</p><pre data-type="codeBlock" text="// CRITICAL: Verify contract has sufficient USDC before crediting
uint256 contractBalance = i_usdc.balanceOf(address(this));
require(contractBalance &gt;= usdcAmount, &quot;insufficient contract balance&quot;);"><code><span class="hljs-comment">// CRITICAL: Verify contract has sufficient USDC before crediting</span>
<span class="hljs-keyword">uint256</span> contractBalance <span class="hljs-operator">=</span> i_usdc.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
<span class="hljs-built_in">require</span>(contractBalance <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> usdcAmount, <span class="hljs-string">"insufficient contract balance"</span>);</code></pre><p>2. Double-Redemption Prevention</p><p><strong>The attack:</strong></p><ol><li><p>User requests redemption (tokens burned)</p></li><li><p>Oracle is slow, 8 days pass</p></li><li><p>User cancels, gets tokens back</p></li><li><p>Oracle finalizes redemption (still works!)</p></li><li><p>User withdraws USDC</p></li><li><p>User has BOTH tokens AND USDC</p></li><li><p>Protocol is drained</p></li></ol><p>fix:</p><pre data-type="codeBlock" text="// Check request hasn't expired (prevent double-redemption via cancel)
if (isRequestExpired(requestId)) {
    revert RedemptionManager__RequestExpired();
}"><code><span class="hljs-comment">// Check request hasn't expired (prevent double-redemption via cancel)</span>
if (isRequestExpired(requestId)) {
    revert <span class="hljs-built_in">RedemptionManager__RequestExpired</span>();
}</code></pre><p>3. Decimal Precision</p><p>I call this the slow rug. But not really, it's just taking invisible conversion fees from your users.</p><p><strong>The theft:</strong></p><ol><li><p>Say, user has 1.999999999999999999 USDC (18 decimals) = $2</p></li><li><p>Converts to 1.999999 USDC (6 decimals) = $1.999999</p></li><li><p>Lost: $0.000001</p></li><li><p>Over 1M redemptions: <strong>$1,000 stolen from users</strong></p></li></ol><p>fix:</p><pre data-type="codeBlock" text="// Round up instead of truncate to favor users (never underpay)
return (amount + 1e12 - 1) / 1e12;"><code><span class="hljs-comment">// Round up instead of truncate to favor users (never underpay)</span>
<span class="hljs-keyword">return</span> (amount <span class="hljs-operator">+</span> <span class="hljs-number">1e12</span> <span class="hljs-operator">-</span> <span class="hljs-number">1</span>) <span class="hljs-operator">/</span> <span class="hljs-number">1e12</span>;</code></pre><p>Protocol absorbs the negligible rounding cost (&lt;$0.000001 per withdrawal) instead of users. This is okay. I did a little research on this and figured this also is how traditional accounting systems work. </p><p>There are many more, but these are the common mistakes that might get missed by yer static analyzers. </p><h3 id="h-what-this-code-is-missing" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">What This Code Is Missing</h3><p>While we may have done tackled the whole RWA architecture by now, there are still plenty of considerations that you need to take in mind before you run this in prod.</p><ol><li><p><strong>Multisig Governance</strong></p></li></ol><p>This thing still runs on a single admin/agent keys. Get at least a 3-of-5 Gnosis Safe for all privileged operations. </p><ol start="2"><li><p><strong>Circuit Breakers</strong></p></li></ol><p>Having circuit breakers require you to have a monitoring infrastructure and complex state management, but you need it. There must be a way to auto-pause the market on anomalies eg. &gt;10% price moves, high redemption volume, etc.</p><ol start="3"><li><p><strong>Real KYC/AML Integration</strong></p></li></ol><p>I have oversimplified the AMLA module on Part 1 to make it easily digestible. In reality, you need real-time sanctions screening, claim verification, chain analytics... and these require you to have licensed provider APIs and ongoing compliance.</p><ol start="4"><li><p><strong>Custody Integration for Proof of Reserves</strong></p></li></ol><p>This actually requires legal agreements and enterprise accounts. We only have mock oracles in this tutorial.</p><ol start="5"><li><p><strong>Brokerage API Integration</strong></p></li></ol><p>We have used manual oracles for simplicity, but it will be comedy if you try this in real life environments. Your brokerage first needs to have an API, and it must be accessible to you and support your stack.</p><p>If you want to launch this in production, you need:</p><ul><li><p><strong>12-15 months</strong> of development, with prerequisites</p></li><li><p><strong>5-10 person team</strong> (engineers, compliance, operations)</p></li><li><p>A huge budget (modernity is expensive)</p></li></ul><h3 id="h-how-to-philippine-rwa-tokenization" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">How to: Philippine RWA Tokenization</h3><p>If you're looking to implement this with Philippine-based assets (PSE stocks, Pagibig bonds, real estate tokens), the regulatory landscape is still on its way. But structurally, here's what you need on the tech side:</p><ol><li><p> <strong>Brokerage API:</strong> for securities like in our tutorial, API from PSE-compatible brokers (COL Financial, BPI Trade, etc.), connect to the oracles. </p></li><li><p> <strong>KYC Provider:</strong> I highly recommend we have a self-custodial DID system that is gated by National ID. This is another long topic! But if not, a traditional KYC system will work. Connect it to the IdentityRegistry contract.</p></li><li><p><strong>Custody:</strong> Local qualified custodian</p></li><li><p><strong>Regulatory registration and all legal matters </strong>(Sorry, can't help you on this one)</p></li></ol><p>But architecture-wise and code-wise, things remain. Swap the API endpoints, update the KYC provider, get local legal counsel. The smart contracts don't care if you're tokenizing AAPL or Jollibee stock. <span data-name="poultry_leg" class="emoji" data-type="emoji">🍗</span></p><h3 id="h-the-part-where-i-tell-you-not-to-use-this" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">The Part Where I Tell You Not To Use This</h3><p><strong>This code is excellent for learning.</strong><br>It shows you how identity plugs into tokens, why compliance can’t be an afterthought, how redemption actually works, and where oracles fit into the system.</p><p><strong>But this code is NOT ready for production. </strong>It’s missing the parts that actually make a system survive.</p><p><strong>So, what you can do now:</strong></p><ol><li><p>Clone the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/0xDanki/rwa-tutorial">repo</a> and run the tests</p></li><li><p>Modify the contracts for your use case</p></li><li><p>Experiment with different compliance modules</p></li><li><p>Understand the architecture before adding infrastructure</p></li></ol><p><strong>What you should NOT do with this tutorial:</strong></p><ul><li><p>Launch with real assets (seriously, don't)</p></li><li><p>Handle real user funds (I will find you)</p></li><li><p>Claim production-readiness (you will get sued)</p></li><li><p>Deploy to mainnet without modifications (you will get rekt)</p></li></ul><blockquote><p><strong>If you're building a production RWA protocol</strong> and need help with architecture, security, compliance, or just plain need advice, reach out. I've been studying its patterns for a while, and I know where the bodies are buried.</p></blockquote><h3 id="h-resources" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Resources:</h3><p>Danki's Full Tutorial code: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="git@github.com:0xDanki/rwa-tutorial">Github Repo</a></p><p>ERC-3643 Standard: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.erc3643.org/erc-3643">Docs</a></p><p>Chainlink Oracle: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.chain.link/">Docs</a></p><p>Patrick Collin's RWA Tutorial (Special thanks):<strong> </strong><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/PatrickAlphaC/rwa-creator">Github Repo</a></p><p><br>&nbsp;<br></p><p><br></p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <category>erc3643</category>
            <category>rwa</category>
            <category>tokenization</category>
            <category>defi</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/adb310edf862f90b1ede69f197a7ae6205b2f6fe392439807bcd3ffdfe3e6846.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Part 2 Tutorial: Tokenizing Using ERC-3643]]></title>
            <link>https://paragraph.com/@tinerispe/part-2-tutorial-tokenizing-using-erc-3643</link>
            <guid>3dJ95z635RVWq0rtu8hS</guid>
            <pubDate>Mon, 06 Apr 2026 13:00:36 GMT</pubDate>
            <description><![CDATA[In Part 1, I showed you something most RWA demos conveniently avoid. We didn’t start with minting until we have an identity registry. We separated compliance from token logic. We made sure the transfers weren’t just balance checks, but decisions based on who is allowed to participate. At that point, your token stopped being a toy. But everything we’ve built so far is still perfectly self-contained. Smart contracts can't make HTTP requests. They can't check your brokerage account. They can't k...]]></description>
            <content:encoded><![CDATA[<p>In Part 1, I showed you something most RWA demos conveniently avoid.</p><p>We didn’t start with minting until we have an identity registry. We separated compliance from token logic. We made sure the transfers weren’t just balance checks, but decisions based on who is allowed to participate. At that point, your token stopped being a toy.</p><p>But everything we’ve built so far is still perfectly self-contained. Smart contracts can't make HTTP requests. They can't check your brokerage account. They can't know if you, as the RWA token issuer actually own the assets.</p><p>So now we introduce the part everyone loves to oversimplify: oracles.</p><h3 id="h-step-3-oracle-layer-reality-exists-outside" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Step 3 <strong>—</strong> Oracle Layer (Reality Exists Outside)</h3><p>Not all oracles are equal. And more importantly, you will almost always need more than one. Because you’re not just asking one question. You’re asking many:</p><ul><li><p>what is the current price of this asset?</p></li><li><p>is this token actually backed?</p></li><li><p>did something happen offchain that should update state onchain?</p></li><li><p>has a redemption request been fulfilled?</p></li></ul><p>Each of these will need information from offchain. Let's put our price oracle and proof of reserve oracle now:</p><pre data-type="codeBlock" text="// src/oracle/interfaces/IPriceOracle.sol
interface IPriceOracle {
    function latestRoundData() external view returns (
        uint80 roundId,
        int256 answer,      // Price with 8 decimals
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );
}


// src/oracle/interfaces/IAttestationOracle.sol
interface IAttestationOracle {
    function hasReserves(string memory asset, uint256 amount)
        external view returns (bool);
}"><code><span class="hljs-comment">// src/oracle/interfaces/IPriceOracle.sol</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IPriceOracle</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">latestRoundData</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">returns</span></span> (<span class="hljs-params">
        <span class="hljs-keyword">uint80</span> roundId,
        <span class="hljs-keyword">int256</span> answer,      <span class="hljs-comment">// Price with 8 decimals</span>
        <span class="hljs-keyword">uint256</span> startedAt,
        <span class="hljs-keyword">uint256</span> updatedAt,
        <span class="hljs-keyword">uint80</span> answeredInRound
    </span>)</span>;
}


<span class="hljs-comment">// src/oracle/interfaces/IAttestationOracle.sol</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IAttestationOracle</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hasReserves</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> asset, <span class="hljs-keyword">uint256</span> amount</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">bool</span></span>)</span>;
}</code></pre><p>As an example, I'm using Chainlink for both price feed and Proof of Reserve attestation</p><p><strong>Again, separation of concerns:</strong> Oracles should be mere data providers, if you must enforce logic for transfers and other things, do them on a separate contract. </p><p>Moving on...</p><p>Your data should also have an expiration date.  If the oracle hasn't updated in 3 hours, the price might be wrong. Using stale data can lead to:</p><ul><li><p>Minting tokens at wrong price (loss for protocol)</p></li><li><p>Redemptions at wrong price (loss for users)</p></li><li><p>Arbitrage (loss for everyone T_T)</p></li></ul><p>So, <strong>it's better to revert than use stale data.</strong></p><p>Let's create a utility to do the staleness check on our price feed:</p><pre data-type="codeBlock" text="// src/oracle/libraries/OracleLib.sol
library OracleLib {
    uint256 private constant TIMEOUT = 3 hours;
   
    function staleCheckLatestRoundData(IPriceOracle priceFeed)
        internal view returns (uint80, int256, uint256, uint256, uint80)
    {
        (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) =
            priceFeed.latestRoundData();
       
        // Check for stale price
        uint256 secondsSince = block.timestamp - updatedAt;
        if (secondsSince &gt; TIMEOUT) {
            revert OracleLib__StalePrice();
        }
       
        // Check for invalid price
        if (answer &lt;= 0) {
            revert OracleLib__InvalidPrice();
        }
       
        return (roundId, answer, startedAt, updatedAt, answeredInRound);
    }
}"><code><span class="hljs-comment">// src/oracle/libraries/OracleLib.sol</span>
<span class="hljs-class"><span class="hljs-keyword">library</span> <span class="hljs-title">OracleLib</span> </span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">constant</span> TIMEOUT <span class="hljs-operator">=</span> <span class="hljs-number">3</span> <span class="hljs-literal">hours</span>;
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">staleCheckLatestRoundData</span>(<span class="hljs-params">IPriceOracle priceFeed</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"><span class="hljs-keyword">uint80</span>, <span class="hljs-keyword">int256</span>, <span class="hljs-keyword">uint256</span>, <span class="hljs-keyword">uint256</span>, <span class="hljs-keyword">uint80</span></span>)
    </span>{
        (<span class="hljs-keyword">uint80</span> roundId, <span class="hljs-keyword">int256</span> answer, <span class="hljs-keyword">uint256</span> startedAt, <span class="hljs-keyword">uint256</span> updatedAt, <span class="hljs-keyword">uint80</span> answeredInRound) <span class="hljs-operator">=</span>
            priceFeed.latestRoundData();
       
        <span class="hljs-comment">// Check for stale price</span>
        <span class="hljs-keyword">uint256</span> secondsSince <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">-</span> updatedAt;
        <span class="hljs-keyword">if</span> (secondsSince <span class="hljs-operator">&gt;</span> TIMEOUT) {
            <span class="hljs-keyword">revert</span> OracleLib__StalePrice();
        }
       
        <span class="hljs-comment">// Check for invalid price</span>
        <span class="hljs-keyword">if</span> (answer <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">revert</span> OracleLib__InvalidPrice();
        }
       
        <span class="hljs-keyword">return</span> (roundId, answer, startedAt, updatedAt, answeredInRound);
    }
}</code></pre><p>Great! Now there are still other things you need an oracle for. For example, making sure that the system knows whether a token redemption request has been fullfilled in the physical world. </p><p>You can find the sample code for other oracles in my repo. </p><h3 id="h-step-4-stock-token-the-orchestrator" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Step 4 <strong>—</strong> Stock Token (The Orchestrator)</h3><p>Finally, this is where we bring it all together. We'll use a token with ERC20 mechanics to represent our stock, but with additional rules:</p><pre data-type="codeBlock" text="// src/token/StockToken.sol
contract StockToken is ERC20, AccessControl {
    IIdentityRegistry private _identityRegistry;
    IModularCompliance private _compliance;
    IAttestationOracle private _attestationOracle;
    bool private _requireAttestation;
   
    function transfer(address to, uint256 amount)
        public override whenNotPaused returns (bool)
    {
        address from = msg.sender;
       
        // 1. Check freezing
        require(!_frozen[from] &amp;&amp; !_frozen[to], &quot;address frozen&quot;);
       
        // 2. Check unfrozen balance
        require(balanceOf(from) - _frozenTokens[from] &gt;= amount, &quot;insufficient unfrozen balance&quot;);
       
        // 3. Check identity verification
        require(_identityRegistry.isVerified(to), &quot;receiver not verified&quot;);
       
        // 4. Check compliance
        require(_compliance.canTransfer(from, to, amount), &quot;not compliant&quot;);
       
        // 5. Execute transfer
        _transfer(from, to, amount);
       
        // 6. Notify compliance (for state updates)
        _compliance.transferred(from, to, amount);
       
        return true;
    }
   
    function mint(address to, uint256 amount) public onlyAgent {
        require(_identityRegistry.isVerified(to), &quot;receiver not verified&quot;);
        require(_compliance.canTransfer(address(0), to, amount), &quot;mint not compliant&quot;);
       
        // Optional: Verify backing via attestation oracle
        if (_requireAttestation &amp;&amp; address(_attestationOracle) != address(0)) {
            require(
                _attestationOracle.hasReserves(&quot;STOCK&quot;, amount),
                &quot;insufficient reserves&quot;
            );
        }
       
        _mint(to, amount);
        _compliance.created(to, amount);
    }
}"><code><span class="hljs-comment">// src/token/StockToken.sol</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">StockToken</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC20</span>, <span class="hljs-title">AccessControl</span> </span>{
    IIdentityRegistry <span class="hljs-keyword">private</span> _identityRegistry;
    IModularCompliance <span class="hljs-keyword">private</span> _compliance;
    IAttestationOracle <span class="hljs-keyword">private</span> _attestationOracle;
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">private</span> _requireAttestation;
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>)
        <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">whenNotPaused</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>)
    </span>{
        <span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
       
        <span class="hljs-comment">// 1. Check freezing</span>
        <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>_frozen[<span class="hljs-keyword">from</span>] <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> <span class="hljs-operator">!</span>_frozen[to], <span class="hljs-string">"address frozen"</span>);
       
        <span class="hljs-comment">// 2. Check unfrozen balance</span>
        <span class="hljs-built_in">require</span>(balanceOf(<span class="hljs-keyword">from</span>) <span class="hljs-operator">-</span> _frozenTokens[<span class="hljs-keyword">from</span>] <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amount, <span class="hljs-string">"insufficient unfrozen balance"</span>);
       
        <span class="hljs-comment">// 3. Check identity verification</span>
        <span class="hljs-built_in">require</span>(_identityRegistry.isVerified(to), <span class="hljs-string">"receiver not verified"</span>);
       
        <span class="hljs-comment">// 4. Check compliance</span>
        <span class="hljs-built_in">require</span>(_compliance.canTransfer(<span class="hljs-keyword">from</span>, to, amount), <span class="hljs-string">"not compliant"</span>);
       
        <span class="hljs-comment">// 5. Execute transfer</span>
        _transfer(<span class="hljs-keyword">from</span>, to, amount);
       
        <span class="hljs-comment">// 6. Notify compliance (for state updates)</span>
        _compliance.transferred(<span class="hljs-keyword">from</span>, to, amount);
       
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyAgent</span> </span>{
        <span class="hljs-built_in">require</span>(_identityRegistry.isVerified(to), <span class="hljs-string">"receiver not verified"</span>);
        <span class="hljs-built_in">require</span>(_compliance.canTransfer(<span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), to, amount), <span class="hljs-string">"mint not compliant"</span>);
       
        <span class="hljs-comment">// Optional: Verify backing via attestation oracle</span>
        <span class="hljs-keyword">if</span> (_requireAttestation <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> <span class="hljs-keyword">address</span>(_attestationOracle) <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
            <span class="hljs-built_in">require</span>(
                _attestationOracle.hasReserves(<span class="hljs-string">"STOCK"</span>, amount),
                <span class="hljs-string">"insufficient reserves"</span>
            );
        }
       
        _mint(to, amount);
        _compliance.created(to, amount);
    }
}</code></pre><p>Now you can see that our StockToken does a couple of things that are not normally part of an ERC20 behavior:</p><ul><li><p>it delegates verification to IdentityRegistry</p></li><li><p>it delegates rules to Compliance</p></li><li><p>integrates data from offchain through oracles (disabled by default for this tutorial)</p></li></ul><p>Notice that transfer cannot happen if the address of <code>msg.sender</code> or <code>to</code> is not part of the verified and compliant list.</p><p>If you want to check, clone our repo and run <code>forge test --match-contract StockTokenTest</code> to verify transfers are restricted.</p><p><strong>What if the regulators required wallet freezing?</strong></p><p>In some scenarios, you might be required to add a freezing and force transfer function in case of things like court orders, violations, or suspicious activities... Here's how you do it:</p><pre data-type="codeBlock" text="mapping(address =&gt; bool) private _frozen;
mapping(address =&gt; uint256) private _frozenTokens;


function setAddressFrozen(address wallet, bool freeze) public onlyAgent {
    _frozen[wallet] = freeze;
    emit AddressFrozen(wallet, freeze, msg.sender);
}


function freezePartialTokens(address wallet, uint256 amount) external onlyAgent {
    _frozenTokens[wallet] += amount;
    emit TokensFrozen(wallet, amount);
}

function forcedTransfer(address from, address to, uint256 amount)
    public onlyAgent returns (bool)
{
    require(balanceOf(from) &gt;= amount, &quot;insufficient balance&quot;);
    require(_identityRegistry.isVerified(to), &quot;receiver not verified&quot;);
   
    // Unfreeze tokens if needed
    uint256 freeBalance = balanceOf(from) - _frozenTokens[from];
    if (amount &gt; freeBalance) {
        uint256 tokensToUnfreeze = amount - freeBalance;
        _frozenTokens[from] -= tokensToUnfreeze;
    }
   
    // Execute transfer (bypass compliance for forced transfers)
    _transfer(from, to, amount);
    _compliance.transferred(from, to, amount);
   
    return true;
}"><code><span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">bool</span>) <span class="hljs-keyword">private</span> _frozen;
<span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">private</span> _frozenTokens;


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setAddressFrozen</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> wallet, <span class="hljs-keyword">bool</span> freeze</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyAgent</span> </span>{
    _frozen[wallet] <span class="hljs-operator">=</span> freeze;
    <span class="hljs-keyword">emit</span> AddressFrozen(wallet, freeze, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
}


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">freezePartialTokens</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> wallet, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyAgent</span> </span>{
    _frozenTokens[wallet] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amount;
    <span class="hljs-keyword">emit</span> TokensFrozen(wallet, amount);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">forcedTransfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>)
    <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyAgent</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>)
</span>{
    <span class="hljs-built_in">require</span>(balanceOf(<span class="hljs-keyword">from</span>) <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amount, <span class="hljs-string">"insufficient balance"</span>);
    <span class="hljs-built_in">require</span>(_identityRegistry.isVerified(to), <span class="hljs-string">"receiver not verified"</span>);
   
    <span class="hljs-comment">// Unfreeze tokens if needed</span>
    <span class="hljs-keyword">uint256</span> freeBalance <span class="hljs-operator">=</span> balanceOf(<span class="hljs-keyword">from</span>) <span class="hljs-operator">-</span> _frozenTokens[<span class="hljs-keyword">from</span>];
    <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">&gt;</span> freeBalance) {
        <span class="hljs-keyword">uint256</span> tokensToUnfreeze <span class="hljs-operator">=</span> amount <span class="hljs-operator">-</span> freeBalance;
        _frozenTokens[<span class="hljs-keyword">from</span>] <span class="hljs-operator">-</span><span class="hljs-operator">=</span> tokensToUnfreeze;
    }
   
    <span class="hljs-comment">// Execute transfer (bypass compliance for forced transfers)</span>
    _transfer(<span class="hljs-keyword">from</span>, to, amount);
    _compliance.transferred(<span class="hljs-keyword">from</span>, to, amount);
   
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}</code></pre><p>In danki code, forced transfers bypass compliance checks but still verify receiver identity. This is intentional, court orders don't care about daily transfer limits.</p><p>Yaiy, great. We're down to the final and most important piece:</p><h3 id="h-step-5-redemption-flow" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Step 5 <strong>—</strong> Redemption Flow</h3><p>There's one little problem when redeeming a stock token: blockchains are synchronous. Stock markets are asynchronous. You can't sell a stock in the same transaction you burn the token.</p><p>In the blockchain:</p><ul><li><p>everything inside a transaction happens <strong>immediately and atomically</strong></p></li><li><p>either the whole thing succeeds, or it fails</p></li><li><p>there is no waiting, no callbacks, no “come back later”</p></li></ul><p>But you can't do that in real markets. In real stock trading, everything is <strong>delayed, queued, and processed externally:</strong></p><ol><li><p>you request to sell TSLA</p></li><li><p>broker submits order</p></li><li><p>exchange matches it</p></li><li><p>settlement happens (T+2, sometimes faster but still not instant)</p></li><li><p>cash is credited</p></li></ol><p>This can take seconds to execute and sometimes days to settle.</p><p>Solving this separates real RWA from a cosplaying ERC20. And I'll tell you how. </p><p>We create a 3-step async redemption process. The solution is a bit long this time, and please take note that THIS IS NOT MY PRODUCTION CODE, but here we go:</p><pre data-type="codeBlock" text="// src/redemption/RedemptionManager.sol
contract RedemptionManager is AccessControl, ReentrancyGuard {
    bytes32 public constant ORACLE_ROLE = keccak256(&quot;ORACLE_ROLE&quot;);
   
    struct RedemptionRequest {
        address user;
        uint256 tokenAmount;
        uint256 expectedUsdc;
        uint256 timestamp;
        bool fulfilled;
    }
   
    mapping(bytes32 =&gt; RedemptionRequest) private s_redemptionRequests;
    mapping(address =&gt; uint256) private s_withdrawalBalance;
    mapping(address =&gt; bytes32[]) private s_userRequests; // Track user's requests
   
    // Step 1: User requests redemption
    function requestRedemption(uint256 tokenAmount)
        external nonReentrant returns (bytes32 requestId)
    {
        // Calculate expected USDC
        uint256 stockPrice = i_stockPriceOracle.getPrice();
        uint256 usdcPrice = i_usdcPriceOracle.getPrice();
        uint256 expectedUsdc = (tokenAmount * stockPrice) / usdcPrice;
       
        // Generate unique request ID
        requestId = keccak256(abi.encodePacked(
            msg.sender,
            tokenAmount,
            block.timestamp,
            s_requestCounter++
        ));
       
        // Store request
        s_redemptionRequests[requestId] = RedemptionRequest({
            user: msg.sender,
            tokenAmount: tokenAmount,
            expectedUsdc: expectedUsdc,
            timestamp: block.timestamp,
            fulfilled: false
        });
       
        // Track for user
        s_userRequests[msg.sender].push(requestId);
       
        // Burn tokens immediately (prevents double-spending)
        i_stockToken.burn(msg.sender, tokenAmount);
       
        emit RedemptionRequested(requestId, msg.sender, tokenAmount, expectedUsdc);
    }
   
    // Step 2: Oracle finalizes after selling stock off-chain
    function finalizeRedemption(bytes32 requestId, uint256 usdcAmount)
        external onlyRole(ORACLE_ROLE) nonReentrant
    {
        RedemptionRequest storage request = s_redemptionRequests[requestId];
       
        require(request.user != address(0), &quot;request not found&quot;);
        require(!request.fulfilled, &quot;already fulfilled&quot;);
        require(!isRequestExpired(requestId), &quot;request expired&quot;);
       
        // Check slippage (allow up to 2% deviation)
        uint256 minAcceptable = (request.expectedUsdc * 98) / 100;
        require(usdcAmount &gt;= minAcceptable, &quot;slippage exceeded&quot;);
       
        // CRITICAL: Verify contract has sufficient USDC
        uint256 contractBalance = i_usdc.balanceOf(address(this));
        require(contractBalance &gt;= usdcAmount, &quot;insufficient contract balance&quot;);
       
        // Mark as fulfilled
        request.fulfilled = true;
       
        // Credit user's withdrawal balance
        s_withdrawalBalance[request.user] += usdcAmount;
       
        emit RedemptionFulfilled(requestId, request.user, usdcAmount);
    }
   
    // Step 3: User withdraws USDC
    function withdraw() external nonReentrant returns (uint256 amount) {
        amount = s_withdrawalBalance[msg.sender];
        require(amount &gt; 0, &quot;no balance&quot;);
       
        // Clear balance before transfer (CEI pattern)
        s_withdrawalBalance[msg.sender] = 0;
       
        // Transfer USDC (with rounding up to favor user)
        uint256 usdcAmount = (amount + 1e12 - 1) / 1e12; // 18 decimals → 6 decimals
        require(i_usdc.transfer(msg.sender, usdcAmount), &quot;transfer failed&quot;);
       
        emit Withdrawn(msg.sender, amount);
    }
   
    // Helper: Get user's pending requests
    function getPendingRequests(address user)
        external view returns (bytes32[] memory)
    {
        bytes32[] memory allRequests = s_userRequests[user];
        uint256 pendingCount = 0;
       
        // Count pending
        for (uint256 i = 0; i &lt; allRequests.length; i++) {
            if (!s_redemptionRequests[allRequests[i]].fulfilled) {
                pendingCount++;
            }
        }
       
        // Build array
        bytes32[] memory pending = new bytes32[](pendingCount);
        uint256 index = 0;
        for (uint256 i = 0; i &lt; allRequests.length; i++) {
            if (!s_redemptionRequests[allRequests[i]].fulfilled) {
                pending[index++] = allRequests[i];
            }
        }
       
        return pending;
    }
}"><code><span class="hljs-comment">// src/redemption/RedemptionManager.sol</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">RedemptionManager</span> <span class="hljs-keyword">is</span> <span class="hljs-title">AccessControl</span>, <span class="hljs-title">ReentrancyGuard</span> </span>{
    <span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> ORACLE_ROLE <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"ORACLE_ROLE"</span>);
   
    <span class="hljs-keyword">struct</span> <span class="hljs-title">RedemptionRequest</span> {
        <span class="hljs-keyword">address</span> user;
        <span class="hljs-keyword">uint256</span> tokenAmount;
        <span class="hljs-keyword">uint256</span> expectedUsdc;
        <span class="hljs-keyword">uint256</span> timestamp;
        <span class="hljs-keyword">bool</span> fulfilled;
    }
   
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">bytes32</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> RedemptionRequest) <span class="hljs-keyword">private</span> s_redemptionRequests;
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">private</span> s_withdrawalBalance;
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">bytes32</span>[]) <span class="hljs-keyword">private</span> s_userRequests; <span class="hljs-comment">// Track user's requests</span>
   
    <span class="hljs-comment">// Step 1: User requests redemption</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">requestRedemption</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenAmount</span>)
        <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bytes32</span> requestId</span>)
    </span>{
        <span class="hljs-comment">// Calculate expected USDC</span>
        <span class="hljs-keyword">uint256</span> stockPrice <span class="hljs-operator">=</span> i_stockPriceOracle.getPrice();
        <span class="hljs-keyword">uint256</span> usdcPrice <span class="hljs-operator">=</span> i_usdcPriceOracle.getPrice();
        <span class="hljs-keyword">uint256</span> expectedUsdc <span class="hljs-operator">=</span> (tokenAmount <span class="hljs-operator">*</span> stockPrice) <span class="hljs-operator">/</span> usdcPrice;
       
        <span class="hljs-comment">// Generate unique request ID</span>
        requestId <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
            <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>,
            tokenAmount,
            <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>,
            s_requestCounter<span class="hljs-operator">+</span><span class="hljs-operator">+</span>
        ));
       
        <span class="hljs-comment">// Store request</span>
        s_redemptionRequests[requestId] <span class="hljs-operator">=</span> RedemptionRequest({
            user: <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>,
            tokenAmount: tokenAmount,
            expectedUsdc: expectedUsdc,
            timestamp: <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>,
            fulfilled: <span class="hljs-literal">false</span>
        });
       
        <span class="hljs-comment">// Track for user</span>
        s_userRequests[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].<span class="hljs-built_in">push</span>(requestId);
       
        <span class="hljs-comment">// Burn tokens immediately (prevents double-spending)</span>
        i_stockToken.burn(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, tokenAmount);
       
        <span class="hljs-keyword">emit</span> RedemptionRequested(requestId, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, tokenAmount, expectedUsdc);
    }
   
    <span class="hljs-comment">// Step 2: Oracle finalizes after selling stock off-chain</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">finalizeRedemption</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> requestId, <span class="hljs-keyword">uint256</span> usdcAmount</span>)
        <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyRole</span>(<span class="hljs-params">ORACLE_ROLE</span>) <span class="hljs-title">nonReentrant</span>
    </span>{
        RedemptionRequest <span class="hljs-keyword">storage</span> request <span class="hljs-operator">=</span> s_redemptionRequests[requestId];
       
        <span class="hljs-built_in">require</span>(request.user <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"request not found"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>request.fulfilled, <span class="hljs-string">"already fulfilled"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>isRequestExpired(requestId), <span class="hljs-string">"request expired"</span>);
       
        <span class="hljs-comment">// Check slippage (allow up to 2% deviation)</span>
        <span class="hljs-keyword">uint256</span> minAcceptable <span class="hljs-operator">=</span> (request.expectedUsdc <span class="hljs-operator">*</span> <span class="hljs-number">98</span>) <span class="hljs-operator">/</span> <span class="hljs-number">100</span>;
        <span class="hljs-built_in">require</span>(usdcAmount <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> minAcceptable, <span class="hljs-string">"slippage exceeded"</span>);
       
        <span class="hljs-comment">// CRITICAL: Verify contract has sufficient USDC</span>
        <span class="hljs-keyword">uint256</span> contractBalance <span class="hljs-operator">=</span> i_usdc.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
        <span class="hljs-built_in">require</span>(contractBalance <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> usdcAmount, <span class="hljs-string">"insufficient contract balance"</span>);
       
        <span class="hljs-comment">// Mark as fulfilled</span>
        request.fulfilled <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;
       
        <span class="hljs-comment">// Credit user's withdrawal balance</span>
        s_withdrawalBalance[request.user] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> usdcAmount;
       
        <span class="hljs-keyword">emit</span> RedemptionFulfilled(requestId, request.user, usdcAmount);
    }
   
    <span class="hljs-comment">// Step 3: User withdraws USDC</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdraw</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) </span>{
        amount <span class="hljs-operator">=</span> s_withdrawalBalance[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>];
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"no balance"</span>);
       
        <span class="hljs-comment">// Clear balance before transfer (CEI pattern)</span>
        s_withdrawalBalance[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
       
        <span class="hljs-comment">// Transfer USDC (with rounding up to favor user)</span>
        <span class="hljs-keyword">uint256</span> usdcAmount <span class="hljs-operator">=</span> (amount <span class="hljs-operator">+</span> <span class="hljs-number">1e12</span> <span class="hljs-operator">-</span> <span class="hljs-number">1</span>) <span class="hljs-operator">/</span> <span class="hljs-number">1e12</span>; <span class="hljs-comment">// 18 decimals → 6 decimals</span>
        <span class="hljs-built_in">require</span>(i_usdc.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, usdcAmount), <span class="hljs-string">"transfer failed"</span>);
       
        <span class="hljs-keyword">emit</span> Withdrawn(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }
   
    <span class="hljs-comment">// Helper: Get user's pending requests</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPendingRequests</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> user</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 class="hljs-keyword">memory</span></span>)
    </span>{
        <span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">memory</span> allRequests <span class="hljs-operator">=</span> s_userRequests[user];
        <span class="hljs-keyword">uint256</span> pendingCount <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
       
        <span class="hljs-comment">// Count pending</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> allRequests.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>s_redemptionRequests[allRequests[i]].fulfilled) {
                pendingCount<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
            }
        }
       
        <span class="hljs-comment">// Build array</span>
        <span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">memory</span> pending <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes32</span>[](pendingCount);
        <span class="hljs-keyword">uint256</span> index <span class="hljs-operator">=</span> <span class="hljs-number">0</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> allRequests.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>s_redemptionRequests[allRequests[i]].fulfilled) {
                pending[index<span class="hljs-operator">+</span><span class="hljs-operator">+</span>] <span class="hljs-operator">=</span> allRequests[i];
            }
        }
       
        <span class="hljs-keyword">return</span> pending;
    }
}</code></pre><p>Redemption here happens in 3 steps:</p><ol><li><p>The user requests redemption. We estimate the USDC using oracle prices, store the request, and burn the tokens immediately. Burn first, ask questions later. No double-spending or pretending you still hold the asset.</p></li><li><p>The oracle finalizes after the stock is actually sold offchain. This is the part people like to handwave. The chain can’t wait for brokers or settlement, so we don’t fake it. Offchain happens first, then the oracle comes back and settles it onchain. We check slippage and make sure the money is actually there.</p></li><li><p>The user withdraws. For security reasons, we don’t push funds automatically... we credit a balance and let them pull. Great, fewer ways to get rekt.</p></li></ol><p>That’s async redemption for you. <strong>You can burn a token instantly but you cannot sell a stock instantly.</strong></p><p>A few more notes on this design: oracle pays for finalization and user pays for withdrawal, plus this accumulate multiple redemptions in batches for gas efficiency. Also, if user loses their <code>requestId</code>, they can't cancel expired requests. This design track all requests per user so they can always query their pending redemptions.</p><h3 id="h-step-6-offchain-integrations" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Step 6 <strong>—</strong> Offchain Integrations</h3><p>Unfortunately with RWA, <strong>the backend is part of your system.</strong></p><p>Smart contracts are great at enforcing rules. They're terrible at making HTTP requests, communicating with brokerage accounts, or knowing if you actually sold the stock.</p><p>So you need an<strong> </strong>off-chain service<strong> </strong>that listens for on-chain events, executes real-world actions (sell stock, buy USDC), and reports them back to the chain.</p><p>This is not optional. This is the entire point of RWA.</p><p>The only catch is that your backend is now a trusted party. Which means if it lies, breaks, or gets owned, your redemption flow goes with it. <strong>Audit accordingly.</strong></p><p><strong>Let's make an event listener:</strong></p><pre data-type="codeBlock" text="// Backend service listening for redemption events
contract.on(&quot;RedemptionRequested&quot;, async (requestId, user, tokenAmount, expectedUsdc) =&gt; {
    console.log(`Redemption requested: ${requestId}`);
   
    // 1. Sell stock on brokerage (Alpaca, Interactive Brokers, etc.)
    const order = await brokerage.sellStock(&quot;AAPL&quot;, tokenAmount);
    await order.waitForExecution(); // Wait for T+2 settlement
   
    // 2. Receive USD proceeds
    const proceeds = order.filledValue;
    console.log(`Sold for $${proceeds}`);
   
    // 3. Convert to USDC (via DEX or OTC)
    const usdc = await convertToUsdc(proceeds);
   
    // 4. Send USDC to RedemptionManager
    await usdcContract.transfer(redemptionManager.address, usdc);
   
    // 5. Finalize redemption on-chain
    await redemptionManager.finalizeRedemption(requestId, usdc);
   
    console.log(`Redemption finalized: ${requestId}`);
});"><code><span class="hljs-comment">// Backend service listening for redemption events</span>
<span class="hljs-keyword">contract</span>.on(<span class="hljs-string">"RedemptionRequested"</span>, async (requestId, user, tokenAmount, expectedUsdc) <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
    console.log(`Redemption requested: ${requestId}`);
   
    <span class="hljs-comment">// 1. Sell stock on brokerage (Alpaca, Interactive Brokers, etc.)</span>
    const order <span class="hljs-operator">=</span> await brokerage.sellStock(<span class="hljs-string">"AAPL"</span>, tokenAmount);
    await order.waitForExecution(); <span class="hljs-comment">// Wait for T+2 settlement</span>
   
    <span class="hljs-comment">// 2. Receive USD proceeds</span>
    const proceeds <span class="hljs-operator">=</span> order.filledValue;
    console.log(`Sold <span class="hljs-keyword">for</span> $${proceeds}`);
   
    <span class="hljs-comment">// 3. Convert to USDC (via DEX or OTC)</span>
    const usdc <span class="hljs-operator">=</span> await convertToUsdc(proceeds);
   
    <span class="hljs-comment">// 4. Send USDC to RedemptionManager</span>
    await usdcContract.<span class="hljs-built_in">transfer</span>(redemptionManager.<span class="hljs-built_in">address</span>, usdc);
   
    <span class="hljs-comment">// 5. Finalize redemption on-chain</span>
    await redemptionManager.finalizeRedemption(requestId, usdc);
   
    console.log(`Redemption finalized: ${requestId}`);
});</code></pre><p>Again, not production code. In real life, you will integrate this using a real oracle, Ethers and Alpaca. But this should give you a good overview of how it looks like.</p><p><strong>This is where the "RWA" part happens.</strong> The smart contract is just the on-chain interface. The real work (selling stocks, converting to USDC) happens off-chain. It's unavoidable. And this is why RWA is hard. Not because of Solidity. Because of integrating blockchain with traditional finance.</p><h3 id="h-at-this-point-weve-crossed-the-boundary" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">At this point, we’ve crossed the boundary.</h3><p>Our self-contained smart contract system in Part 1 is now something else entirely.</p><p>We’ve:</p><ul><li><p>introduced external data through oracles</p></li><li><p>modeled asynchronous redemption instead of pretending everything settles instantly</p></li><li><p>and came to acceptance that offchain systems are part of the architecture</p></li></ul><p>In Part 3, we’ll go through everything this code is still missing if you want to use it in production.</p><p>We’ll cover:</p><ul><li><p>common RWA security vulnerabilities and quick prevention advice</p></li><li><p>what you actually need before calling this “production-ready”</p></li><li><p>and the migration path to implementing this with Philippine assets</p></li></ul><p>Because honestly you can make these contracts compile in an afternoon. But you'll spend the rest of your time making sure it doesn’t break in the real world.</p><p>See you on the final round! <span data-name="horse" class="emoji" data-type="emoji">🐴</span></p><br><p><br></p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <category>rwa</category>
            <category>tokenization</category>
            <category>erc3643</category>
            <category>oracles</category>
            <category>chainlink</category>
            <category>alpaca</category>
            <category>ethereum</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/afe9bcb2d245468f5040d55735e81841e187f22416ed3ab9cf86016d5c8d0262.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Part 1 Tutorial: Tokenizing Using ERC-3643]]></title>
            <link>https://paragraph.com/@tinerispe/part-1-tutorial-tokenizing-using-erc-3643</link>
            <guid>vlmVa4S6gpgvIJVknrml</guid>
            <pubDate>Sat, 04 Apr 2026 13:36:20 GMT</pubDate>
            <description><![CDATA[RWAs are having a moment. So here's a 3-part tutorial on how to actually create them.]]></description>
            <content:encoded><![CDATA[<p>RWAs are having a moment.</p><p>Everyone suddenly wants to “tokenize assets” like it’s just another token deployment with more serious looking names attached to them.</p><p>It's not. An RWA system is:</p><ul><li><p>permissioned</p></li><li><p>identity-aware</p></li><li><p>compliance-heavy</p></li><li><p>and very, very allergic to “just ship it” instincts that we've grown accustomed to</p></li></ul><p>So let's try it. Let's build a <strong>demo tokenized stock</strong> using ERC-3643 — with identity, compliance, oracle-fed data, and a redemption path.</p><p><strong>What you'll learn:</strong></p><ul><li><p>How to restrict token transfers to verified investors only</p></li><li><p>Why compliance needs to be modular (spoiler: regulations change)</p></li><li><p>Why redemption can't be instant (spoiler: blockchains are synchronous, stock markets aren't)</p></li><li><p>Where oracles fit (and where they don't)</p></li></ul><p><strong>What you won't learn:</strong></p><ul><li><p>How to avoid securities regulations (you can't)</p></li><li><p>How to launch this in production (you shouldn't, without serious infrastructure and security processes)</p></li><li><p>How to get rich quick (wrong tutorial)</p></li></ul><p>Let's go mah frens <span data-name="horse_face" class="emoji" data-type="emoji">🐴</span></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/0xDanki/rwa-tutorial">FULL REPOSITORY HERE</a></p><p>---</p><h3 id="h-why-youre-getting-mocks" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Why You're Getting Mocks...</h3><p><strong>Full disclosure:</strong> I built a fully functional version of this using Alpaca API for permissionless stock trading. It works elegantly. <strong>And I'm never shipping it publicly.</strong></p><p><strong>Why?</strong></p><p>First of all, offering securities without proper licensing is a regulatory nightmare</p><p>"But it's decentralized!" is not a legal defense (ask the Luna folks), and Danki prefers devrelling over eating prison food.</p><p>Nevertheless, I still teach you the <strong>exact same architecture</strong> with mock infrastructure. You'll understand how ERC-3643 is implemented. We'll use commonly used compliance patterns. With real async redemption flow (because T+2 settlement is reality). We'll even use real oracle integration patterns.</p><p>The only difference is we'll have mock oracles instead of live API calls. When you're ready to go live (with proper licensing), you swap 50 lines of mock code for real API calls.</p><p><strong>Oh, and a bonus:</strong> I'll show you how this applies to Philippine-based assets (PSE stocks, real estate tokens) where the regulatory path is clearer.</p><h2 id="h-synthetic-vs-backed-rwas-stop-confusing-them" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Synthetic vs Backed RWAs (Stop Confusing Them)</strong></h2><p>Before we build anything, let’s clean this up.</p><p>A <strong>synthetic RWA</strong> gives you exposure. A <strong>backed RWA</strong> gives you a claim.</p><p>If you back an RWA with something that lives onchain (eg. wBTC, ETH, USDC, LUNA), it's a synthetically backed asset. In synthetically backed RWA, you can't actually claim the asset, but you can claim the tokens backing the value that it represents.</p><p>If you're RWA is backed by the exact asset that it represents, then it's an asset-backed RWA. This type is enforceable, and should be redeemable. The downside is that it is reliant on oracles that brings your offchain data onchain. </p><p>For this tutorial, we’re modeling a <strong>backed RWA architecture</strong>.</p><p>That means:</p><ul><li><p>the token represents a claim</p></li><li><p>the holder must be eligible</p></li><li><p>transfers are restricted</p></li><li><p>redemption exists</p></li></ul><p>If any of those are missing… you already know <span data-name="horse_face" class="emoji" data-type="emoji">🐴</span></p><h2 id="h-components-of-an-rwa-system" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Components of an RWA System</strong></h2><p>Before we touch code, we need to understand the moving parts.</p><pre data-type="codeBlock" text="

┌─────────────────────────────────────────────────────────────┐
│  															                      StockToken                        											    │
│  											Main ERC20 token with identity and compliance integration 								│
└────────────┬────────────────────────────────────────────────┘
     						         │
		    ┌────────┴────────┬──────────────┬─────────────┐
		    │            									   │            						   	 │            						 │
  		    ▼             							       ▼           							▼          						    ▼
┌─────────┐   ┌──────────────┐  ┌─────────┐  ┌──────────────┐
│		Identity 		│   │ 			Compliance 	     │  │ 		Oracles	  │  │			 Redemption	     │
│		Registry		│   │			 (Modular)    	   	│  │        				 │  │ 			Manager  	         │
└─────────┘   └──────────────┘  └─────────┘  └──────────────┘
"><code>

┌─────────────────────────────────────────────────────────────┐
│  															                      StockToken                        											    │
│  											Main ERC20 token <span class="hljs-keyword">with</span> <span class="hljs-keyword">identity</span> <span class="hljs-keyword">and</span> compliance integration 								│
└────────────┬────────────────────────────────────────────────┘
     						         │
		    ┌────────┴────────┬──────────────┬─────────────┐
		    │            									   │            						   	 │            						 │
  		    ▼             							       ▼           							▼          						    ▼
┌─────────┐   ┌──────────────┐  ┌─────────┐  ┌──────────────┐
│		<span class="hljs-keyword">Identity</span> 		│   │ 			Compliance 	     │  │ 		Oracles	  │  │			 Redemption	     │
│		Registry		│   │			 (Modular)    	   	│  │        				 │  │ 			Manager  	         │
└─────────┘   └──────────────┘  └─────────┘  └──────────────┘
</code></pre><h3 id="h-1-contracts-the-stack-not-just-the-token" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>1. Contracts (The Stack, Not Just the Token)</strong></h3><p>ERC-3643 is a suite, not a single contract.</p><p>At minimum, you’re working with:</p><ul><li><p><strong>Token (StockToken)</strong> → the actual asset representation</p></li><li><p><strong>Identity Registry</strong> → who is allowed to hold</p></li><li><p><strong>Claim Topics Registry</strong> → what requirements exist (KYC, accreditation, etc.)</p></li><li><p><strong>Trusted Issuers Registry</strong> → who is allowed to verify users</p></li><li><p><strong>Compliance Contract</strong> → decides if transfers are allowed</p></li></ul><p>Optional:</p><ul><li><p><strong>Oracle contracts</strong> → price, reserves, events</p></li><li><p><strong>Redemption manager</strong> → where offchain meets reality</p></li></ul><p>If your system is just:</p><pre data-type="codeBlock" text="mint( );
transfer( );"><code><span class="hljs-built_in">mint</span>( );
<span class="hljs-built_in">transfer</span>( );</code></pre><p><span data-name="loudly_crying_face" class="emoji" data-type="emoji">😭</span> please close your laptop.</p><h3 id="h-2-compliance-frameworks-yes-you-need-this" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>2. Compliance Frameworks (Yes, You Need This)</strong></h3><p>In ERC-3643 compliance means:</p><ul><li><p>wallets are mapped to identities who can carry claims</p></li><li><p>claims come from trusted issuers</p></li><li><p>compliance checks happen on every transfer</p></li></ul><p>Which means:</p><blockquote><p>your token doesn’t just care about balances<br>it cares about <strong>who is moving them</strong></p></blockquote><h3 id="h-3-oracles-plural-not-one-magic-pipe" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>3. Oracles (Plural, Not One Magic Pipe)</strong></h3><p>You don’t have “an oracle.” You have multiple.</p><p>For a tokenized stock, you might need:</p><ul><li><p><strong>price oracle</strong> → “what is this stock worth?”</p></li><li><p><strong>reserve / attestation oracle</strong> → “is this actually backed?”</p></li><li><p><strong>compliance oracle</strong> → offchain KYC systems</p></li><li><p><strong>corporate actions oracle</strong> → dividends, splits, etc.</p></li><li><p><strong>settlement signal</strong> → redemption completed</p></li></ul><h2 id="h-ok-enough-talking" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Ok, enough talking.</strong></h2><p>Now, install your Foundry and Openzeppellin. They're our dependencies. </p><h3 id="h-step-0-set-up-your-repo" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 0 — Set Up Your Repo</strong></h3><p>This will be our file structure:</p><pre data-type="codeBlock" text="/contracts
  /identity
  /compliance
  /oracle
  /redemption
  /token

/test
 	/integration 
	/unit"><code>/contracts
  /identity
  /compliance
  /oracle
  /redemption
  /token

/test
 	/integration 
	/unit</code></pre><hr><h3 id="h-step-1-identity-first-not-token-first" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 1 — Identity First (Not Token First)</strong></h3><p>Map wallets to identity:</p><pre data-type="codeBlock" text="// src/identity/IdentityRegistry.sol
contract IdentityRegistry is AccessControl {
    bytes32 public constant AGENT_ROLE = keccak256(&quot;AGENT_ROLE&quot;);
   
    // Wallet → Identity contract mapping
    mapping(address =&gt; IIdentity) private _identities;
    mapping(address =&gt; uint16) private _investorCountry;
   
    function registerIdentity(
        address wallet,
        IIdentity identityContract,
        uint16 country
    ) external onlyRole(AGENT_ROLE) {
        require(wallet != address(0), &quot;invalid wallet&quot;);
        require(address(identityContract) != address(0), &quot;invalid identity&quot;);
        require(address(_identities[wallet]) == address(0), &quot;already registered&quot;);
       
        _identities[wallet] = identityContract;
        _investorCountry[wallet] = country;
       
        emit IdentityRegistered(wallet, identityContract);
    }
   
    function isVerified(address wallet) external view returns (bool) {
        return address(_identities[wallet]) != address(0);
    }
   
    function deleteIdentity(address wallet) external onlyRole(AGENT_ROLE) {
        IIdentity oldIdentity = _identities[wallet];
        require(address(oldIdentity) != address(0), &quot;not registered&quot;);
       
        delete _identities[wallet];
        delete _investorCountry[wallet];
       
        emit IdentityRemoved(wallet, oldIdentity);
    }
}"><code><span class="hljs-comment">// src/identity/IdentityRegistry.sol</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">IdentityRegistry</span> <span class="hljs-keyword">is</span> <span class="hljs-title">AccessControl</span> </span>{
    <span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> AGENT_ROLE <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"AGENT_ROLE"</span>);
   
    <span class="hljs-comment">// Wallet → Identity contract mapping</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> IIdentity) <span class="hljs-keyword">private</span> _identities;
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint16</span>) <span class="hljs-keyword">private</span> _investorCountry;
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerIdentity</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> wallet,
        IIdentity identityContract,
        <span class="hljs-keyword">uint16</span> country
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyRole</span>(<span class="hljs-params">AGENT_ROLE</span>) </span>{
        <span class="hljs-built_in">require</span>(wallet <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"invalid wallet"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-keyword">address</span>(identityContract) <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"invalid identity"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-keyword">address</span>(_identities[wallet]) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"already registered"</span>);
       
        _identities[wallet] <span class="hljs-operator">=</span> identityContract;
        _investorCountry[wallet] <span class="hljs-operator">=</span> country;
       
        <span class="hljs-keyword">emit</span> IdentityRegistered(wallet, identityContract);
    }
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isVerified</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> wallet</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">bool</span></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">address</span>(_identities[wallet]) <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>);
    }
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deleteIdentity</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> wallet</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyRole</span>(<span class="hljs-params">AGENT_ROLE</span>) </span>{
        IIdentity oldIdentity <span class="hljs-operator">=</span> _identities[wallet];
        <span class="hljs-built_in">require</span>(<span class="hljs-keyword">address</span>(oldIdentity) <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"not registered"</span>);
       
        <span class="hljs-keyword">delete</span> _identities[wallet];
        <span class="hljs-keyword">delete</span> _investorCountry[wallet];
       
        <span class="hljs-keyword">emit</span> IdentityRemoved(wallet, oldIdentity);
    }
}</code></pre><p>No identity = no participation</p><p><strong>Some decisions I made:</strong></p><p>1. Instead of storing KYC data on-chain (expensive, privacy nightmare), we store a reference to an identity contract. The identity contract holds claims issued by KYC providers.</p><p>2. Only authorized KYC providers can register identities. This isn't permissionless DeFi anymore.</p><p>3. I did a mock country tracking. Different countries have different rules. We need to know where investors are located.</p><p><strong>What this does NOT do:</strong></p><ul><li><p>Store KYC data</p></li><li><p>Enforce transfer limits</p></li><li><p>Check prices</p></li></ul><p>We're all doing these somewhere else, because separation of concerns matters. I let each contract do one thing well.</p><p>Important note on identity:</p><p>The full ERC-3643 standard uses <strong>claims</strong> for verification:</p><pre data-type="codeBlock" text="interface IIdentity {
    function getClaim(bytes32 claimId) external view returns (
        uint256 topic,      // What type of claim (KYC, accreditation, etc.)
        uint256 scheme,     // Signature scheme
        address issuer,     // Who issued the claim
        bytes signature,    // Cryptographic proof
        bytes data,         // Claim data
        string uri          // Additional info
    );
}"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IIdentity</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getClaim</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> claimId</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">uint256</span> topic,      <span class="hljs-comment">// What type of claim (KYC, accreditation, etc.)</span>
        <span class="hljs-keyword">uint256</span> scheme,     <span class="hljs-comment">// Signature scheme</span>
        <span class="hljs-keyword">address</span> issuer,     <span class="hljs-comment">// Who issued the claim</span>
        <span class="hljs-keyword">bytes</span> signature,    <span class="hljs-comment">// Cryptographic proof</span>
        <span class="hljs-keyword">bytes</span> data,         <span class="hljs-comment">// Claim data</span>
        <span class="hljs-keyword">string</span> uri          <span class="hljs-comment">// Additional info</span>
    </span>)</span>;
}</code></pre><p>And how it works in practice is:</p><ol><li><p>User completes KYC with provider</p></li><li><p>Provider issues a claim (cryptographically signed)</p></li><li><p>Claim is stored in user's ONCHAINID contract</p></li></ol><p>Token contract verifies claim before allowing transfers. <strong>Please don't skip this if you're using on prod</strong>. It's very important for future features because:</p><ul><li><p>KYC provider can revoke claims in case user gets sactioned</p></li><li><p>Multiple providers can issue claims (no central provider)</p></li><li><p>We have privacy-preserving considerations (claim data can be hashed)</p><p>For this tutorial, we use simplified verification (just check if identity exists). Full claim verification can be added later without changing the architecture.</p></li></ul><h3 id="h-step-3-compliance-layer" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 3 — Compliance Layer</strong></h3><p>Different jurisdictions have different rules. Rules change over time. You can't hardcode compliance logic. So we'll have pluggable compliance modules</p><pre data-type="codeBlock" text="// src/compliance/ModularCompliance.sol
contract ModularCompliance is Ownable {
    IModule[] private _modules;
   
    function canTransfer(address from, address to, uint256 amount)
        external view returns (bool)
    {
        // Check ALL modules
        for (uint256 i = 0; i &lt; _modules.length; i++) {
            if (!_modules[i].moduleCheck(from, to, amount, address(this))) {
                return false; // ANY module can reject
            }
        }
        return true; // ALL modules must approve
    }
   
    function transferred(address from, address to, uint256 amount)
        external onlyToken
    {
        // Notify ALL modules (for state updates)
        for (uint256 i = 0; i &lt; _modules.length; i++) {
            _modules[i].moduleTransferAction(from, to, amount);
        }
    }
}"><code><span class="hljs-comment">// src/compliance/ModularCompliance.sol</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ModularCompliance</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Ownable</span> </span>{
    IModule[] <span class="hljs-keyword">private</span> _modules;
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">canTransfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</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">bool</span></span>)
    </span>{
        <span class="hljs-comment">// Check ALL modules</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> _modules.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>_modules[i].moduleCheck(<span class="hljs-keyword">from</span>, to, amount, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>))) {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-comment">// ANY module can reject</span>
            }
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; <span class="hljs-comment">// ALL modules must approve</span>
    }
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferred</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>)
        <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyToken</span>
    </span>{
        <span class="hljs-comment">// Notify ALL modules (for state updates)</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> _modules.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            _modules[i].moduleTransferAction(<span class="hljs-keyword">from</span>, to, amount);
        }
    }
}</code></pre><p>Notice we have two functions:</p><p><code>canTransfer()</code> (view function): Pre-transfer validation. Returns true/false. No state changes.</p><p><code>transferred()</code><strong> </strong>(state-changing)<strong>:</strong> Post-transfer state update. Modules update their internal state.</p><p><strong>Why this pattern?</strong></p><p>Because view functions can't change state. If modules need to track balances or daily limits, they need a separate function called AFTER the transfer succeeds.</p><p>This is the part that trips people up. Let me show you why it matters.</p><p>Here's an example module I created, Max Balance Limit (yor regular AMLA module):</p><pre data-type="codeBlock" text="// src/compliance/modules/MaxBalanceModule.sol
contract MaxBalanceModule is AbstractModule {
    uint256 public maxBalance;
   
    // Track balance per IDENTITY (not per wallet)
    mapping(address =&gt; uint256) public identityBalance;
   
    function moduleCheck(address /* from */, address to, uint256 amount, address /* compliance */)
        external view returns (bool)
    {
        address toIdentity = _getIdentity(to);
       
        // Check if receiver would exceed max balance
        if (identityBalance[toIdentity] + amount &gt; maxBalance) {
            return false;
        }
       
        return true;
    }
   
    function moduleTransferAction(address from, address to, uint256 amount)
        external onlyBoundCompliance
    {
        address fromIdentity = _getIdentity(from);
        address toIdentity = _getIdentity(to);
       
        // Update balances
        if (from != address(0) &amp;&amp; fromIdentity != address(0)) {
            identityBalance[fromIdentity] -= amount;
        }
       
        if (toIdentity != address(0)) {
            identityBalance[toIdentity] += amount;
        }
    }
}"><code><span class="hljs-comment">// src/compliance/modules/MaxBalanceModule.sol</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">MaxBalanceModule</span> <span class="hljs-keyword">is</span> <span class="hljs-title">AbstractModule</span> </span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> maxBalance;
   
    <span class="hljs-comment">// Track balance per IDENTITY (not per wallet)</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> identityBalance;
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">moduleCheck</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-comment">/* from */</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount, <span class="hljs-keyword">address</span> <span class="hljs-comment">/* compliance */</span></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">bool</span></span>)
    </span>{
        <span class="hljs-keyword">address</span> toIdentity <span class="hljs-operator">=</span> _getIdentity(to);
       
        <span class="hljs-comment">// Check if receiver would exceed max balance</span>
        <span class="hljs-keyword">if</span> (identityBalance[toIdentity] <span class="hljs-operator">+</span> amount <span class="hljs-operator">&gt;</span> maxBalance) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
       
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">moduleTransferAction</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>)
        <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyBoundCompliance</span>
    </span>{
        <span class="hljs-keyword">address</span> fromIdentity <span class="hljs-operator">=</span> _getIdentity(<span class="hljs-keyword">from</span>);
        <span class="hljs-keyword">address</span> toIdentity <span class="hljs-operator">=</span> _getIdentity(to);
       
        <span class="hljs-comment">// Update balances</span>
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">from</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>) <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> fromIdentity <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
            identityBalance[fromIdentity] <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amount;
        }
       
        <span class="hljs-keyword">if</span> (toIdentity <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
            identityBalance[toIdentity] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amount;
        }
    }
}</code></pre><p><strong>Why track by identity, not wallet?</strong></p><p>Because one person can have multiple wallets. If we track by wallet, they can bypass limits by using 10 different addresses.</p><p><strong>And one more, here's a critical pitfall that will break your system:</strong></p><p>Don't check sender balance in <code>moduleCheck()</code>. The module's internal balance is only updated AFTER the transfer. Checking sender balance in <code>moduleCheck()</code> creates a deadlock where transfers always fail.</p><pre data-type="codeBlock" text="// WRONG: This will always fail
function moduleCheck(address from, address to, uint256 amount) {
    address fromIdentity = _getIdentity(from);
   
    // This checks module's internal balance, which hasn't been updated yet!
    if (identityBalance[fromIdentity] &lt; amount) {
        return false; // DEADLOCK MAH FREN
    }
   
    // ...
}"><code><span class="hljs-comment">// WRONG: This will always fail</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">moduleCheck</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>) </span>{
    <span class="hljs-keyword">address</span> fromIdentity <span class="hljs-operator">=</span> _getIdentity(<span class="hljs-keyword">from</span>);
   
    <span class="hljs-comment">// This checks module's internal balance, which hasn't been updated yet!</span>
    <span class="hljs-keyword">if</span> (identityBalance[fromIdentity] <span class="hljs-operator">&lt;</span> amount) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-comment">// DEADLOCK MAH FREN</span>
    }
   
    <span class="hljs-comment">// ...</span>
}</code></pre><p>here's how to do it correctly:</p><pre data-type="codeBlock" text="// Token contract checks sender balance, module checks receiver limit
function moduleCheck(address /* from */, address to, uint256 amount) {
    address toIdentity = _getIdentity(to);
   
    // Only check receiver's limit
    if (identityBalance[toIdentity] + amount &gt; maxBalance) {
        return false;
    }
   
    return true;
}"><code><span class="hljs-comment">// Token contract checks sender balance, module checks receiver limit</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">moduleCheck</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-comment">/* from */</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>) </span>{
    <span class="hljs-keyword">address</span> toIdentity <span class="hljs-operator">=</span> _getIdentity(to);
   
    <span class="hljs-comment">// Only check receiver's limit</span>
    <span class="hljs-keyword">if</span> (identityBalance[toIdentity] <span class="hljs-operator">+</span> amount <span class="hljs-operator">&gt;</span> maxBalance) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
   
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}</code></pre><p>So you see, between the token and the modules, there is separation of functions:<br><strong>Your</strong> <strong>Token contract:</strong> Validates sender has sufficient balance (ERC20 standard)</p><p><strong>Your Compliance module:</strong> Validates business rules (receiver limits, daily caps, etc.)</p><p>Mix them up if you're having fun on test environments. But so far, this is what works. </p><p>Let's try another AMLA module <span data-name="horse_face" class="emoji" data-type="emoji">🐴</span></p><pre data-type="codeBlock" text="// src/compliance/modules/TransferLimitModule.sol
contract TransferLimitModule is AbstractModule {
    uint256 public dailyLimit;
   
    // identity =&gt; day =&gt; volume
    mapping(address =&gt; mapping(uint256 =&gt; uint256)) public dailyTransferVolume;
   
    function moduleCheck(address from, address /* to */, uint256 amount, address /* compliance */)
        external view returns (bool)
    {
        address fromIdentity = _getIdentity(from);
        uint256 today = block.timestamp / 1 days;
       
        uint256 currentVolume = dailyTransferVolume[fromIdentity][today];
       
        if (currentVolume + amount &gt; dailyLimit) {
            return false;
        }
       
        return true;
    }
   
    function moduleTransferAction(address from, address /* to */, uint256 amount)
        external onlyBoundCompliance
    {
        address fromIdentity = _getIdentity(from);
        uint256 today = block.timestamp / 1 days;
       
        dailyTransferVolume[fromIdentity][today] += amount;
    }
}"><code><span class="hljs-comment">// src/compliance/modules/TransferLimitModule.sol</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">TransferLimitModule</span> <span class="hljs-keyword">is</span> <span class="hljs-title">AbstractModule</span> </span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> dailyLimit;
   
    <span class="hljs-comment">// identity =&gt; day =&gt; volume</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">uint256</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>)) <span class="hljs-keyword">public</span> dailyTransferVolume;
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">moduleCheck</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> <span class="hljs-comment">/* to */</span>, <span class="hljs-keyword">uint256</span> amount, <span class="hljs-keyword">address</span> <span class="hljs-comment">/* compliance */</span></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">bool</span></span>)
    </span>{
        <span class="hljs-keyword">address</span> fromIdentity <span class="hljs-operator">=</span> _getIdentity(<span class="hljs-keyword">from</span>);
        <span class="hljs-keyword">uint256</span> today <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">/</span> <span class="hljs-number">1</span> <span class="hljs-literal">days</span>;
       
        <span class="hljs-keyword">uint256</span> currentVolume <span class="hljs-operator">=</span> dailyTransferVolume[fromIdentity][today];
       
        <span class="hljs-keyword">if</span> (currentVolume <span class="hljs-operator">+</span> amount <span class="hljs-operator">&gt;</span> dailyLimit) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
       
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
   
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">moduleTransferAction</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> <span class="hljs-comment">/* to */</span>, <span class="hljs-keyword">uint256</span> amount</span>)
        <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyBoundCompliance</span>
    </span>{
        <span class="hljs-keyword">address</span> fromIdentity <span class="hljs-operator">=</span> _getIdentity(<span class="hljs-keyword">from</span>);
        <span class="hljs-keyword">uint256</span> today <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">/</span> <span class="hljs-number">1</span> <span class="hljs-literal">days</span>;
       
        dailyTransferVolume[fromIdentity][today] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amount;
    }
}</code></pre><p><strong>Why daily limits?</strong></p><ul><li><p>Prevents rapid liquidation (market manipulation)</p></li><li><p>Complies with Reg D restrictions (limited resale)</p></li><li><p>Detects suspicious activity (sudden large transfers = <span data-name="rotating_light" class="emoji" data-type="emoji">🚨</span>)</p></li></ul><p><strong>Auto-reset:</strong> Counters automatically reset when <code>today</code> changes (new day = new <code>timestamp / 1 days</code>).</p><h3 id="h-great-weve-now-set-up-an-identity-and-compliance-system" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Great, we've now set up an identity and compliance system.</strong></h3><p>We now have:</p><ul><li><p>a system that knows who is allowed to participate</p></li><li><p>a compliance layer that enforces rules at transfer time</p></li><li><p>and a clear separation between identity, policy, and token behavior</p></li></ul><p>In Part 2, we’ll wire this system into the outside world:</p><ul><li><p>bring in oracle data</p></li><li><p>implement token behavior that reflects regulatory constraints</p></li><li><p>and design a redemption flow that doesn’t pretend blockchains can settle instantly</p></li></ul><p>That's all for now. Danki will be egg hunting. I'll be back on Easter! <span data-name="horse_face" class="emoji" data-type="emoji">🐴</span><span data-name="baby_chick" class="emoji" data-type="emoji">🐤</span><span data-name="rabbit_face" class="emoji" data-type="emoji">🐰</span><span data-name="egg" class="emoji" data-type="emoji">🥚</span></p><br>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <category>rwa</category>
            <category>erc-3643</category>
            <category>tutorial</category>
            <category>ethereum</category>
            <category>solidity</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/7b414cde9641c90fc32829fe6fd71a7402b8e475314948aa22493026580e9b6b.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[On RWAs]]></title>
            <link>https://paragraph.com/@tinerispe/on-rwas</link>
            <guid>dTEla625Zlj9k6wcvR8A</guid>
            <pubDate>Fri, 27 Mar 2026 07:54:51 GMT</pubDate>
            <description><![CDATA[An ERC20 token is self-contained. Its value, behavior, and rules live entirely onchain. RWAs don’t work like that. RWAs are actual legal claims represented by a token. It’s something that even I am still finding difficult to grasp as a cryptonative. Because if it’s a legal claim, it means my system is no longer just smart contracts and math. It’s:smart contracts + regulation and documentsda governmentdata pipelinesand physical and legal constraints i can’t just require() awayWhich makes me wo...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"></h2><p>An ERC20 token is self-contained. Its value, behavior, and rules live entirely onchain.</p><p>RWAs don’t work like that. RWAs are actual legal claims represented by a token.<strong> </strong>It’s something that even I am still finding difficult to grasp as a cryptonative.</p><p>Because if it’s a legal claim, it means my system is no longer just smart contracts and math. It’s:</p><ul><li><p>smart contracts + regulation and documents</p></li><li><p>da government</p></li><li><p>data pipelines</p></li><li><p>and physical and legal constraints i can’t just require() away</p></li></ul><p>Which makes me wonder how come all these cryptonative people seem to know and promote RWA when I’m still grappling with the idea that not all asset ownership can be fully decentralized.&nbsp;</p><p>It turns out, it’s because a lot of people aren’t actually thinking about RWAs in their full complexity.</p><p>They’re thinking about it like fractional NFTs… where you take an asset, split it into tokens, and assume ownership just magically works because it’s onchain.</p><h2 id="h-the-fractional-nft-brain" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>The Fractional NFT Brain</strong></h2><p>A lot of the hot air around RWAs goes like this: “we tokenize the asset and split it into shares”</p><p>That works for NFTs where the asset is the token. But if you’re planning to have that token to represent an asset that lives in the physical space, that gets a little more complex, innit?</p><p>My chad,</p><ul><li><p>the asset you’re trying to trade onchain, exists <em>outside</em> the chain</p></li><li><p>tokens merely represent and it can depeg if you didn’t do it right</p></li><li><p>ownership depends on whether someone offchain actually honors it</p></li></ul><p>So… the question should not be “how do i represent this asset”? That is the easy part. The question is,&nbsp; “What happens when I redeem this token?” and the answer should never be “There’s no path to convert it to its tangible version”</p><p>A tokenized RWA should always (and at all times) map the token to a real, enforceable claim. And we’re not done yet, there should always be a path to execute that obligation. Otherwise… you didn’t build an RWA. You built a token with really convincing, but false marketing.</p><p>Ok, let’s go back to dankying with code.&nbsp;</p><h2 id="h-this-is-why-erc-3643-exists" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>This is Why ERC-3643 Exists</strong></h2><p>This is where standards like ERC-3643 come in.</p><p>First of all, it’s not another NFT standard. You can actually use it alongside other core token standards.</p><p>It’s basically a way to introduce constraints that regular tokens don’t have. 3643 is useful when:</p><ol><li><p>you can’t let just anyone hold the token</p></li><li><p>you need customized control over its circulation</p></li><li><p>you need identity, compliance, and restrictions baked into their transfers</p></li></ol><p>And to hold those conditions true, you need an identity registry for that.</p><p>So your token now needs to care who you are and to whom you are transferring it to. Which&nbsp; sounds so not cypherpunk… but who says cypherpunk technologies are only for us weirdos at the edges. They can do wonders in making traditional systems more efficient and secure too.</p><p>That doesn’t contradict the self-sovereign internet we’re building toward. If anything, this is how we get there.</p><h2 id="h-some-things-are-fundamentally-off-chain" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Some things are fundamentally off-chain…</strong></h2><p>Blockchains are deterministic. It’s incredibly good at verifying and enforcing rules on things that live inside them. Deploy a contract and the chain enforces it predictably, and everyone agrees on account balances, signatures, and state transitions.&nbsp;</p><p>But what happens if your application needs info that doesn’t live inside the chain?</p><p>Your contract cannot tell if that building actually exists. It cannot confirm whether gold is sitting in a vault somewhere. It has no idea if a security is legally enforceable or if the issuer just decided to disappear one day. And to make things more complicated, it cannot limit the trading of these assets to only eligible entities if their identities are not verifiable onchain.</p><p>So RWAs end up depending on things that feel… uncomfortable for cryptonatives like Danki.</p><p>Because that means we’re going to rely on custodians to hold assets, oracles to report state, attestations to bridge user information, and legal agreements to actually enforce ownership. Suddenly, you’re not designing a trustless system anymore.</p><p>You’re designing a system that has a lot of trust assumptions. This is where I personally think regulators need to enter, not as a complication in the story, but as part of the design.</p><h2 id="h-regulation-is-not-optional" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Regulation Is Not Optional</strong></h2><p>If you’re building RWAs seriously, you will run into:</p><ul><li><p>KYC / AML requirements</p></li><li><p>jurisdiction restrictions</p></li><li><p>transfer limitations</p></li><li><p>licensing questions</p></li></ul><p>And as an engineer, you will need help too. Let’s say Danki haz built the cleanest, most beautiful RWA smart contract ever. But my proof of reserve connects to a spreadsheet that manually updates every week <span data-name="face_holding_back_tears" class="emoji" data-type="emoji">🥹</span></p><p>Or my oracle is an API that I just vibecoded with Claude</p><p>Or my token transfer requirements verify somebody’s eligibility through a digital pinkyswear.</p><p>My point being… the system is only as strong as its weakest offchain assumption.&nbsp;</p><p>There are problems you cannot solve as a dev. It’s sometimes wise to work with institutions to minimize these risks.&nbsp;</p><p>There’s also this instinct a lot of devs have: “we’ll deal with offchain integrations later.”</p><p>No, you won’t <span data-name="horse_face" class="emoji" data-type="emoji">🐴</span></p><p>Because by the time you’re thinking about integrations, you’ve already made design decisions that either support it or break it entirely. Who can hold the token, who can transfer it, where it’s allowed to move, how redemption works... These aren’t things you can easily undo for a token.</p><p>You don’t bolt compliance on top of an RWA system. By the time you deploy, whether it’s a purely onchain thing or not is already embedded into its logic. And pretending regulation doesn’t exist for your underlying asset doesn’t make your system more aligned with crypto ideals. It just means you’re launching a fractional memecoin, for the time being <span data-name="smirk" class="emoji" data-type="emoji">😏</span></p><h2 id="h-then-why-do-we-even-bother" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Then Why Do We Even Bother?</strong></h2><p>This is a personal opinion.</p><p>If we want our onchain systems to mature, I’m all for testing out where it can help with coordination, verifiability, and transparency. Not just inside our own ecosystems, but in places where things already matter.</p><p>I will support it for as long as it’s fair to the user’s right to privacy and digital autonomy.&nbsp;&nbsp;</p><p><strong>We’re already sitting on a $2.27 trillion onchain economy.</strong></p><p>And the more it grows, the closer we get to financial systems that aren’t just parallel experiments but real, interconnected economies. Systems that can be audited, composed, and reasoned about in ways traditional infrastructure struggles to match. RWAs are one of the paths that push us in that direction.</p><p>With RWA, you’re no longer just a smart contract dev.</p><p>You’re designing financial infrastructure and trust models that extend beyond the chain. And we need more of that.</p><hr><h2 id="h-whats-next" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>What’s Next</strong></h2><p>In the next 2 posts, we’re going to stop talking about ideas and actually build.&nbsp;</p><p>Danki will walk you through:</p><ul><li><p>what “asset-backed” really means</p></li><li><p>the core components of the ERC-3643 RWA standard</p></li><li><p>let’s tokenize a security! (for educational purposes only)<br><br></p></li></ul><p><br></p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <category>rwa</category>
            <category>blockchain</category>
            <category>tokenization</category>
            <category>erc-3643</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/1cd259e69bb4bb370f8916285771d90a749f95ffc8b395aa6fe06311cbb1bb1b.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Blockchain for Enterprise]]></title>
            <link>https://paragraph.com/@tinerispe/blockchain-for-enterprise</link>
            <guid>BzKLmdaMKP3i9rfstk98</guid>
            <pubDate>Fri, 14 Nov 2025 09:26:52 GMT</pubDate>
            <description><![CDATA[People tend to overestimate how easy it is to create a blockchain. Just because you were able to deploy a network doesn’t make you an expert on blockchain. As a matter of fact, even an intern can do it in minutes. Here, try it. You know what else is easy to deploy? A webpage. Creating a blockchain is easy, and you can do it at zero cost and effort for as long as you don’t care about the design and spec of your network. Understanding the engineering constraints to design a secure and functiona...]]></description>
            <content:encoded><![CDATA[<p>People tend to overestimate how easy it is to create a blockchain.</p><p>Just because you were able to deploy a network doesn’t make you an expert on blockchain. As a matter of fact, even an intern can do it in minutes. Here, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.zeeve.io/platform">try it</a>.</p><p>You know what else is easy to deploy? A webpage.</p><p>Creating a blockchain is easy, and you can do it at zero cost and effort for as long as you don’t care about the design and spec of your network.</p><p>Understanding the engineering constraints to design a secure and functional blockchain architecture already takes real knowledge and skills. That’s only to get the theory right. Implementation is another thing. Yet somehow all these noise led to discussions about neither theory nor implementation. If you ask me, that’s… not a great sign.</p><p>Anyway, maybe danki iz just impatient 😌⚙️</p><h3 id="h-cryptonatives-think-everything-works-like-a-public-chain" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Cryptonatives Think Everything Works like a Public Chain</strong></h3><p>From the cryptonative perspective, “using blockchain” means institutions forking their own version of the OP Stack and pretending it’s decentralization and transparency.</p><p>This is absurd.</p><p>Public chains like Ethereum, L2s, and alternative L1’s (which are privately owned chains with a wee more nodes) are engineered for open participation, fully transparent states, and global settlement.</p><p>This design is beautiful for permissionless systems… but is horrible for enterprise or government environments. It’s also why I don&apos;t recommend dumping our data and processes straight to a public chain for the sake of verifiability. Public chains are also perfect adversarial environments and are an excellent mass surveillance tool.</p><p><em>So, should we use a database instead? Private chain?</em></p><p>Maybe. You decide, compadre. But that leads us to…</p><h3 id="h-the-real-work-actually-knowing-your-constraints-and-working-with-them" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>The Real Work: Actually Knowing Your Constraints and Working with them</strong></h3><p>There are three challenges facing blockchain use for enterprise:</p><ol><li><p><strong>Privacy</strong> — enterprise data cannot be globally visible by default</p></li><li><p><strong>Custodiality</strong> — access must be controlled and enforceable</p></li><li><p><strong>Interoperability</strong> — systems must remove data siloes and permissionlessly extend, compose, and integrate with others. Bonus if it’s compatible with other global digital ecosystems.</p></li></ol><p>And since many of you are thinking of the recent “Philippines explores blockchain for transparency” saga…Yes. Let’s design around that.</p><p>There are a few viable options, each with tradeoffs. But first:</p><p><strong><em>“Can’t we just use a shared, append-only database?”</em></strong></p><p>Using a database is actually great, but here’s why you need more than that:</p><p>1. For governments and businesses, storing and governing user data is a liability</p><p>Unless you’re selling data to advertisers or training AI models, storing personal data for millions of people is expensive, and dangerous because it makes you a target. Blockchains allow self-governed data through private keys. Databases + PKI can’t do that.</p><p>2. Human-operated systems is not the future of France</p><p>Humans are slow and prone to mistakes. The less manual handling, the fewer middlemen, the fewer signatures and offices, the more honest and efficient a system becomes. Blockchain provides neutral, programmable rails for automated coordination at scale.</p><p>3. Data permanence matters</p><p>Multiple diverse validators (even in a permissioned consortium) means durability and accountability. Combined with public anchors, it makes any form of cover-up difficult.</p><p><strong><em>“Okay Danki, what are our options?”’</em></strong></p><p>There’s no single “best architecture”, only tradeoffs.</p><p>The only rule is that we design around the constraints.</p><p>Different institutions will pick different points in the spectrum based on:</p><ul><li><p>budget</p></li><li><p>threat model</p></li><li><p>compliance requirements</p></li><li><p>risk appetite</p></li><li><p>engineering maturity</p></li><li><p>and how allergic they are to devops</p></li></ul><p>So here are your options:</p><h3 id="h-option-1-a-private-walled-garden-chain" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Option 1: A Private Walled-Garden Chain</h3><p>You use Hyperledger Fabric or Corda, throw in a few millions, spin up your validators, and create your own enclosed world.</p><p><strong>Great if:</strong></p><p>You have big money and want full control of the network, automation via smart contracts, and internal auditability of your processes.</p><p><strong>Advantages:</strong></p><ul><li><p>privacy by default</p></li><li><p>no strangers validating your data</p></li><li><p>smart contract automation</p></li><li><p>internal accountability</p></li></ul><p><strong>Downsides:</strong></p><ul><li><p>devops hell (validators, nodes, uptime, monitoring)</p></li><li><p>zero decentralization</p></li><li><p>zero interoperability with public chains ergo, zero public accountability</p></li><li><p>becomes a glorified distributed database</p></li><li><p><strong>if you’re poor, do not attempt</strong></p></li></ul><h3 id="h-option-2-a-hybrid-private-to-public-chain" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Option 2: A Hybrid Private to Public Chain</h3><p>You work with Hyperledger Besu or Polygon Miden/Supernets (they keep changing the name). Either way, your private chain keeps the data private while pushing public data to a public chain for accountability.</p><p><strong>Great if:</strong></p><p>You want a private chain but still need to prove things publicly. And you also want compatibility with global public-chain ecosystems. And you have more money to spend than the person choosing Option 1.</p><p><strong>Advantages:</strong></p><ul><li><p>privacy preserved</p></li><li><p>auditability retained</p></li><li><p>no mass surveillance</p></li><li><p>you choose what becomes public</p></li><li><p>interoperability with the public chain ecosystem (Ethereum or Polygon)</p></li></ul><p><strong>Downsides:</strong></p><ul><li><p>gas fees still exist</p></li><li><p>adversarial actors can still analyze data if insecure implementation</p></li><li><p>devops still exists</p></li><li><p>even worse for the budget. because now, you’d have to pay gas to the public chain on top of your private chain expenses.</p></li></ul><p><strong>Note:</strong></p><p>pls don’t be cringe.</p><p>Publish <strong>hash commitments</strong>, NOT your entire dataset through NFT metadata. Nobody wants to accidentally leak the entire payroll onchain.</p><h3 id="h-option-3-trusted-execution-environments-tees-aka-let-hardware-be-our-witness" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Option 3: Trusted Execution Environments (TEEs) aka Let Hardware Be Our Witness</h3><p>Instead of building a private chain, you do all the transactions on a special, institution-owned hardware (a physical atm machine, computer, or vault). Assume it’s an impenetrable enclave that nobody can tamper with. Post attested results onchain.</p><p><strong>Great if:</strong></p><p>You want private transactions but want to post proofs publicly onchain. Security matters less than speed and cost efficiency. Probably not as expensive as options 1 and 2, but you have to trust a lot of vendors.</p><p><strong>Advantages:</strong></p><ul><li><p>data stays private</p></li><li><p>very small onchain footprint</p></li><li><p>fast and relatively cheaper</p></li><li><p>conceptually simple, even old people can understand it</p></li></ul><p><strong>Downsides:</strong></p><ul><li><p>you have to trust the manufacturers</p></li><li><p>you have to trust that it can’t create a signature if it’s tampered</p></li><li><p>enclave vulnerabilities exist (and have been exploited)</p></li><li><p>vendor lock-in</p></li><li><p>might be inconvenient for users to be physically present to use your system</p></li></ul><p>It’s “privacy by hardware,” not privacy by design. Still valid, but choose wisely.</p><h3 id="h-option-4-onchain-commitments-with-zero-knowledge-proofs-zkp" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Option 4: Onchain Commitments with Zero-Knowledge Proofs (ZKP)</h3><p>You create an identity system. Keep your database. Keep your backend chaos. Keep your entire Frankenstein architecture.</p><p>Add a module that generates zero-knowledge proofs whenever something important happens:</p><ul><li><p>bids</p></li><li><p>requests</p></li><li><p>balance updates</p></li><li><p>awarding</p></li><li><p>fund releases</p></li></ul><p>Maybe even partner with banks to allow consented creation of proofs for certain accounts. Proofs get published publicly onchain. Data stays private. Verification can happen publicly or privately.</p><p><strong>Great if:</strong></p><p>You want to modernize slowly. You don’t expose the data to the whole globe but only to a select population who’s concerned. You just commit to its existence and integrity. You’re not ready for blockchain’s interoperability and automation, but you somehow have access to a rare cryptid dat is a ZK Engineer.</p><p><strong>Advantages:</strong></p><ul><li><p>raw data never touches the chain</p></li><li><p>cheap on L2s (₱20 or less per proof)</p></li><li><p>public accountability without exposing internals</p></li><li><p>works with your current databases and workflows</p></li><li><p>incremental modernization</p></li></ul><p><strong>Downsides:</strong></p><ul><li><p>ZK engineers are costly and are difficult to find</p></li><li><p>ZK circuits can be fragile if done incorrectly</p></li><li><p>It’s a little slow to create proofs, though not that bad</p></li><li><p>side-channel leaks are still possible, SNARKS aren’t quantum safe yada yada…</p></li><li><p>requires actual cryptographic understanding and discipline</p></li><li><p>if you don’t design your architecture to be interoperable, then it won’t be</p></li></ul><p>I’m biased. But unless we invest in local cryptographic engineering education, we’ll be importing foreign crypto talents to the tune of $300k/year per engineer.</p><p>So if you actually want a Philippines that can build privacy-preserving national infrastructure… Maybe– JUST MAYBE – support Danki’s Cipher PH next year. We’ll want Filipino ZK wizards instead of paying Silicon Valley rent.</p><h3 id="h-so-which-architecture-should-the-philippines-use" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">So Which Architecture Should the Philippines Use?</h3><p>Hekk… that’s a tough one.</p><p>You could blend everything. You could phase it out. You could even throw darts at a diagram and see what sticks. Very on-brand for us, tbh.</p><p>If you’d ask me: Start with onchain proofs. They’re small, manageable, and low-drama. Private chains and DIDs can come later when everyone’s blood pressure has stabilized.</p><p>But I’ll favor whichever path won’t collapse under the weight of Filipino bureaucracy + budget + politics + lack of technical crypto talents.</p><p>The one that fits our constraints, our budget, our reality… not the fantasy someone saw on a conference slide deck. And definitely not the slapdash horrors that were only created out of spite.</p><p>We just need to build carefully, add tech where it actually helps, and avoid swan-diving into the deep end without floaters.</p><p>Nice and steady. Good math, low chaos. 🐴💛✨</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/15117bf10dbbb038500697c3bdc8e1844c949c32c9fd0fdc1295b268b661dc0f.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Can They Really Sell Your Eyeball Scans? A Technical Review of World]]></title>
            <link>https://paragraph.com/@tinerispe/can-they-really-sell-your-eyeball-scans-a-technical-review-of-world</link>
            <guid>g8PHKA122lskhsjJV6EF</guid>
            <pubDate>Wed, 02 Jul 2025 21:49:48 GMT</pubDate>
            <description><![CDATA[Here I am, resurrecting my blog like a dusty necromancer coming back for one last summon. And what brought me back from the digital grave? Larpers. Everywhere. People posing as crypto &apos;experts&apos; when they haven’t done the actual work of researching whatever the hekk it is they are talking about. It’s all vibes and appearances and no substance. Lately, the Orb and World has been made an antagonist in the Filipino crypto scene. And everyone suddenly became a data privacy expert and mor...]]></description>
            <content:encoded><![CDATA[<p>Here I am, resurrecting my blog like a dusty necromancer coming back for one last summon.</p><p>And what brought me back from the digital grave?</p><p>Larpers. Everywhere. People posing as crypto &apos;experts&apos; when they haven’t done the actual work of researching whatever the hekk it is they are talking about. It’s all vibes and appearances and no substance.</p><p>Lately, the <em>Orb</em> and World has been made an antagonist in the Filipino crypto scene. And everyone suddenly became a data privacy expert and morality champion overnight.</p><p>But y’all… the code is literally open-source. Public. On GitHub. Verifiable and transparent. Yet most of the noise came from people whose deepest technical dive was into Tiktok, probably. I mean opinions are fine. Sure, pop off. But when you&apos;re out here spreading crypto misinformation like it&apos;s your side hustle, that’s not just lazy. That’s lying. Please don’t do that, it’s bad.</p><p>Anyway, because nobody’s doing it, Danki went through World’s codebase to dissect and translate it for humans. But first…</p><h2 id="h-why-do-we-even-need-poh" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Why do we even need PoH?</strong></h2><p>We are entering a phase of the internet where:</p><ul><li><p>Bots have social media accounts</p></li><li><p>Bots make video calls and phone calls</p></li><li><p>Bots make fake IDs</p></li><li><p>Bots have crypto wallets</p></li><li><p>Bots can vote on-chain</p></li><li><p>Bots have fans clubs</p></li></ul><p>My fren, it has become normal to not know whether you&apos;re reading content from a person or a language model. It’s already bad. But it’s gonna get weirder.</p><p>AI systems are already powerful enough to influence, deceive, vote, and even transact like humans. It’s already anarchy, albeit one where you only detect the tip of the iceberg.</p><p>My point being.. Proof of Humanity, or a system that makes it easy to prove that you’re a human is going to impact people who don’t even know about crypto.</p><p>Just some ways that immediately come to mind:</p><ul><li><p><strong>Preventing Deepfakes</strong>: Video calling and communication platforms can integrate PoH to make sure that the user is talking to a real human.</p></li><li><p><strong>Pseudonymous Communication:</strong> You can even prove humanity without doxxing who you are</p></li><li><p><strong>Aid distribution</strong>: Not saying it’s UBI, but any aid system can eliminate ghost beneficiaries, fraud, and identity duplication through PoH.</p></li><li><p><strong>Secure KYC over the Internet</strong>: You don’t have to travel miles and queue up to register a government ID. If you have a valid PoH then it will be possible to do it at home.</p></li></ul><p>So as you can see, it isn’t just some crypto flex. It’s more like a universal captcha but one that cannot be ‘outsourced’ and one that doesn’t annoy u by asking to find traffic lights in 144p JPEGs.</p><p>Now, going back to the code.</p><p>++++++</p><h2 id="h-how-the-orb-actually-works" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>How the Orb Actually Works</strong></h2><p>There’s a large codebase, but here are the main components you can look into if you want to know what the system does to your iris scan:</p><ul><li><p><code>open-iris</code>: a pipeline that converts raw iris images into iris codes (has three parts: segmentation → normalization → feature extraction).In human language: It takes note of your iris’ features and extracts those to generate a binary code that serves as your identifier.<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/worldcoin/open-iris">Open Iris Repo</a></p></li><li><p><code>orb-software</code>: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/worldcoin/orb-software">Orb Software Repo</a></p><ul><li><p><code>orb-core/do_signup</code> calls image capture, iris processing, then signs iris codes and sends them to the backend uniqueness service, which is closed-source (bleh)<strong>In human language:</strong> It calls all the code needed to process your iris data and signs it to make sure everything came from the orb and not somewhere else</p></li><li><p><code>orb-secure-element</code> &amp; <code>orb-attest</code> modules that generate hardware-backed attestations; the backend validates these signatures. <strong>In human language:</strong> It shows how your iris code data is encrypted both at rest and in transit.</p></li></ul></li><li><p><code>signup-sequencer</code>: takes your identity commitments, or 32-byte hashes that were produced after an iris is verified and updates the Merkle Tree. <strong>In human language:</strong> Generates a <strong>ZK proof</strong> that says “this person was added to our list of real humans”. This ZK proof will not tell who you are or where you scanned or what your iris code is, nothing. Just that you were part of the list<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/worldcoin/signup-sequencer">Signup Sequencer Repo</a></p></li></ul><p>Ok, now let’s begin with the lines we’re most concerned about:</p><p>Do they delete the iris scans? Yes. But not by calling a <code>delete()</code> function. The Orb processes iris images <strong>entirely in memory</strong> and (because this is written in Rust) it discards it automatically right after use.</p><pre data-type="codeBlock" text="let frame = eye_tracker.capture_frame().await?;
let iris_code = compute_iris_code(&amp;frame)?;
"><code>let frame <span class="hljs-operator">=</span> eye_tracker.capture_frame().await?;
let iris_code <span class="hljs-operator">=</span> compute_iris_code(<span class="hljs-operator">&#x26;</span>frame)?;
</code></pre><p>The <code>image</code> variable is processed in these lines, but <strong>never written to disk or saved.</strong></p><p>I asked my AI to scan of the codebase for any file I/O routines (<code>File::create</code>, <code>fs::write</code>, etc.) during the processes in the Orb-core pipeline. Looks like there’s none. So they’re really not saving it.</p><p>Then what are they getting from the scans?</p><p>An <em>iris code</em> that looks like a nonsensical bunch of 1’s and 0’s. You can check the <code>open-iris</code> repo to see how they transform it. The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://world.org/blog/engineering/iris-feature-extraction">binarization algorithm is explained here</a> in a more understandable language, but you can knock yourselves out analyzing the code <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/worldcoin/open-iris/tree/main/src/iris/nodes/iris_response">here</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/worldcoin/open-iris/tree/main/src/iris/nodes/templates_filter">here</a>.</p><p>Do they even save that garble of a binary number that is your iris code? Also no.</p><p>They only use it to check whether you’re a unique human being. They split it up into three parts and use SMPC (Secure Multiparty Computation) to compare chopped up shares of it with all the chopped up shares of the other iris codes that were ever created. Do note that this part is closed-source. But it’s still nice to read their paper about <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eprint.iacr.org/2024/705">large scale MPC</a>.</p><p>There’s no more need for that iris code once your humanity has been proven. This is where ZK comes in. The iris code is made into a <em>commitment hash</em> through a (boo) another closed-source but <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://bitcoinke.io/wp-content/uploads/2024/03/WorldCoin-ORB-Security-Review-BitKE.pdf">audited</a> code by Trail of Bits.</p><p>Now your commitment hash is hang by the Merkle Tree, any application that integrates with World ID can verify the proofs generated by your account.</p><p>Side: I would personally trust Trail of Bits because they’re one of the reputable auditing names out there (and they basically taught me how to fuzz test), but to each his own.</p><p>So let’s go back to what these clout-chasers were saying:</p><h3 id="h-theyre-storing-my-sensitive-iris-data-to-do-whatever-the-hekk-they-want-with-it" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>“They&apos;re storing my sensitive iris data to do whatever the hekk they want with it!&quot;</strong></h3><p>No, Chad. They don’t even <em>have</em> your iris code. What they have is an encrypted representation of the existence of your iris code. Which can’t be unhashed by anyone, not even by World themselves.</p><p>If you can follow the flow: no biometric data stored = no breach of trust. Unless you plan to sell your fingers, phone, and World private key to the same person on Facebook Marketplace, nobody&apos;s getting anything from anyone.</p><h3 id="h-but-theyre-going-to-sell-the-data-they-got-through-the-orb" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>&quot;But they&apos;re going to sell the data they got through the Orb!&quot;</strong></h3><p>An even spicier conspiracy. What will they sell? The commitment hash of the existence of your iris code that not even them can decrypt? Who’s gonna buy that Chad?</p><h3 id="h-then-why-are-they-buying-my-biometrics-in-exchange-for-tokens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>&quot;Then why are they buying my biometrics in exchange for tokens?&quot;</strong></h3><p>Okay, I get it somehow. But here’s a reality check:</p><ul><li><p>Most people don’t know why everyone needs PoH</p></li><li><p>Most people don’t know how Web3, PoH, or even single-factor authentication works.</p></li><li><p>But they all <em>need</em> the protection that PoH provides.</p></li></ul><p>Not everyone has to understand combustion engines to ride a bus. They just need a ride.</p><p>You can’t onboard 8 billion people by giving them a 5-hour cryptography lecture. So what do you do? You incentivize adoption.</p><p>It’s kind of a lesson in game theory. If you can’t convince the granmas to care, then flip the incentives. Make doing the right thing the easiest option. World is not paying you for your data. It’s rewarding you so that you would participate.</p><h3 id="h-theyre-using-politicians-to-get-people-to-scan-their-eyeballs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>&quot;They&apos;re using politicians to get people to scan their eyeballs!&quot;</strong></h3><p>LOL. Bro. Danki tries to be an honest, accurate, kind person with integrity. But guess what wins people over most of the time? Appearances. Loss aversion and chasing incentives. You think people lined up for an Orb because they read the World whitepaper? No. They heard they’d get free money.</p><p>And yes, politicians got involved. Is that ideal? No. Is that efficient? Yes. Is it dangerous? Only if they can tamper with the Orb.</p><p>Which leads me to…</p><h3 id="h-can-somebody-tamper-with-the-orb-to-get-my-original-iris-images" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>&quot;Can somebody tamper with the Orb to get my original iris images?&quot;</strong></h3><p>Yes, but it’s extremely difficult to do so undetected. There are many layers of detection in the orb:</p><ul><li><p>The open-source <code>orb-attest</code> component interacts directly with the Orb’s secure element, which holds private signing keys in a tamper-resistant hardware chip.</p></li><li><p>Measured Boot &amp; Orb OS - closed source but if we’d believe the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/worldcoin/orb-software/tree/main/attest">README</a>, it uses secure enclave and secure element for key operations.</p></li><li><p>The <code>orb-firmware</code> project includes tamper-detection logic and secure runtime that monitors hardware tampering, power glitches, physical access, etc. It’s undisclosed and IP’d but that’s understandable.</p></li></ul><p>Anyway, spending this whole night reviewing World&apos;s codebase is like reading an autist&apos;s work. There&apos;s no effort to dumb things down so that the general public can understand, because they know the average person won&apos;t even try. But I’m blown, the amount of thought and detail put into its design is interesting.</p><h3 id="h-but-what-if-they-deployed-something-different-from-whats-in-the-repo" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">“But what if they deployed something <em>different</em> from what’s in the repo?”</h3><p>Could be the case. But it&apos;s too much effort to construct a very complicated system with hundreds of commits and multiple deployment signatures, all just to cover a lie. I would have stopped at giving cash for iris data. But nah, they went full nerd.</p><p>Now, if you&apos;d still like to throw arguments against cold hard code, let me help you, here are some suggestions for a more believable counterpiece. Despite multiple opportunities, da danki herself hasn’t put her eyeballs anywhere near the Orb.</p><h2 id="h-so-why-i-still-havent-scanned-why-i-might" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>So Why I Still Haven’t Scanned + Why I Might</strong></h2><p>Because I <em>do</em> have a few legitimate questions, and it’s okay to be skeptical if your skepticism is informed:</p><ol><li><p><strong>What happens to the encrypted iris code after it gets sent to the backend for uniqueness checking?</strong></p><p>Hamming distance comparisons via partial key shares are cool and all, but who are the entities running the three MPC nodes? If they’re all affiliated with World, then yes it&apos;s cryptographically secure, but not decentralized. And in that case these three entities can collude to reconstruct all the iris codes that were ever created.</p><p>If the MPC protocol and architecture is open-sourced and shown to have sufficient decentralization, then I’ll feel much safer about it.</p></li><li><p><strong>What happens if I lost my World ID, can I regenerate a new one via my previously scanned eyeballs?</strong></p><p>I think there’s no answer to this yet, but Danki would love some clarity.</p></li><li><p><strong>What happens if you want to delete your World ID?</strong></p><p>Spoiler: because it’s a protocol and I don’t see any code or mention of a component that will delete your PoH, you probably can’t.</p></li></ol><h2 id="h-i-just-cant-fear-the-orb-if-its-opensource" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>I Just Can’t Fear the Orb If It’s Opensource</strong></h2><p>World is not perfect. But it’s trying. And it’s doing it in the open. You know what’s worse? All these other KYC systems in the planet hoarding your data in privately managed servers, then leaking it in some sketchy DeepWeb dump.</p><p>Sometimes saying you don’t know and asking questions is a more pro move than making up stories and building on clout. And before you spin things at least check the source.</p><p>I’m touching grass now. Hope the world heals. Hope you lerned something. 🐴</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e6b3949995a9cd878dbaa9bc57660c950e7f630729c20955b11fee12193a34fa.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[A Stark Focus on StarkNet's Cairo 1.0]]></title>
            <link>https://paragraph.com/@tinerispe/a-stark-focus-on-starknet-s-cairo-1-0</link>
            <guid>ItD7lWTsibfoGQBmPUeo</guid>
            <pubDate>Sat, 23 Sep 2023 04:49:13 GMT</pubDate>
            <description><![CDATA[I haven’t escaped Cairo hell yet when the Cairo 1.0 rolled out, promising ZK-STARK-provable computation with simpler syntax and better security through an intermediate representation they call Sierra. Now after a few traumatic attempts at understanding the old version of Cairo (excuse me I’m quite normie), my fear was that the addition of this new steps will make it more complex, but luckily I was proven wrong.A Quick Rundown on How Cairo WorksCairo is specifically designed for creating valid...]]></description>
            <content:encoded><![CDATA[<p>I haven’t escaped Cairo hell yet when the Cairo 1.0 rolled out, promising ZK-STARK-provable computation with simpler syntax and better security through an intermediate representation they call Sierra.</p><p>Now after a few traumatic attempts at understanding the old version of Cairo (excuse me I’m quite normie), my fear was that the addition of this new steps will make it more complex, but luckily I was proven wrong.</p><h3 id="h-a-quick-rundown-on-how-cairo-works" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">A Quick Rundown on How Cairo Works</h3><p>Cairo is specifically designed for creating validity proofs, and for this reason it compiles to a special machine code they call CASM which translates to a set of polynomial constraints and runs on a VM that was optimized for proofs (aka. Cairo is not an EVM Compatible language).</p><p>Cairo VM is different in a few ways from EVM, like for instance, field element is the only data type in Cairo while we have a variety of 256-bit words for EVM languages like Solidity which we still have to emulate into field elements if we’re going to run proofs, so why not just go with it directly. Anyway… what makes proving multiple times faster in Cairo VM is their read-only memory model. Linear type systems like this also guarantees safety when it comes to making sure that memory cannot be deleted or rewritten.</p><h3 id="h-so-were-trading-off-composability-for-efficiency-arent-we" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">So we’re trading off composability for efficiency aren’t we…</h3><p>This probably one of the bigger downsides of using a proof-optimized system, I think. Because Cairo is made to run on a different VM, we can’t just create dApps with it that are interoperable with protocols in the EVM, which is a big bummer but luckily we now have <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.kakarot.org/">Kakarot</a>, a enshrined zkEVM implemented in Cairo that can serve as an abstraction layer for programs written in Cairo, practically enabling EVM calls and Solidity bytecode to be interpreted in StarkNet. It is still not live in production as of time of writing, to note. But I’m looking forward to try the RPC and will post about it.</p><h3 id="h-the-dawn-of-sierra" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">The Dawn of Sierra</h3><p>So with the original Cairo, the compilation goes like this:</p><p>Cairo --→ CASM</p><p>Where Cairo compiles locally into CASM (Cairo Assembly) which is a machine code that translates to polynomial constraints. So every program written in Cairo generates a proof of correct CASM execution and that is then sent to the StarkNet sequencer.</p><p>This process has some nuances though. For example, if the user submits an invalid transaction in CASM directly to the sequencer and it reverted, then there will be no proof generated that the user has indeed submitted a failed transaction so that the network can collect fees from it. And if StarkNet can’t collect fees for doing certain computations then it can be an opening to DoS attacks in the network.</p><p>So now we have to introduce a safe form of CASM that will be provable at all times, and that is where this intermediate representation, Sierra comes in. In Cairo 1.0, the program first has to locally compile into Sierra which then compiles into safe CASM when submitted to the sequencers for verifying. This means can no longer send CASM directly to StarkNet, limiting the attack surface and making the network more secure.</p><p>The compilation process in Cairo 1.0 looks like this:</p><p>Cairo 1.0 --→ Sierra --→ Safe CASM</p><p>Some other notable features of Sierra include:</p><p>-Gas counting upon compilation to Sierra. Sierra makes sure that each function counts its gas correctly and returns if it has reached the limit.</p><p>-Intermediate representation enables type information from Sierra to work with native types, opening the possibility that StarkNet sequencers may eventually run on native hardware</p><h3 id="h-why-cairo-10-syntax-is-easier-to-use" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Why Cairo 1.0 Syntax is Easier to Use</h3><p>When I first encountered Cairo, you will still have to create types for different integers and addresses using field entities or felt. And then I had to understand when and how to use variable declaration patterns like tempvar, local… Tbh it’s a little overwhelming for noobs like me, but it gets more understandable now because some of these low-level decisions are now abstracted away in the language.</p><p>There are many examples where I can point out that it’s looking more like Rust and is easier to the eyes, and here are some:</p><ul><li><p><code>func</code> is now just <code>fn</code>, and its return variables no longer have variable names but just their types.</p></li><li><p><code>@storage_var</code> is no more, now you have to put them in a storage struct, write the contract traits, and then write its implementation.</p></li><li><p>the decorator syntax is now more Rust-like. So is creating enums and matching patterns</p></li><li><p>the use of an interface contract they call traits, which has a separate section for its implementation</p></li><li><p>enabling type generic parameters through the use of <code>&lt;&gt;</code></p></li><li><p>easy upgradeability by just replacing class hash!</p></li></ul><p>There are many more that I probably missed, so better check out their <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.cairo-lang.org/">docs</a>.</p><p>As y’all know, I come from a Solidity background and still currently exploring Rust. So if you’re interested, let’s be scruffy, learn from mistakes together, and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://web.telegram.org/k/#-1967793949">take this path with me</a>! I’ll be hacking away a few sample programs in Cairo 1.0 and hopefully get to share some with you, too.</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/041ea9e5f551d60cfd85f85704544fe2f432299d9b2d8b3648f35938a9ca2cf9.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Blockchain Legos: The Modular Stack]]></title>
            <link>https://paragraph.com/@tinerispe/blockchain-legos-the-modular-stack</link>
            <guid>EZD2NPTmWRbSEpvbwBXF</guid>
            <pubDate>Tue, 29 Aug 2023 16:53:52 GMT</pubDate>
            <description><![CDATA[If you’ve been here long enough, you would have already heard of the blockchain trilemma where you can only pick two out of three between security, speed, and decentralization. But that is so 2020. Some years ago, we expect one single blockchain to perform various functions for us. For instance, Ethereum has become congested because it was juggling between validating incoming transactions, arranging them into blocks, executing them, and finally keeping all these growing records available at a...]]></description>
            <content:encoded><![CDATA[<p>If you’ve been here long enough, you would have already heard of the blockchain trilemma where you can only pick two out of three between security, speed, and decentralization. But that is so 2020.</p><p>Some years ago, we expect one single blockchain to perform various functions for us. For instance, Ethereum has become congested because it was juggling between validating incoming transactions, arranging them into blocks, executing them, and finally keeping all these growing records available at all times. That was a lot of tasks. And it slows down the network. So Layer 2’s and sidechains were made to decongest it by delegating the task of computation for L1s and then pushing the result of multiple transactions within a single transaction to L1.</p><p>Sounds simple. And then we ask clarifying questions like, how do these L2 solutions prove to L1 that the transaction they process is actually valid? That’s how we get to proving systems, and there are two types:</p><ol><li><p><strong>Fraud Proofs</strong></p><p>Fraud proofs are used by optimistic rollups. Optimistic rollups just assume that all transactions are valid and pushes them to their block, which sounds a little crazy but is actually secure because there are fraud proofs. There will be a time window for anyone to challenge the block made in L2 before it becomes accepted in L1.</p><p>To challenge a transaction’s validity, anyone can compute a fraud proof which is basically a proof of mismatch between specific parts of the merkle tree and the condensed data on the merkle root. When a fraud proof is accepted, the rollup simply re-updates its state and the block builder who included the invalid transaction gets a penalty.</p><p>You gotta note that we require that data from L1 be accessible to L2 in order for challengers to produce a fraud proof.</p></li><li><p><strong>Zero-Knowledge Proofs</strong></p><p>Zero-knowledge rollups also bundle up transactions and summarizes their blocks to L1. But unlike fraud proofs, validity proofs don’t need a challenge period to finalize state changes. It uses zero-knowledge cryptography to prove that the changes being pushed to L1 is indeed a result of executing valid transactions made on the rollup. Once the validity proof is submitted to L1, then the state changes are instantly accepted.</p><p>Now this wouldn’t require any access to L1 data, because they are merely sending a proof of computation that happened within the zk rollup.</p><p>There are many cool ways to design a zk rollup, and there are many flavors of this cryptography to choose from (eg. SNARKS, STARKS, etc.), but I would have to write the comparison in a separate post. Hekk, I might wanna write about how a typical proving circuit in zk-rollups look like. But moving on…</p></li></ol><p>You might notice that data availability is sometimes an issue if the historical transaction needs to be proven as valid by other chains. But storage in the blockchain is expensive, and nodes have to continuously store a growing history if they want to make the data available. So to give us options, we have at least three types of L2 solutions:</p><ol><li><p>Rollups – a layer that executes the transactions and then sends a proof AND a record of all these processed transactions as call data to L1.</p></li><li><p>Validiums – only executes the transaction and sends a proof of validity to L1. Incredibly cheap but has a chance of locking your funds in the validium. Great use for not-so important transactions like on-chain games.</p></li><li><p>Volitions – lets the user decide whether to use a validium or a rollup for their transaction.</p></li></ol><p>And then there are also sidechains whose history is separate from the L1 and has its own consensus model (they don’t do any proving to L1 and don’t inherit its security). Sidechains are merely connected to L1s by a two-way bridge that can move assets between the chains.</p><p>Ok folks, that’s the execution layer. Now people can have the option of faster transaction processing through these L2s. Although with this design we are still putting strain on L1 because it has three jobs to do: making the network agree on just one version of truth (the blockchain’s state), deciding on how to sequence the transactions, and storing all that transaction history. We’re gonna be munching on these next, but for now GN!</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/7279c4d4a4f0b27749a236a305771b3f8a497b29a0d4388f2d52088a19b367bf.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Demystifying the Quantum Threat]]></title>
            <link>https://paragraph.com/@tinerispe/demystifying-the-quantum-threat</link>
            <guid>tRcOwGfm8JstiNiOBkdt</guid>
            <pubDate>Thu, 03 Aug 2023 23:33:18 GMT</pubDate>
            <description><![CDATA[In da past two weeks, I have encountered at least 3 people who talk about quantum menace as if it will be the end of all existing blockchains today. So here are some facts:Most blockchains today rely on elliptic curve signatures (like ECDSA/EdDSA). These are not post-quantum secure. A sufficiently powerful quantum computer could forge signatures and spend funds from exposed keys. This is probably where they’re coming from.Hash functions like Keccak256 (used in Ethereum) are more resilient, ev...]]></description>
            <content:encoded><![CDATA[<p>In da past two weeks, I have encountered at least 3 people who talk about quantum menace as if it will be the end of all existing blockchains today. So here are some facts:</p><ul><li><p>Most blockchains today rely on elliptic curve signatures (like ECDSA/EdDSA). These are <a target="_blank" rel="noopener" class="dont-break-out markup--anchor markup--li-anchor" href="https://math.mit.edu/~apost/courses/18.204-2016/18.204_Jeremy_Wohlwend_final_paper.pdfhttps://math.mit.edu/~apost/courses/18.204-2016/18.204_Jeremy_Wohlwend_final_paper.pdf"><strong>not post-quantum secure</strong></a>. A sufficiently powerful quantum computer <em>could</em> forge signatures and spend funds from exposed keys. This is probably where they’re coming from.</p></li><li><p>Hash functions like Keccak256 (used in Ethereum) are <strong>more resilient</strong>, <a target="_blank" rel="noopener" class="dont-break-out markup--anchor markup--li-anchor" href="https://cryptobook.nakov.com/quantum-safe-cryptography">even under quantum attacks</a>. Grover’s algorithm weakens them, but doesn’t break them outright. So no, a quantum attacker doesn’t just rewrite chain history or magically “undo hashes.”</p></li><li><p>This means the real risk is key compromise, not instant consensus collapse. But note: signatures are also used in validators, bridges, multisigs, etc. so it’s not just wallets.</p></li><li><p>Blockchains are not static. They can upgrade. Ethereum alone has already coordinated massive changes (see: EIP-4844, account abstraction direction, etc.). Migration to post-quantum signatures is hard — but very doable with enough lead time.</p></li><li><p>Post-quantum cryptography isn’t hypothetical. We already have standardized schemes (like lattice-based signatures from NIST). The problem is <strong>efficiency and integration</strong>, not existence.</p></li><li><p>ZK systems are a mixed bag. Many SNARKs today rely on algebraic assumptions (pairings, discrete logs) that are <strong>not quantum-safe</strong>. Most of today’s widely used ZK opt for <a target="_blank" rel="noopener noreferrer nofollow noopener noopener" class="dont-break-out markup--anchor markup--li-anchor" href="https://www.cs.cmu.edu/~goyal/ConSZK.pdf">statistical zero-knowledge over statistical soundness</a>… which means a quantum adversary may break soundness in terms of deriving toxic waste from a trusted setup ceremony and fake a ZK-SNARK. A strong enough quantum adversary could forge proofs in some setups. STARKs and hash-based systems are generally more post-quantum friendly.</p></li><li><p>Also, please stop quoting raw qubit numbers like it’s a countdown timer. Breaking ECC requires <strong>error-corrected (logical) qubits</strong>, not the noisy ones you see in headlines. We are not there yet. Not even close.</p></li></ul><p>Now there are a lot of ways quantum computing can potentially break the blockchain. But right now it’s mostly a <strong>migration problem</strong>, not an extinction event.</p><p>As for me, I’ll only start panicking if someone figures out how to efficiently reverse hash functions. <a target="_blank" rel="noopener noreferrer nofollow noopener" class="dont-break-out markup--anchor markup--p-anchor" href="https://en.wikipedia.org/wiki/Post-quantum_cryptography">I don’t think it will happen though</a>. But to each his own.</p><p>Ok now go, have better discussions about post-quantum cryptography in da blockchainz <span data-name="horse_face" class="emoji" data-type="emoji">🐴</span></p><br>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <category>post-quantum-cryptography</category>
            <category>cryptography</category>
            <category>blockchain</category>
            <category>ethereum</category>
            <category>pqc</category>
            <category>quantum</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/af17be7e4c23687808328d19d8ef241bf461c95c49f365d87c31b29884fd47e9.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ZK Proofs Part IV: Tornado Cash ]]></title>
            <link>https://paragraph.com/@tinerispe/zk-proofs-part-iv-tornado-cash</link>
            <guid>keFTVq7EXHksGah6jebm</guid>
            <pubDate>Wed, 12 Jul 2023 19:26:14 GMT</pubDate>
            <description><![CDATA[Excuse any typo or grammar lapses in this post. Yer donkey just got poisoned by spoiled milk and is now drunk typing on a high fever. This helps tho. Today’s topic requires some courage to be published. We’d be talking about one of the most controversial protocols in DeFi and how ZK was used for its privacy-preserving properties: Tornado Cash. Do you know one of the founders of got jailed for it… fortunately he’s already released as of today-- just with a monitoring device on his ankles. Goms...]]></description>
            <content:encoded><![CDATA[<p>Excuse any typo or grammar lapses in this post. Yer donkey just got poisoned by spoiled milk and is now drunk typing on a high fever. This helps tho. Today’s topic requires some courage to be published.</p><p>We’d be talking about one of the most controversial protocols in DeFi and how ZK was used for its privacy-preserving properties: Tornado Cash. Do you know one of the founders of got jailed for it… fortunately he’s already released as of today-- just with a monitoring device on his ankles. Gomsh.</p><p>So anyway, I’m gonna talk about how the protocol works under the hood and maybe add in some ideas on how mixers kan be built better so you can prevent bad elements from using the protocol while avoiding being put on a human cage for trying to preseve everyone’s privacy.</p><p>Wait. <strong>Disclaimer:</strong> I don’t by any means encourage illegal activities in the blockchain. And if you use Tornado Cash or any similar mixer, there is a good chance that some protocols and centralized exchanges might blacklist your wallet. This is purely for educational purposes only as Tornado Cash is one of the first ZK-powered dApps that gained popularity among privacy-conscious users.</p><h2 id="h-how-do-mixers-work" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How do Mixers work?</h2><p>A mixer is a special type of smart contract that takes in deposits from different users and allows a different address to withdraw from it without revealing from whom the deposit came from.</p><p>Well, not really. If there are only a few people who deposited into the mixer, then it’s fairly easy to guess from which address you’re withdrawing. That aside, the closest TradFi equivalent for this would be a bank’s safe deposit box with a lot of people going in and out of the room. In a safe deposit box, anyone who has the key can access whatever is stored in their safe. And while it’s easy to know who withdrew from which deposit box if only one person ever goes in and out of the room, it will be harder and harder to guess if many people frequent the room.</p><p>There are other custodial mixers in the space, but Tornado Cash is unique because it is totally permissionless and decentralized. You only get to withdraw the tokens you deposited and not a combination of tokens that were relinquished to the protocol by other users and was mixed by a centralized authority.</p><p>Just by throwing around these ideas, it’s not hard to imagine how Tornado Cash became a tool for hackers to withdraw the crypto they obtained thru nefarious means. I’ll be sharing some ways on how to prevent this from happening later. But first, our ZK lesson…</p><h2 id="h-tornados-trusted-setup" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Tornado’s Trusted Setup</h2><p>The protocol uses a ZK-SNARK based library which, if you remember from a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/donkadonk.eth/ofe5mGXiFVQGmk3AaKxdzwH5DytIChCQNfZfuHc5avw">previous post</a>, goes like this:</p><ol><li><p>The circuit has a trusted setup which is an algorithm that the verifier uses to create non-interactive proofs</p></li><li><p>In the setup, the prover has to pick a random number to evaluate from. This answer is run through a hash and becomes part of the proof. The other part of the proof is obtained by having the hash evaluated through another function in the Fiat-Shamir Transformation.</p></li></ol><p>Now, in Tornado, the depositor picks two random numbers <em>k</em> and <em>r</em> : <em>k</em> being a nullifier whose hash will be used to make sure that no token can be withdrawn more than once, and <em>r</em> is another random number through which we’ll evaluate the algorithm. These two numbers should be private at all times because they’ll be used to withdraw the deposited funds anonymously. The withdrawer then runs both numbers in a hashing function <em>S[R, h, A, f, t]</em> to prove that he knows the values of <em>k</em> and <em>r</em>.</p><p>Wait whut iz <em>R, h, A, f, t</em> you say?</p><p>They are all public variables.</p><p><em>R</em> is the merkle root of the tree we’re withdrawing from</p><p><em>h</em> is the hash of our nullifier <em>k</em></p><p><em>A</em> is the recipient address of the deposited token</p><p><em>f</em> is the fee the recipient pays to the relayer address</p><p>and <em>t</em> is the relayer address</p><p>Now, here’s how the prover computes:</p><p>**<em>S[R, h, A, f, t] =</em> {I KNOW <em>k, r ∈ B 248, l ∈ B 16, O ∈ Z 16 p</em> **</p><p><strong>SUCH THAT <em>h = H1(k)</em> AND <em>O</em> is the opening of <em>H2(k||r)</em> at position <em>l</em> to <em>R</em>}</strong></p><p>Ok, we have a new variable <em>O</em> and <em>l</em>. Simply put, <em>O(T , l)</em> is the Merkle opening for leaf with index <em>l,</em> and <em>T</em> is a merkle tree with a height of 20. You don’t have to worry much about it, it’s just a way of verifying that there’s a node in the tree whose deposit can be unlocked by values <em>k</em> and <em>r</em>.</p><p>If you would notice, there is really no <em>A</em> and <em>f</em> in the equation above. But adding them in the hashing function is actually a thoughtful move because while these variables are not a necessary part of the computation for the proof, having the recipient address and fee in the hash can prevent a frontrunning attack by MEV bots.</p><p>Anyway, from these variables we’ll compute for two values again and by the end of the process we’ll have two public parameters: <em>dp and dv</em> which will be used for constructing the proof and the verifier respectively (I won’t go into detail anymore as the functions for computing these params don’t seem very important, but you can read <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://berkeley-defi.github.io/assets/material/Tornado%20Cash%20Whitepaper.pdf">here</a>).</p><p>When a proof is successfully submitted to the Tornado Cash contract, the tokens are released to recipient address <em>A</em>, the relayer fee <em>f</em> is released to relayer address <em>t</em>, and the nullifier hash is added to a list of hashes that have already been used (to ensure the proof cannot be reused to withdraw).</p><p>Ok that was some knotty process. Though believe it or not Tornado Cash is one of the simpler ones. It was still dangerous tech tho, so I am also suggesting some ways we can have a privacy-preserving dApp like this while keeping it free from illegal activities.</p><h2 id="h-ideas-on-how-to-make-mixers-less-sketchy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ideas on How to Make Mixers Less Sketchy</h2><p>The good news is that the web3 landscape is no longer the same as in 2019 when the Tornado Cash whitepaper was published. We have a lot more advanced ways of verifying identity and activities on-chain. One such ways is using DIDs or decentralized IDs to prove that the user has not been on any sanctions list, blacklisted by any blockchain security company, or has not made any red flag transactions before allowing them to deposit into the mixer.</p><p>Easier said than done. As y’all know, computation in the blockchain is expensive and it might take a lot of gas to do this. Or we might need an oracle but it might beat the purpose of preserving user privacy.</p><p>A more decentralized way of tackling this is to give the withdrawers the ability to prove that they are not linked to a certain address (without revealing anything else about them). This enables law enforcers to narrow down the addresses who are probably withdrawing funds obtained from criminal activity.</p><p>And if you’re someone who’s planning to use mixers for shady reasons, know that they are not an end-all-be-all to transaction privacy on chain. There are so many ways you could give away your identity while using these protocols but I deleted the section so good luck in der haha.. If you’re a cybersec and wanna lern how to catch em, contact Danki and I’ll give away some secret movez.</p><h2 id="h-up-next" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Up Next</h2><p>Em not very satisfied with the fact that I haven’t yet explained how we even get these equations to begin with. So the next ZK post will be about how we turn literal programs into a ZK circuit. You’ll be meeting Quadratic Arithmetic Program, R1CS, and the many stages of turning regular programming logic into a ZK-Proof. Might even do a demo on Circom but I can’t promise yet as I’m so hung up on debugging ZK programs.</p><p>For now I sleeps and recover. Bye frens!</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/a4dee80e334d9c5a2fcc7edcb29b9e2c337117830cb46324b5a56208a249c6d9.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Splitting Tokens with Pendle]]></title>
            <link>https://paragraph.com/@tinerispe/splitting-tokens-with-pendle</link>
            <guid>dV5gMQovVV0sl5Pcbs3m</guid>
            <pubDate>Fri, 30 Jun 2023 14:52:44 GMT</pubDate>
            <description><![CDATA[Lato-lato is the current craze where I live as of the time of this writing. And thank goodness coz while the ear-splitting clacks of this toy can be heard all over the town, it gave me an epiphany about a certain DeFi protocol that I’ve been dying to understand. The protocol iz Pendle Finance. Most crypto apers miss this because admittedly the barrier to understanding what it does is high. But I think it’s a hidden gold mine just waiting to be discovered by the wider crypto public. So… here w...]]></description>
            <content:encoded><![CDATA[<p>Lato-lato is the current craze where I live as of the time of this writing. And thank goodness coz while the ear-splitting clacks of this toy can be heard all over the town, it gave me an epiphany about a certain DeFi protocol that I’ve been dying to understand.</p><p>The protocol iz Pendle Finance. Most crypto apers miss this because admittedly the barrier to understanding what it does is high. But I think it’s a hidden gold mine just waiting to be discovered by the wider crypto public.</p><p>So… here we go. Pendle Finance is the DeFi counterpart of the $400T interest derivative market. By that, I mean it enables you to trade the fluctuations of interest rates.</p><p>In staking, you got this thing called APY that tells how much interest you’re getting for your crypto. But APY is not set in stone. A protocol may say it will be giving you 69,000% APY but only give you 6% by the end of the period. This is because APY changes depending on the supply and demand of the token you’re staking, how diluted it becomes, and even on how many participants are staking. So rather than just praying that you’ll get the promised yield for your staked crypto, you can actually trade the yield proactively on Pendle and have more control of your staking earnings.</p><h2 id="h-twin-balls-the-yield-and-the-principal" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Twin Balls: The Yield and The Principal</h2><p>It all begins by splitting your staked token into two: the Principal Token (PT) and the Yield Token (YT). The PT can be redeemed 1:1 with your underlying token once the maturity date has been reached. Before maturity date, you can buy PT at a lower price and it will increase in token value until you can exchange it equally with the real token.</p><p>YT represents the yield for each underlying token. So if, for example, you deposited 5 staked ETH that has 5% APY, then you’ll get 5 YT to represent that 0.05 ETH yield for each token. If you wanted to short the interest rate, you sell some YT back into PT. But if you wanna bet that the APY will go higher, then you buy more YT. Say, you’re bullish that ETH staking rewards will go higher. You can just buy 100 YT-stETH and now you can get the increasing yield for 100 stETH without actually needing to buy 100 ETH.</p><p>But what’s the relationship between the PT and YT? Remember when I said you can buy PT at a lower price before maturity date? Well it gets that discount from the APY of your staked token. And since YT represents the APY from your staked token which you’re making redeemable through PT by the end of the period, YT loses its token value as time passes until it becomes zero at maturity date.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4fef4b037a9a24dbc7da9ea33b562cc79e463b10baf4c02f77e25186ee929181.png" alt="PT and YT values over time" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">PT and YT values over time</figcaption></figure><p>In short, the values of PT and YT move inversely correlated to each other over time… pretty much like lato-lato balls converging again after being split in opposite directions.</p><p>If you’re OC, here’s the exact formula for the relationship between the two tokens:</p><p><strong><em>P</em>(<em>PT</em>)+<em>P</em>(<em>YT</em>)=<em>P</em>(<em>Underlying</em>)</strong></p><p>Now you know the rules of splitting. Jast keep lato-lato in mind because there are huge advantages to using Pendle that you can’t get anywhere else:</p><h2 id="h-fixed-yield-strategy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Fixed Yield Strategy</h2><p>Pendle’s design enables participants to lock in the yields for their staked tokens. As you olredy know, the yields in your staking protocols are unpredictable and you never know what you’re gonna get in total. Dis causes headaches and uncertainty.</p><p>But remember, if you buy PT at an already declared APY (its implied yield), you are always guaranteed of the exact yield you’d be getting by maturity date. So how to lock in your yields? Just buy the PT of your choice token and hold til maturity date. Tadaaa, fixed yield.</p><h2 id="h-pt-trading-vs-spot-trading" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">PT Trading vs Spot Trading</h2><p>Spot trading iz nice and all, until I realized you can also trade PT like they’re spot tokens. It does make sense because the value of the PT moves in parallel relative to its underlying asset. But compared to spot, PT also has a guaranteed fixed yield. The token literally multiplies. So if I ever make a losing trade, at least I can get some yield from holding a PT. And if I make a winning trade, I still get additional yield to sweeten the win… so, it’s really a no brainer decision to use PT to trade whenever possible.</p><h2 id="h-liquidity-providing-with-zero-impermanent-loss" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Liquidity Providing with Zero Impermanent Loss</h2><p>This is probably my favorite. Pendle pools are pretty spooky because you always know how it ends up on maturity date: you get back your investment at the same token value. This is because PT is equal to your underlying asset by the end of the period. And if they have the same value, then there will be no impermanent loss eventually.</p><p>How do you use this to your advantage? Well if you’re just planning to hold a staked token for a long time, you can get free money by putting it in Pendle’s liquidity pool and earn incentives, swap fees, PT yield, as well as staking rewards. All that is risk-free, because read the previous paragraph anon. I can’t be any more excited for staker frens 🐴💖</p><h2 id="h-a-use-case-for-simple-personz" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">A Use Case for Simple Personz</h2><p>Us plebs may not even want to trade interest rates or participate in liquidity pools. It still has some use for us though. Say danki wanna buy ETH and is planning to hold it for the long term. I will likely get the best discounted prices at Pendle, because PT haz yield. The catch is that I can only redeem them at a later date, but since I’d be holding the tokens for a long while anyhow, the waiting time wouldn’t matter.</p><p>Buying PT can actually serve as a big discount voucher to any token you wanted to buy and hold. Simple enough if you just wanna ape :D</p><h2 id="h-another-defi-poetry" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Another DeFi Poetry</h2><p>As you see, the token-splitting mechanism that started it all seems very unique, and that is because it has its own token standard. Once a yield-bearing token is provided to Pendle, they create an SY token behind the scenes which is what the protocol uses to interface with the original token you provided. From there, they mint your PT and YT.</p><p>The interesting thing is that SY tokens <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-5115">might have a use outside Pendle</a> because it literally made a standard interface for protocols to interact with yield-bearing tokens in a generic yield-generating pool. It might eliminate any protocol’s need to use custom code when dealing with the special mechanisms of these pools and yield-bearing tokens. I might go back to this in detail in the future!</p><p>The AMM is interesting too… since we’re trading yield, the range gets pretty narrow and predictable and the model almost mimics a concentrated liquidity pool. So the trading range is easily set and the users benefit from lower price impact and better capital efficiency for LP-ers.</p><p>I kan sense some frends’ brainz are screaming olredy. It’s normal tho, Pendle is one of the more unique protocols I reviewed. Danki almost cried from trying to understand some token splittin mechanisms haha.. But we made it! We have a detailed explainer of one of the most novel DeFi protocols that I think is gonna be big in the future.</p><p>I hope you lerned something. Next time I want to talk about primitive pools where the liquidity curve dynamically changes over time. Or maybe I’ll just donkey around and write something lighter and funnier. Whatever it is, thenks for hanging out in here. Ciao!</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/4c759eb4b45a7e7fc5bbd8f8f81b54aa42455696a5082e4e5c3c005d7b28f5e9.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[The Web3 Ethos]]></title>
            <link>https://paragraph.com/@tinerispe/the-web3-ethos</link>
            <guid>OOA5yDytBV5gotOxsHQm</guid>
            <pubDate>Tue, 27 Jun 2023 11:19:05 GMT</pubDate>
            <description><![CDATA[Depending on who you ask, the definition of Web3 wildly varies from “a new buzzword that crypto scammers use” to “the next big thing in the history of interwebz”. I’m here to tell you that both are true. And I’m not gonna pretend that Web3 is a utopian dream where only good projects emerge because that is simply not the reality of it. Web3 is a tool… it can be used for either good or bad and a lot of gray areas in between. But it is one powerful tool, so today I’m going to dissect what makes ...]]></description>
            <content:encoded><![CDATA[<p>Depending on who you ask, the definition of Web3 wildly varies from “a new buzzword that crypto scammers use” to “the next big thing in the history of interwebz”.</p><p>I’m here to tell you that both are true. And I’m not gonna pretend that Web3 is a utopian dream where only good projects emerge because that is simply not the reality of it. Web3 is a tool… it can be used for either good or bad and a lot of gray areas in between. But it is one powerful tool, so today I’m going to dissect what makes a ‘good’ use of this technology.</p><p>Before I make this explainer, let’s make sure we’re on the same page and are talking about the same thing:</p><p><strong>What Web3 is NOT:</strong> cryptocurrencies and crypto trading. While cryptocurrencies are a part of the space, web3 isn’t an uber-bullish notion that every token must go up in value. Ask yourself deeper questions before you get hyped, <em>“What is the utility of this token and why are people buying it?”</em> Sometimes it only takes a few probing questions to realize that a lot of these projects are only built for the sole purpose of making money for its creators and early investors without bringing any benefit for the greater public.</p><p>Ok, now we’ve gotten that out of the way, let’s try to define what Web3 is in the context of previous internet functionalities:</p><p><strong>Web1</strong> - A read-only web. This is how the early interned was initially intended to be. There are static websites to disseminate information but there is no participation from the user.</p><p><strong>Web2</strong> - A read-and-write web. This is where things started to explode because now the user is not a mere passive consumer of information but a source of data. Users can now post their content, get tracked for their activities, and have unique experiences inside the platform they’re in. The problem is that all these data can be accessed and sold by the companies who run the centralized servers from behind.</p><p><strong>Web3</strong> - A read-write-and-own web. A new internet paradigm where users can own digital assets (which can be anything from game items to one’s own identity) through their wallets. Instead of relying on centralized authorities, the system runs on code and these code utilize mechanisms and game theory to consistently incentivize and punish behaviors that are beneficial/detrimental to the system.</p><h2 id="h-dankis-back-story" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Danki’s Back Story</h2><p>I hab a bad back. Even as a kid, I slump while I walk. And while I always get good grades in school my posture gives away the fact that I’m not always sure of myself. Couple that with lack of social skills and the involuntary smile whenever I’m uncomfortable, and you got the perfect recipe for being the least believed person practically anywhere you go.</p><p>Why am I telling this?</p><p>Because the aggregate of these experiences is how I got sucked into Web3. In a centralized world, people make the judgement. And while most of the time they don’t act out of malice, their judgements are always prone to biases and fallacies leading them to bad decisions. So I learned the hard way that it is in everyone’s best interest to make a system that doesn’t rely on a central authority’s judgement, but instead uses an architecture where the rules are clearly laid out and are executable, and authority is distributed.</p><p>Web3 offers a dream for donkeys with bad backs. Finally, we have a mechanism that doesn’t rely on mere trust and beliefs but on provability. Blockchain and decentralization enables anyone in the network to objectively verify things and have everyone subjected to the same hardcoded rules without special, subjective exceptions.</p><h2 id="h-more-than-just-the-tech" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">More Than Just the ‘Tech’</h2><p>Betting on web3 and blockchain technology is a bet for everyone’s need for transparency, self-sovereignty, and inclusivity.</p><p>In this world full of breaches in user privacy, where all the things you posses are at the mercy of centralized authorities, where deepfakes can easily be used for mass deception… we all need a little decentralization and ownership to bring back this imbalance of power to a healthier level.</p><p>That being said, these are the things and concepts that I think represents the role of Web3 in disrupting the way things are done today:</p><h3 id="h-decentralization" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Decentralization</h3><p>A centralized system is only as good as the one who holds the decision-making power over it. While centralization itself is not a bad thing, there are certain cases where it is better to have this power and ownership distributed among the users, builders, and stakeholders to align the incentives and risks to the actual decision making process.</p><h3 id="h-permissionlessness" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Permissionlessness</h3><p>Permissionlessness means that everyone has equal access to the system as long as they follow its rules and that there are no gatekeepers to censor who can be part of Web3 and who cannot be.</p><h3 id="h-trustlessness" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Trustlessness</h3><p>This property of Web3 enables people to be part of the same system without needing to trust that the others will cooperate. How is this made possible? By utilizing game theory and mechanisms to incentivize honest behavior and punish the malicious ones.</p><h3 id="h-ownership" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Ownership</h3><p>When you truly own something, it means no one can take it away from you without you giving it away. Ask yourself: do you truly own your data if your platform or service provider can easily sell it and use it without your knowledge? Do you truly own your money if your bank can close your account any time? Do you truly own your digital creations if they can easily be copied and reposted by anyone? Web3 solves the ownership problem by having all your digital assets singularly managed inside your own wallet.</p><h3 id="h-immutability" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Immutability</h3><p>Immutability means that something is irreversible and cannot be tampered with. The history of the transactions in the blockchain cannot easily be changed in any form by anyone with a vested interest. Doing so would require an attack so expensive, it will be totally impractical.</p><p>…And these things, mah frens, iz why Web3 is such a big playground that continues to fascinate a lot of people. It’s not always because of the money. When something about these concepts resonate with your own values, it will be unavoidable to get sucked into the space.</p><p>Can you feel the pull now? Give in. It’s a deep, crazy rabbithole down here, but it’s worth the jump.</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/deced3307947b5f07dfacc9ca1448857e5c2ededdb2ac41b6ee38488b582eb67.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[The Evolution of Uniswap]]></title>
            <link>https://paragraph.com/@tinerispe/the-evolution-of-uniswap</link>
            <guid>EIXNAgIeMA7yNvYWTN5V</guid>
            <pubDate>Fri, 23 Jun 2023 09:08:25 GMT</pubDate>
            <description><![CDATA[In the beginning, the only way you could buy crypto was either through bargaining with another human or going through the hoops of centralized exchangez. And it’s all good, but manually finding a seller that will meet your quantity and price is difficult and centralized exchanges are prone to mismanagement. So from these inefficiencies, DEXes are born. And safe to say that as of the moment, the most popular DEX that is catering to hundreds of thousands of crypto users is Uniswap. This has bee...]]></description>
            <content:encoded><![CDATA[<p>In the beginning, the only way you could buy crypto was either through bargaining with another human or going through the hoops of centralized exchangez. And it’s all good, but manually finding a seller that will meet your quantity and price is difficult and centralized exchanges are prone to mismanagement.</p><p>So from these inefficiencies, DEXes are born. And safe to say that as of the moment, the most popular DEX that is catering to hundreds of thousands of crypto users is Uniswap.</p><p>This has been one of my autistic obsessions. Uniswap’s upgrades have always marked something new for DeFi. And now we’re on v4, it seems that the exchange has matured to a point that it may truly be able to cater to sophisticated market making strategies that are usually only found in TradFi.</p><p><strong>Warning</strong>: Long post ahead and may involve some math. Do not read beyond the asterisks if you get seizures from seeing graphs and formulas.</p><p>********</p><h2 id="h-uniswap-v1-eth-centered-swaps" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Uniswap v1: ETH-Centered Swaps</h2><p>At this point DEXes are just dawning and we have <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/donkadonk.eth/dzZkJ15c_gkV1oEdZYQ18zIfxNO-0yPUu8Jb9HbKlsM">a simple market maker</a> that uses the ubiquitous <strong>x * y = k</strong> formula. The most prominent characteristic of V1 though is that it only allows for swap between native ETH and another ERC-20 token. So, if say you want to swap your USDT for some MATIC, then you will have to go the route of swapping USDT to ETH, and then swapping that ETH to MATIC. There is no getting around it. You pay double fees just to make that trade, which iz sadt. And slippage sucks because there is no way to read what is happening on the other pools to find the most cost-efficient way to trade your tokens. And that is how V2 entered…</p><h2 id="h-uniswap-v2-the-birth-of-routers" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Uniswap v2: The Birth of Routers</h2><p>Uniswap V2 fixes problems from V1 by giving the users the ability to create a custom ERC20-ERC20 pool, do a direct swap inside that pool, or use the Router.</p><p>Ok that’s new. Since Routers were introduced, we now have a smart contract that can look to every liquidity pool contract that was permissionlessly created using Uniswap V2 protocol. It enables users to choose the “route” of their transaction. For example, somebody may opt to swap their USDT for MATIC through the USDT-DAI / DAI-ETH / ETH-MATIC route. It may sound crazy because who in their right mind will complicate the routes of their trades, but this is especially useful for arbitrage which is a technique MEVs use to profit from price discrepancies between pools. I’ll have a separate article for this maybe, but no promises. The important thing you need to know is that arbitrageurs help these pools become more liquid so the slippage is lessened when you make a trade between less common tokens in Uniswap.</p><p>There are actually many updates in V2, but I’ll only talk about one more coz we’re gonna hang around V3 and V4 much longer: V2 now has an on-chain oracle price feed.</p><p>This improvement is not actually for Uniswap direct users but for other markets and protocols that will use their exchange prices as basis for transaction.</p><p>Anyway, the problem is that many pools in the Uniswap fluctuate wildly, and they’re not even hard to manipulate because you can easily <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/donkadonk.eth/rpJoz2uE-6v0U9ZKiffVA2foSa2BKhzBla9927ms9Hc">borrow millions of dollars from a flashloan</a>. The attacker can use an arbitrage bot to pocket the discrepancy from the real price. So wat did Uniswap do?</p><p>V2 then made the oracle to measure prices at the end of the block so that any manipulator wouldn’t be able to arbitrage to their choice of protocol without first making a bad trade in Uniswap at the end of a previous block.</p><p>Anyway, after a block is mined, there will be a few seconds before the next block comes. This elapsed time will be measured and then multiplied to the end-of-block price (and now we have the time-weighted price). And the process starts again on the next block. You simply sum these time-weighted prices up for whichever time interval your external contract wanna measure.</p><p>A little visual to illustrate TWAP math:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/64b0771368f3bb03f02900cf4f13191d3d012f80bd9d63712c8b542b15c14ea7.png" alt="Image from Uniswap. Sorry I&apos;m too lazy to type in the formula. " blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Image from Uniswap. Sorry I&apos;m too lazy to type in the formula.</figcaption></figure><p>Notice that we’re simply computing the average price movement like normal, which is the cumulative price on that period over the total elapsed time. You will later on learn that this is not a very accurate formula when you wanna compute for the average change in price in terms of percentage (and not numbers).</p><h2 id="h-uniswap-v3-range-bound-liquidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Uniswap v3: Range-bound Liquidity</h2><p>I’ve waited like 3 posts to talk about this LOL. V3 is my personal favorite upgrade because it’s solving a huge problem in capital efficiency and at the same time, it has opened new market making strategies aside from just passive liquidity providing.</p><p>Remember the problem I’ve mentioned in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/donkadonk.eth/dzZkJ15c_gkV1oEdZYQ18zIfxNO-0yPUu8Jb9HbKlsM">Constant Product Market Makers</a>? Well, those liquidity distributed at extreme prices may never be utilized. So, V3 introduced concentrated or range-bound liquidity where liquidity providers can pick at which price range they are willing to provide. Now they can find themselves always in the middle of the liquidity graph where most of trades happen and consequently where most of commissions are earned. And it benefits the user too, because more liquidity in their trading prices means less price impact on their trades.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/52a68f0d6d58112af5664d87d1a159fbc90cafe9dba0a938998a3035e04906ee.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This design has opened some new questions though because the liquidity will be irregularly distributed across this curve. For example, how will Uniswap even compute for the exchange rate? Surely, we can’t just <strong>x * y = k</strong> the whole thing to compute for the price in any arbitrary point in this curve. And how do you even determine at what exact values can the liquidity be placed?</p><p>I scratch my earz, cause we’re getting to ticks. Just take a look:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/412b25dfad996e9a07fa2a73c1bd1268ec49669681ed6dc98a6016c6509a6cff.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>These little markings along the liquidity curve represents a price point that you can use to either define which price you can place an order, or define the range where you can place your liquidity. Imagine ticks like a pixel on your computer. You can’t have two colors inside a pixel the same way you can’t have two different liquidity in between ticks.</p><p>So, basically, you have a definite <strong>k</strong> value in the <strong>x * y = k</strong> for each space between ticks.</p><p>The catch is that ticks are not expressed in terms of currency. They are a different unit of price, because ticks and their spacing still have to be initialized and determined depending on the fee tier of the pool.</p><p>So, how does Uniswap compute for the tick and how can you convert these ticks into a price value?</p><p>The relationship between the price and tick is this:</p><p><strong>Tick = log1.0001(Price)</strong></p><p>Don’t let this formula intimidate you tho, it only means that each tick is the power that you have to raise 1.0001 to in order to receive a certain output price.</p><p>If we rearrange this formula to get the price at any given tick, then it looks simpler:</p><p><strong>Price = 1.0001^τ</strong></p><p>Uniswap pools always have a range of ticks between -887272 and 887272, which equates to 2^-128 to 2^128 in price, which is practically 0 to infinity and covers the whole range of possible prices.</p><p>But it is not advisable to use the <strong>Price = 1.0001^τ</strong> formula in computing price because of precision loss due to integer constraints. So we have a variable called <strong>sqrtPriceX96</strong> to compute for price.</p><p>Without going into much details, sqrtPricex96 is exactly what it says: the product of the square root of the price and 2^96 (as it is expressed in bits). Why this formula tho? It was the largest value for precision that allowed for the best gas efficiency.</p><p>Anyway, going back to concentrated liquidity... If you’re a degen looking for market making opportunities, you have to set the narrowness of your range depending on how actively you wanted to manage your LP positions. If you don’t want to watch it too often, then set your range wide enough to capture the bulk of price movement (and fees). If you’re the more of an active LP-er, then you can narrow your positions so that your liquidity earns more fees for less, but you have to move it an pay gas when the price has gotten out of your range.</p><p>There is so much more to this, but I guess this whole V3 thing has opened up a bunch of new possibilities in terms of innovation (comparatively) in traditional orderbooks and market making strategies on crypto, and it is a long topic so it will be for another post. I promise it’ll be less abstract and more visuals and stories from there!</p><h2 id="h-uniswap-v4-hooks-and-the-death-of-routers" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Uniswap v4: Hooks and the Death of Routers</h2><p>Just as I thought that it couldn’t get any wackier in V3, Uniswap announced its release of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v4-core/blob/main/whitepaper-v4-draft.pdf">V4 drafts</a> just a few days ago. And boy, it was so different. For one, the Router is gone. The pool factory is nil, too. Instead it has a singleton contract with just about 500 lines of code that holds all of the pools you could ever use in Uniswap 🤯🤯🤯 This saves gas big time, as you no longer have to pay double fees and gas even if your most efficient route takes more than single swaps.</p><p>If that’s not different enough, pools can now have their own rules, too. Pool creators can now use this thing called hooks, which are externally deployed contracts where you can write your custom logic and execute it at a specific point of trigger from the pool. When a trigger event happens, Uniswap’s hook contract calls back the specified hook of the pool creator.</p><p>This deadly combination of having a Singleton contract to hold all pools and having Hooks to create custom rules for each of them can have several use cases for the traders and liquidity providers:</p><p>-they can have oracles of all shapes and sizes, making it easier to create tools for both manual and algorithmic traders alike</p><p>-pools can have volatility-based fees</p><p>-LPs can automatically send their inactive liquidity somewhere else to make more $$$</p><p>-autocompounding of LP rewards</p><p>-redistributing internal MEV profits to the LPs</p><p>…These are just some suggested by Uniswap Labs themselves. There are definitely more. Danki haz a few ideas like active cross-pool liquidity management, but maybe and I’ll share more of these once I’m totally convinced of my POC.</p><h2 id="h-aliens-next" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Aliens next?</h2><p>Geez. If you’re still here, then congratulations, you now have more knowledge of AMMs than most DeFi degens. I’d consider you a fren.</p><p>And since you’re frens with Danki now, I’ll tell you a secret: I expect V5 to be more boring than V3 and V4. Maybe a super cheap crosschain swap for later or some improvements in privacy. Though who knows, my brain iz too little to wrap around all the developments in the space and the new ways you could connect them together. But my bet is that we’re transitioning to a new meta after Uniswap has already solidified its most important features. The ground has already been laid for new financial products to thrive.</p><p>Or if that’s not it… maybe some AI or ML integration will reopen the Pandora’s box for DEXes, but I dunno how that will happen. I’d like to have you share some insights too! 🐴🌟</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/87da0e59025b0ec373c69d96574cec0276e90d0c31286681502f967a4563ac0b.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[LSD and the New Dimensions of ETH ]]></title>
            <link>https://paragraph.com/@tinerispe/lsd-and-the-new-dimensions-of-eth</link>
            <guid>RK7igkRowK2HBgdbwuND</guid>
            <pubDate>Wed, 21 Jun 2023 00:39:53 GMT</pubDate>
            <description><![CDATA[I’ve always thought that not being able to use your staked assets was sort of the catch in earning your staking rewards. That’s the way it works with time deposits in banks, or even in securities where if more people hold the asset the stronger it becomes. On hindsight, having all that liquidity locked up in a validator is kind of a bummer for the whole ETH ecosystem. Consider these two scenarios: First is an eth economy where the value of the currency is high because a lot of it has been loc...]]></description>
            <content:encoded><![CDATA[<p>I’ve always thought that not being able to use your staked assets was sort of the catch in earning your staking rewards. That’s the way it works with time deposits in banks, or even in securities where if more people hold the asset the stronger it becomes. On hindsight, having all that liquidity locked up in a validator is kind of a bummer for the whole ETH ecosystem.</p><p>Consider these two scenarios: First is an eth economy where the value of the currency is high because a lot of it has been locked up by speculators looking for yield. The second is an economy where the value of eth is high because a lot of people have been using it to pay gas, burning it on transactions, and biggest of all, using it to supply liquidity in DeFi protocols. Which one is more sustainable?</p><p>I dunno bout you, but I’d go for the second- the demand-and-churn driven price. Merely inflating a token’s value by hoarding the supply doesn’t seem to be a healthy strategy for growing an ecosystem because at some point, stakers will be looking to sell.</p><p>You could be wondering what em blabbering about. I’ll tell you fren. But please don’t call da tokhang task force coz Danki is completely sober. I just stumbled on some really good shit. It’s called LSD-- short for Liquid Staking Derivatives.</p><h2 id="h-unfreezing-liquidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Unfreezing Liquidity</h2><p>The catch is that we need to stake to more validator nodes to secure the network and prevent centralization, but we also need that eth for liquidity in our DeFi markets. Well, there’s the demand.</p><p>The other dot to connect is that eth staking protocols issue their stakers a token that represents their staked eth. They needed it to keep track of who owns what amount of stake, and then they burn this token once the eth is ready to unstake. This token is by the way your LSD, and it functions like an IOU from your staking protocol.</p><p>The final dot in this trifecta of liquidity is that some dexes and exchanges started to offer ETH to stETH swaps… so you don’t even have to wait for your stake to unlock to get back your ETH .</p><p>Given this high level of liquidity, it didn’t take long for people and protocols to realize that they could as well use their staked ETH as a means of exchange… almost as good as real ETH, with gas usage the exception.</p><h2 id="h-and-so-the-first-domino-falls" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">And so, the first domino falls…</h2><p>There are many, many implications to LSDs, but the first thing that happened is that it just made that ~5% yield you get from staking your ETH totally risk free. It’s almost a no-brainer to stake now because you can just exchange your staked tokens back to ETH in any popular dex out there, at any time you wish.</p><p>And while danki iz (personally) not a pure believer of efficient markets, i know that risk-free transactions will be a magnet to eventually pull a really huge portion of ETH to be staked inside validator nodes.</p><p>Except that actually, it already happened.</p><h2 id="h-the-start-of-an-lsd-summer" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The start of an LSD Summer</h2><p>The biggest barrier to entry to staking is the 32 ETH minimum requirement to run a validator node. That’s a lot for your average cryptobro. So staking protocols like Lido offered to pool people’s ETH to run nodes. Now anyone can get staking yields for any amount of they wanna stake.</p><p>Of course people eat it up. And now Lido with its whopping $13.08B TVL (that’s approximately 26% of all value locked in DeFi application) became the biggest protocol in the face of the planet, in just a span of months. And my hypothesis is that it will still get bigger.</p><p>It gets crazier though.</p><p>At one point Lido’s dominance posed a threat to Ethereum’s decentralization because they operate a huge portion of validator nodes. They eventually shifted to delegating to other node operators, but the operators still need to apply to Lido, making it permissioned. This doesn’t sit well if you’re an advocate of decentralization wanting to stake to both earn yields and help the ecosystem at the same time.</p><p>So you’ve guessed it, new liquid staking protocols emerged. For instance, there’s RocketPool that runs a permissionless network of node operators and offers the same thing Lido does but with a more diverse set of validators and infrastructure.</p><p>Then there are some more protocols that were made for better UX and easy integration, but you get the gist.</p><h2 id="h-change-of-meta" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Change of Meta</h2><p>Der are two more things I wanna give as your takeaway about LSDs: first is that allowing for staked ETH to be spent like normal ETH opens up a whole new market for yield-bearing assets. And I wanna talk about fascinating new primitives that emerged because staked ETH.</p><p>Second is that since ETH is not merely a financial asset but also a way to secure the consensus layer of a network, liquid staking derivatives also opens up new possibilities and risks for other aspects of the ecosystem, and not just on the DeFi side.</p><p>But I’ll take a break for now. There’s also somewhere in DeFi that my hooves can’t just stop typing about. Those are… AMMs haha. I promise it’s going to be a full story next time tho 🐎✨</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/9c87b55fb4c27ef180a5321fdae0d8b9e5fc22381908874d9be7c2e945fa5a0d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Bridges]]></title>
            <link>https://paragraph.com/@tinerispe/bridges</link>
            <guid>CG5tiMIZ0ZxBAx5l2yZK</guid>
            <pubDate>Sat, 03 Jun 2023 01:30:20 GMT</pubDate>
            <description><![CDATA[Danki iz in a crossroad right now. At the time it looks like I’m in the perfect spot to be a bridge. Em smart enough to build and fully understand what’s going on the cryptoverse but em also dumb enough to only talk about it using relatable human language. And maybe dat is how I got you, my few handful of subscriber frends. That, or you enjoy seeing me make awkward metaphors about an obscure, super niche subject that my mom would never understand. But here’s where being a bridge gets tricky: ...]]></description>
            <content:encoded><![CDATA[<p>Danki iz in a crossroad right now.</p><p>At the time it looks like I’m in the perfect spot to be a bridge. Em smart enough to build and fully understand what’s going on the cryptoverse but em also dumb enough to only talk about it using relatable human language.</p><p>And maybe dat is how I got you, my few handful of subscriber frends. That, or you enjoy seeing me make awkward metaphors about an obscure, super niche subject that my mom would never understand.</p><p>But here’s where being a bridge gets tricky: at one point, you’ll feel the gravity pulling you towards the world with the larger entropy. In one of those days, you start thinking <em>“What if I stop half-assing these two worlds and commit to just one?”</em></p><p>That day is today.</p><p>I’ll tell you the story at the end of this post, but first let’s talk about cross-chain bridges.</p><h2 id="h-why-do-we-even-bridge" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Why do we even bridge?</h2><p>Say you want to send some ETH as a gift to your crush who is a Solana moonboy. Surprisingly, he would gladly accept your ETH (and maybe your little hart as well) but there’s just a little problem: Solana is a different chain from Ethereum.</p><p>Now you might ask, “Why is there even a problem with sending ETH to Solana? Can’t we just send it to the other chain and transact in peace like god intended us to be?”</p><p>Well, sadly, the two networks are built differently. Their data structures, mechanisms, as well as programming languages are not the same. Imagine wanting to send a text message using telegram. You can’t no matter how destined you are.</p><p>Here’s another more realistic scenario because let’s be honest your crush doesn’t like you: what if you wanted to use your ETH as collateral to a loan to a DeFi protocol in Solana? How do you even do that?</p><h2 id="h-breaking-down-the-problem" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Breaking down the problem</h2><p>My dankibrain got very confused when bridges are explained to me. But then I realized it is confusing because it is a complex problem that needs to be broken down. So after hours of chewing and chewing information, I decided that there are three components to the cross-chain interoperability problem:</p><ol><li><p><strong>How the blockchains communicate their state</strong></p><p>Since the chains vary so much in their design, there is only one way they can communicate their state to each other, and that is through off-chain attestation. Meaning, you get an intermediate blockchain or a decentralized oracle network to tell the chain what is happening to the other. This is how LayerZero and other bridges that use Chainlink’s CCIP work.</p></li><li><p><strong>How the tokens are ‘transferred’</strong></p><p>Lock and Mint aka. Asset Wrapping - the user locks their tokens inside a contract (or a node depending on the design of the network). An attester verifies that the user has locked their tokens and then mint a synthetic version of it in the destination network. When the user wishes to bridge back, they burn their synthetic token and get released their locked original tokens back.</p><p>Omnichain Fungible Tokens - this is basically just lock-and-mint but instead of minting a chain-compatible synthetic asset, you deploy a locked proxy contract in the omnichain so that the tokens remain in their native form, and then you can mint it to a destination chain.</p><p>Cross-chain Pools - This clever mechanism didn’t even require cross-chain communication. Just buy a stable asset of a protocol that has it in many chains, and then exchange that asset back in the destination chain that you wish. Voila.</p></li><li><p><strong>How the bridge ensures that no tokens will be stolen</strong></p><p>This is probably the most heated topic when it comes to bridges but also the most no-brainer. You got two options when it comes to who manages your locked bridge tokens:</p><p>Trusted/Custodial bridges- when your locked original tokens get managed by a central governing authority that verify and confirm your transaction and then mint a synthetic equivalent token that you can use on the other chain. The downside, of course, is that you have to trust them to not run away with your real tokens while you’re using their synthetic version. This was implemented in wBTC with BitGo as their institutional custodian.</p><p>Trustless bridges- when your tokens get minted and burned algorithmically. Your tokens are locked in a smart contract until you have burned the synthetic token. There is no getting around the system.</p><p>As in the case of Bitcoin, one example of a trustless bridge is Wanchain’s renBTC, where the token-holding nodes are cryptographically secured through Multiparty Computation. Participants only hold a component of the key to the locked tokens so that no single node can steal it.</p></li></ol><h2 id="h-a-game-of-weakest-link" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">A Game of Weakest Link</h2><p>It’s always the bridges that get hacked in DeFi. At first, it’s unclear to my little dankibrain why, but then I put it like dis: Why do airports always have the tightest security?</p><p>Aha, because the entry of even just a single bad actor can terrorize the whole country. If your neighbors are sick, you get sick as well. If one of the chains are compromised, it can result in an imbalance of tokens for both. The attack surface just significantly got bigger.</p><p>And some of the biggest bridge hacks that I can remember aren’t even because of a bug. Bridge design is quite complex that sometimes, the architecture of the system is the bug. And then there is also that single smart contract that keeps all that valuable locked tokens, which is a pretty hot target for a lot of mallories.</p><p>Anyway, that’s enough caution to using bridges for you. Just use native tokens if you can. And may the odds be ever in our favor.</p><p>******</p><p>I had two brushes with the larger blockchain world this week.</p><p>First is dat I met a frend who is hellbent on spreading a blockchain movement in the country. He is a very well-connected guy but he wanted to start developing in this small city in the mountains.</p><p>I can see his idea though. He got jaded in the mainstream web3 culture in the big city where people seldom talk about real world use cases of decentralized tech. So he’s starting here where the population is young and passionate and highly educated. A clean slate. We are on the same cause.</p><p>Two days later, I met my mentor. For some cosmic anomaly this guy who is already deeply entangled in the global DeFi scene found Danki on the webz while he was takin a vacation in his hometown. And his hometown, of all places, happens to be this sleepy town where I live.</p><p>Dankibrain haz never expanded so much as the few hours he haz talked about DeFi. And I have never thought so hard about where I wanna go after that conversation. Which leads me to my point…</p><p>I’d stop being your bridge and start tying you to my waist so I can pull you to the other world. And I might talk more freely in this blog 😅 I’d probably write about some alien topics dat I’m scared no one will read.</p><p>Or yeah, the goal anyway iz to tap dance alone so it doesn’t really matter if anyone is reading dis. But I hope you still hang around in dis little rabbit hole. DeFi is a big place, I’d tell mor shiny moon math soon. 🐴✨</p>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/adde3af6653bb55d5183fd674e08270b21632ac483ed011658014e3901dd0283.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ZK Proofs Part III: More ZK Math]]></title>
            <link>https://paragraph.com/@tinerispe/zk-proofs-part-iii-more-zk-math</link>
            <guid>DMt0eRbA7g0n2Rg3aLAC</guid>
            <pubDate>Wed, 10 May 2023 16:54:33 GMT</pubDate>
            <description><![CDATA[Where were we… ah yes, Fiat Shamir Transformation. Anyway from dis point on we’ll probably have less Marites and more cryptography, because I want my frens to know wat these things are called in the actual ZK world. So lezgow.Fiat-Shamir Transformation in DetailAs I sed, ZK-SNARKs make use of a trusted setup as their verifier. And that a trusted setup is an algorithm that looks like dis:Screenshot taken from WikipediaNow, there are two things you can note about this: First is that the trusted...]]></description>
            <content:encoded><![CDATA[<p>Where were we… ah yes, Fiat Shamir Transformation.</p><p>Anyway from dis point on we’ll probably have less Marites and more cryptography, because I want my frens to know wat these things are called in the actual ZK world. So lezgow.</p><h2 id="h-fiat-shamir-transformation-in-detail" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Fiat-Shamir Transformation in Detail</h2><p>As I sed, ZK-SNARKs make use of a trusted setup as their verifier. And that a trusted setup is an algorithm that looks like dis:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/33ae94836082e0911f64f612ce778f615c0a17f3bf0c6ce1d39f0d29d476014f.png" alt="Screenshot taken from Wikipedia" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Screenshot taken from Wikipedia</figcaption></figure><p>Now, there are two things you can note about this:</p><p>First is that the trusted setup uses a randomizer at #2, when Peggy picks a number to evaluate from.</p><p>The second is that in #3 Peggy runs her answers through a hash function… and a hash is a one-way function where you can compute for the answer but you cannot derive the variables that were used to get your answer. So at this point, there’s no way Peggy could have guessed the result of her hash beforehand, not until she runs her variables in the trusted setup.</p><p>This two-part mechanism ensures that the prover could not fake their proofs.</p><p>At #4, Peggy just creates the proof by evaluating her hash through another function and submitting this value along with the number she has computed in #2. This proof is publicized and can now be checked by anyone by plugging these values to the equation in #5.</p><p>And that, my frens, is how ZK-SNARK works.</p><h2 id="h-polynomial-commitment" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Polynomial Commitment</h2><p>Remember why Becky couldn’t just recite the polynomial to Marites in the previous <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/donkadonk.eth/ofe5mGXiFVQGmk3AaKxdzwH5DytIChCQNfZfuHc5avw">article</a>?</p><p>Becky as the prover can’t just give away the polynomial because it practically gives away her 10 million gossips to Marites who may or may not be trying to fish for it. Plus the polynomial is just too lengthy to recite.</p><p>So instead of doing that, we use a commitment scheme called the Polynomial commitment scheme.</p><p>Wat agen? Okay let’s dissect dis frens…</p><p>A commitment scheme allows you to commit a value while keeping it hidden, like putting a note inside a vault. In our analogy, Becky could put her polynomial inside the vault to solve this problem. Now she sends the vault to Marites.</p><p>There’s still a problem with that tho. Marites doesn’t have the key to the vault, and Becky isn’t willing to give the key because that would be giving away her precious polynomial to a stranger she doesn’t trust.</p><p>So what does Becky do? She constructs a special kind of vault that can prove to Marites the value of her polynomial at whichever point Marites asks to evaluate. And dat special type of vault is wat we call a <strong>Polynomial Commitment</strong>, or PC.</p><h2 id="h-now-for-the-magic-trick" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Now for the magic trick…</h2><p>Merkle Trees, the data structure through which blockchain data are stored, has properties that act like a polynomial commitment scheme.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/06b4cce5cda2b9353fd665d5de53b46249fa4c0ba980e460403034318d34afa2.png" alt="Image taken from Wikipedia" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Image taken from Wikipedia</figcaption></figure><p>As you see, each node contains a hash of the child nodes below them. So the top hash, or the merkle root, is a hash you can only arrive to if you compute all the hashes from the nodes below it. The top hash represents and proves the existence of all the data below it without revealing what they are.</p><p>Can you see the similarities now?</p><p>What if we do this then… Let’s store each term (the variables and coefficients) of Becky’s polynomial inside a merkle tree node. Then we hash them to arrive at a merkle root. Now this top hash serves as a polynomial commitment without revealing any part of Becky’s polynomial, innit?</p><p>Now you know how it all fits together in ZK-SNARKs.</p><h2 id="h-wait-ders-more" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Wait, ders more…</h2><p>Gomsh it’s been a long ride, and by now I hope you already have an idea of how the parts work to produce a ZK Proof. We used ZK-SNARK in our example but as I sed, there are many, many flavors of ZK. And there are many kinds of commitment schemes.</p><p>For instance, let’s see a polynomial commitment scheme called <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://vitalik.ca/general/2017/11/22/starks_part_2.html?ref=mirror.xyz/donkadonk.eth/">FRI</a>, which doesn’t even need a trusted setup and is being used by the other common type of ZK, ZK-STARKs.</p><h2 id="h-fast-reed-solomon-interactive-oracle-proofs-of-proximity-i-cant-pronounce-it-so-just-say-fri" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Fast-Reed Solomon Interactive-Oracle Proofs of Proximity (I can’t pronounce it, so just say FRI)</h2><p>Ders a little problem in checking the polynomial commitment in order to verify a polynomial. First, how does the verifier tell if the commitment even represents an actual polynomial? And second and more commonly, how do we check if the polynomial is not so enormously sized that it’s impossible to use for computation?</p><p>Dis is where we use FRIs.</p><p>The idea with FRI is that we check the degree of the terms inside the polynomial by running them through a certain mathematical transform (commitment phase) and then letting the verifier evaluate how a few points in the diagonal fits within the range of a graph derived from a billion data points (querying phase).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ec539bd34a0f6c2af5726be3ef5ab27daf32b31f46e5449fad418ada887d1e91.png" alt="Screenshot taken from Vitalik Buterin&apos;s blog" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Screenshot taken from Vitalik Buterin&apos;s blog</figcaption></figure><p>There are still ways to make the FRI more efficient for both the prover and the verifier which you can read on Vitalik’s <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://vitalik.ca/general/2017/11/22/starks_part_2.html?ref=mirror.xyz/donkadonk.eth/">post</a>.</p><p>There ar also many other polynomial commitment schemes that are widely used in the blockchain space, like the KZG commitment that will be used in Ethereum along with Proto-Danksharding. But I’ll reserve that for a different post later coz boy, what a story already.</p><p>Anyway, I hope dis has fed your ZK curiosity for today. Maybe on the next post we’ll talk about the application side and how ZK circuits are actually created. For now, hold Danki’s promise dat der will be another ZK post. Byebye!</p><hr><div data-type="subscribeButton" class="center-contents"><a class="email-subscribe-button" href="null">Subscribe</a></div>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/a090acfc299d26a5b37f547ff9b4c13e4c6c0b723926c310f74b9ff71ade73f3.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ZK Proofs Part II: ZK Maths]]></title>
            <link>https://paragraph.com/@tinerispe/zk-proofs-part-ii-zk-maths</link>
            <guid>CkeDjIJ3e0BdCwx7DtEX</guid>
            <pubDate>Tue, 09 May 2023 17:15:55 GMT</pubDate>
            <description><![CDATA[Standing before Danki today are ghosts of the audit reports I haven’t read and dat job hunt I’ve been meaning to do for weeks now… but in the name of mighty procrastination, let’s write something totally fun but completely unrelated to everything I needed to do: ZK maths frens. If you have no idea what this is all about and why danki is so happy as my hooves type dis post, then check out Part I. But if you’re too lazy to click… ZKP or Zero Knowledge Proofs is a cryptographic mechanism that al...]]></description>
            <content:encoded><![CDATA[<p>Standing before Danki today are ghosts of the audit reports I haven’t read and dat job hunt I’ve been meaning to do for weeks now… but in the name of mighty procrastination, let’s write something totally fun but completely unrelated to everything I needed to do: ZK maths frens.</p><p>If you have no idea what this is all about and why danki is so happy as my hooves type dis post, then check out <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/donkadonk.eth/hClpqIrX5yueGzZjZemqVb3sBlkU5ydDvob5y45BrW4">Part I.</a></p><p>But if you’re too lazy to click… ZKP or Zero Knowledge Proofs is a cryptographic mechanism that allows you to prove that something is true without revealing any more information other than the fact that it is true.</p><p>It sounds like magic, so today I’ll demystify how this happens under the hood.</p><h2 id="h-consider-this-problem" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Consider this problem…</h2><p>Let’s say Marites has ten million data points of hot gossip (it’s a really huge set) and told it to a person inside a confession box. That person is Becky, but Marites doesn’t know that.</p><p>How does Marites ensure that Becky is the person who heard all her 10 million hot gossips?</p><p>Well, der are a few ways that Marites can verify from Becky:</p><h3 id="h-option-1-ask-becky-to-recite-all-the-10-million-gossips" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Option 1: Ask Becky to recite all the 10 million gossips</h3><p>Now, this frens, requires a martyr out of Marites and Becky. Because who has time to recite ten-facken-million gossips? It requires so much time and computational power from both of them that it’s impractical.</p><h3 id="h-option-2-ask-becky-to-just-recite-one-gossip-from-the-set" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Option 2: Ask Becky to just recite one gossip from the set</h3><p>Fast, but it doesn’t prove that Becky really has all of the gossips. Becky might just have a lucky guess.</p><h3 id="h-option-3-make-a-polynomial-whose-graph-go-through-all-the-10-million-numbers-that-represent-each-gossip" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Option 3: Make a polynomial whose graph go through all the 10 million numbers that represent each gossip</h3><p>Holy schmoly. This is probably the answer, but you’re now thinkin, “whuuut--?”</p><p>Okay, calm down. Just look at this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d0b9ce123acffa785abd4b71413e96180066a74946bd11418382c051fb755170.png" alt="Image by Porter Adams whose class introduced me to ZK" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Image by Porter Adams whose class introduced me to ZK</figcaption></figure><p>Notice that any two different polynomials go through different points most of the time. There may be a few points where their curves intersect, but that’s about it.</p><p>Now, let’s say dat this is the polynomial that goes through Marites’ 10 million gossips. Of course just imagine the real one will be much, much complicated but for da sake of visualization:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c842b82292f9ac388fda8ddaa5ab58fee7d4d36587605ca3f2d4f4048ce2816d.png" alt="Image from Interactive Mathematics" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Image from Interactive Mathematics</figcaption></figure><p>If Becky knows the 10 million gossips, she can recreate this polynomial.</p><p>But the polynomial will be complex and it is still too lengthy to recite to Marites… Besides, from Becky’s point of view, she doesn’t know if Marites is a actually the person who told her the 10 million gossips while she’s inside the confession box. If she gives Marites the full polynomial, she practically risks leaking all the gossips to some random stranger.</p><p>So Marites, instead of asking Becky for the polynomial, will give Becky a number and ask her to compute for the value of the polynomial at that number. If Becky answers correctly, then Marites is convinced that Becky knows the polynomial and is really the person who heard all her 10 million gossips.</p><p>And voila, that is ZK Maths in a nutshell my frens.</p><h2 id="h-non-interactive-zk-proofs" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Non-interactive ZK Proofs</h2><p>What if there are many people claiming to be Becky? Marites will soon find that it’s tiring to pick a number and it’s taking so much of her energy to ask the same cursed question everytime.</p><p>So everyone agrees to just evaluate the polynomial at x = 123.</p><p>Cool. Now Marites doesn’t even need to ask the question. Anyone who claims to be Becky can just post their answer and a robotic verifier can check.</p><p>But there’s a problem with this. If everyone has the power to set the question and they already know the question beforehand, it will be easier for someone to cheat on the exam innit?</p><p>Presenting ZK Snarks-- dis neat little non interactive ZK creates a trusted setup for picking the number. In the trusted setup, no one knows the number beforehand because it will be generated as the verifier interacts with the setup.</p><p>If you’re curious what this trusted setup is, it’s just an algorithm. And the algorithm is created randomly in a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://zkproof.org/2021/06/30/setup-ceremonies/amp/">trusted setup ceremony</a>.</p><p>Anyway, an example of a trusted setup works like this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/00f81f26d2757da9c0c4dd6155a597a4ecfea655379b3c82d0853ac080cd038c.png" alt="Image from Wikipedia" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Image from Wikipedia</figcaption></figure><p>This math or mechanism where the prover uses a trusted setup to create a digital signature from their proof is called the <strong>Fiat-Shamir Transformation</strong>.</p><h2 id="h-ok-dis-has-gotten-longer-than-i-thought" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ok, dis has gotten longer than I thought</h2><p>Somry fren, but we have to continue dis some other time. Danki iz hungry now, and the next one will be about ways a ZK-SNARK improves all these polynomial verifying via polynomial commitment and recursion, which iz a topic of its own.</p><p>So wait for it. Mor mor math once Danki haz been fed.</p><p>つづく</p><hr>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/f87940482ddc003a8a5041a6442ff594e5119b8713fa1ad139ca88f6de1939af.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Announcement: We are Pioneering DeFi Philippines]]></title>
            <link>https://paragraph.com/@tinerispe/announcement-we-are-pioneering-defi-philippines</link>
            <guid>sRj1kFgUg86ZwIe83vu4</guid>
            <pubDate>Fri, 05 May 2023 08:39:37 GMT</pubDate>
            <description><![CDATA[So, this happened. Two folks asked Danki if we could create a publication for Filipino DeFi enthusiasts. And those two ‘folks’ happened to be THE Walter Boo and Kristian Quirapas of Web3 Philippines. The idea iz already yummy as a sundae on our 35°C sweltering weather, but the cherry on top is better: we three are all writing for you, frens. Three columns from three people with different and reliable expertise, all in one publication. Danki can’t contain it, I hab to tap dance 100x faster 🐴✨...]]></description>
            <content:encoded><![CDATA[<p>So, this happened. Two folks asked Danki if we could create a publication for Filipino DeFi enthusiasts. And those two ‘folks’ happened to be THE Walter Boo and Kristian Quirapas of Web3 Philippines.</p><p>The idea iz already yummy as a sundae on our 35<em>°</em>C sweltering weather, but the cherry on top is better: we three are all writing for you, frens. Three columns from three people with different and reliable expertise, all in one publication. Danki can’t contain it, I hab to tap dance 100x faster 🐴✨</p><p>The thing is, there aren’t many resources about Decentralized Finance that cater to Filipinos and their unique context. We fell inlove with NFT Art and GameFi, and y’all aped into memecoins… maybe it’s time to level up to something that is less abstract and closer to utility? Because beneath all the NFT craze, P2E, and hot new tokens-- beyond all the hype, rugs, and schemes… there is an underlying capital markets dat is developing and it’s time that you know about it, too.</p><p>And no, Danki iz not talking about shiny new things to hype. Please don’t turn DeFi into another marketing scam or I’d be first to kick you. Em talking about actually understanding blockchain technology, getting a detailed perspective on how it works, witnessing firsthand its application in finance and many other things. We’re talking research. We’re talking analysis that iz not fed to you like junkfood to the brain, but something you can do on your own.</p><p>Because we believe in our readers’ intelligence. And so we are making a channel that allows you enough understanding to make your own sober judgements of blockchain protocols and their implications.</p><p>And of course, more memes.</p><p>See you around!</p><hr><div data-type="subscribeButton" class="center-contents"><a class="email-subscribe-button" href="null">Subscribe</a></div>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/f8054d4b7be5d3c64b8be1efae9618b59b6ee0bf3992d0711eedc5ce77159c31.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Constant Product Market Makers]]></title>
            <link>https://paragraph.com/@tinerispe/constant-product-market-makers</link>
            <guid>BmthugTs0lPOLxqpx30N</guid>
            <pubDate>Fri, 05 May 2023 07:47:31 GMT</pubDate>
            <description><![CDATA[I took a vacation. Well, not really. I was reading codebases everyday and practically lost track of the days. And the further I went, the harder it was to explain what the hekk iz Danki up to. I was already deep into Curve’s Stableswap Equation when I realized that almost a week has already passed. So here I am frens, doing my best attempt to catch you up. Now the story. It all started with Constant Product Market Makers…The Most Basic Type of SwapIf you’re swapping your USDT for ETH on say, ...]]></description>
            <content:encoded><![CDATA[<p>I took a vacation.</p><p>Well, not really. I was reading codebases everyday and practically lost track of the days. And the further I went, the harder it was to explain what the hekk iz Danki up to. I was already deep into Curve’s Stableswap Equation when I realized that almost a week has already passed.</p><p>So here I am frens, doing my best attempt to catch you up.</p><p>Now the story. It all started with Constant Product Market Makers…</p><h2 id="h-the-most-basic-type-of-swap" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Most Basic Type of Swap</h2><p>If you’re swapping your USDT for ETH on say, Uniswap v1, then USDT doesn’t magically transform into ETH tokens through some wizard program. No na no… instead da ETH is already stored somewhere, as well as the USDT you just deposited. And that ‘somewhere’ is called a liquidity pool.</p><p>In the USDT-ETH example, a liquidity pool works like this:</p><ol><li><p>Somebody initially supplies two tokens in the pool, in equal value</p></li><li><p>As users like you start swapping their tokens in the pool, the ratio of the two tokens change</p></li><li><p>But the total liquidity remains the same, just the ratio is different</p></li><li><p>The price of one token is computed by the ratio of its supply with the other token.</p></li></ol><p>…And this liquidity is expressed in the famous Constant Product MM equation:</p><pre data-type="codeBlock" text="x * y = k
"><code>x * <span class="hljs-attr">y</span> = k
</code></pre><p>Where x is the amount of token1, y is the amount of token2, and k is the liquidity of the pool.</p><p>So the price of each token changes as people swap because the ratio in the liquidity pool changes.</p><h2 id="h-it-looks-like-dis" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">It looks like dis…</h2><p>Say, somebody initially supplied 10,000 USDT and and 5 ETH in the pool:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f0575824b377efefa53a931bf38a4b49ce3f4b934d23799b759d784c919c1a06.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>At this point, if we use x <em>y = k, then</em> k = 10,000 * 5 (which is 50,000). And the ratio will be 2,000 USDT per 1 ETH</p><p>Now, enter you, and you wanna swap your 2000 USDT for ETH. Let’s ignore fees for now. How will this affect the pool and at what price will you get your ETH?</p><p>Well, you are practically adding 2000 USDT in the pool which should correspond to a change in the amount of ETH. ETH will decrease in order for <em>k</em> to stay constant.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/25efc24bd5ff4ce7969e75b2b8fc8b92f1141e34c49107d95fd9fc37567455e4.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Then, the market maker will solve for the amount of ETH it will give you:</p><p>k = 50,000 = (10,000 + 2,000) * (5 + dx)</p><p><strong>dx = -0.833333 ETH</strong></p><p>ETH supply in the pool will decreased by 0.833333, which is the amount they’ll give you for the 2000 USDT you deposited</p><p><em>“But why 0.8333333, I thought the price is at 2000 USDT per 1 ETH!”</em></p><p>Were you cheated? Not really, because the ratio of USDT per ETH was going up as you were adding more USDT to the pool, bringing the price of ETH higher than what was initially shown… And dat, frends, is called the <strong>price impact</strong> of your trade.</p><p>And because price is computed by the two token’s ratio in the pool, the lower the k value or liquidity, the bigger the price impact of each swap.</p><p>In traditional finance, you can also recognize the same phenomenon where the low-volume assets are more volatile. In their case, instead of an algorithm, the market makers are the hedge funds (except the hedgies got alpha but that’s for another story lol).</p><p>Anyway, the amount of tokens in the pool is not only affected by swaps. Liquidity in the pool can also be removed or added.</p><h2 id="h-then-what-happens-if-somebody-provides-more-liquidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Then what happens if somebody provides more liquidity?</h2><p>When somebody provides liquidity to a protocol, they get minted what is called an LP Token. The <strong>LP token</strong> represents the percentage of liquidity that the user owns in the pool. This keeps track of how much of the pool they can withdraw and the share of rewards they get from the collected fees.</p><p>Ok, let’s see this in action.</p><p>Say, your good frend Marites supplied 5 more ETH and 10,000 USDT to the pool at the current rate of 2000 USDT per 1 ETH.</p><p>The pool had 5 ETH and 10,000 USDT before and had minted 10 LP tokens to its providers, and now that Marites added to it, it has 10 ETH and 20,000 USDT. Rich frend Marites now owns 50% of the pool, and she gets minted 10 LP tokens for that. Now she owns 10 out of the 20 LP tokens.</p><p>If Marites wanted to withdraw her share of the pool, she will burn the 10 LP tokens.</p><p>But will Marites get the exact same 5 ETH and 10,000 USDT she deposited?</p><p>99% of the time, she will not. Instead she will get her two tokens in whatever ratio the pool currently has after users have swapped tokens in it. This could result to impermanent loss.</p><p>I wanna talk about impermanent loss, but das a long topic that I reserve for investors, traders, and degens in DeFi Philippines. If you wanna know more about the benefits and risks of providing liquidity to a Constant Product AMM, wait for my post in there!</p><h2 id="h-you-will-rarely-find-pure-cpmms-nowadays" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">You will rarely find pure CPMMs nowadays</h2><p>It might sound amazing on paper, but the reality iz Constant Product MMs are very inefficient in managing liquidity.</p><p>Most of the liquidity in the pool will fall on the extreme ends of the curve where they aren’t utilized. Meanwhile, price impact is still huge in the middle range where most of the trades happen. So the newer dexes and protocols use a different model to remedy this inefficiency… and that’s what I actually wanna talk about. haha</p><p>But at least now you understand how the most basic form of AMM works, and we can move on to the real exciting part. We’re gonna talk modern market maker models in the next posts! 🐎✨</p><hr>]]></content:encoded>
            <author>tinerispe@newsletter.paragraph.com (0xDanki)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/40aa518732f74661a62f162fc7de576a101528128b5d7a0bfac3480f929418b7.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>