<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Leo Joseph]]></title><description><![CDATA[Leo Joseph]]></description><link>https://leojay-net.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 21 Oct 2026 13:46:02 GMT</lastBuildDate><atom:link href="https://leojay-net.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From Solidity to Clarity: My Journey Building DeFi Direct on Stacks]]></title><description><![CDATA[Explore DeFi contract conversion from Ethereum to Stacks, focusing on smart contract differences, error handling, and transaction processes]]></description><link>https://leojay-net.hashnode.dev/from-solidity-to-clarity-my-journey-building-defi-direct-on-stacks</link><guid isPermaLink="true">https://leojay-net.hashnode.dev/from-solidity-to-clarity-my-journey-building-defi-direct-on-stacks</guid><category><![CDATA[clarity]]></category><category><![CDATA[Solidity]]></category><category><![CDATA[Blockchain]]></category><category><![CDATA[defi]]></category><dc:creator><![CDATA[Joseph Aleonomoh]]></dc:creator><pubDate>Mon, 30 Jun 2025 00:36:58 GMT</pubDate><content:encoded><![CDATA[<p><em>Hey there! So I decided to take my DeFi Direct contract from Ethereum (Solidity) and port it over to Stacks (Clarity). Let me walk you through this wild ride of converting between two completely different smart contract languages. Buckle up, because this is gonna be a long one!</em></p>
<h2 id="heading-the-big-picture-what-were-building"><strong>The Big Picture: What We're Building</strong></h2>
<p>Before we dive into the nitty-gritty, let me explain what DeFi Direct actually does. It's a bridge service that lets users convert their crypto tokens directly to fiat currency. Think of it like a fancy crypto ATM, but instead of spitting out cash, it deposits money straight into your bank account. Pretty cool, right?</p>
<p>The core flow is:</p>
<p>1. User initiates a transaction with their tokens</p>
<p>2. Contract locks the tokens and calculates fees</p>
<p>3. Transaction manager (off-chain service) processes the fiat conversion</p>
<p>4. Contract releases tokens to the appropriate parties</p>
<h2 id="heading-the-language-showdown-solidity-vs-clarity"><strong>The Language Showdown: Solidity vs Clarity</strong></h2>
<h3 id="heading-solidity-ethereum"><strong>Solidity: Ethereum</strong></h3>
<p>Solidity is like JavaScript's rebellious cousin who decided to handle money. It's object-oriented, has familiar syntax, and lets you do some pretty wild stuff (sometimes too wild, which is why we have so many hacks).</p>
<h3 id="heading-clarity-stacks"><strong>Clarity: Stacks</strong></h3>
<p>Clarity is like that friend who's super careful about everything. It's designed to be predictable, readable, and safe. No hidden surprises, no reentrancy attacks, no overflow issues. It's like having a smart contract language with training wheels, but in a good way.</p>
<h2 id="heading-lets-start-with-the-basics-contract-structure"><strong>Let's Start with the Basics: Contract Structure</strong></h2>
<h3 id="heading-solidity-contract-declaration"><strong>Solidity Contract Declaration</strong></h3>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: MIT // This is just a license comment. Clarity doesn't need this because... </span>
                                <span class="hljs-comment">// well, it's just different.</span>

<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.28;</span> <span class="hljs-comment">// Tells the compiler which version to use.</span>

<span class="hljs-comment">// Solidity uses imports like most programming languages. </span>
<span class="hljs-comment">// Clarity uses traits and contract references instead.</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/IERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/utils/ReentrancyGuard.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./DirectSettings.sol"</span>;

<span class="hljs-comment">// This is inheritance. The contract extends two other contracts. </span>
<span class="hljs-comment">// Clarity doesn't have inheritance, so we'll handle this differently.</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">FiatBridge</span> <span class="hljs-keyword">is</span> <span class="hljs-title">DirectSettings</span>, <span class="hljs-title">ReentrancyGuard</span></span>
</code></pre>
<h3 id="heading-clarity-contract-declaration"><strong>Clarity Contract Declaration</strong></h3>
<p><strong>What's happening here:</strong></p>
<p>- <code>;; title: defi-direct</code> - This is just a comment. Clarity uses <code>;;</code> for comments instead of <code>//</code>.</p>
<p>- <code>define-trait sip010-trait</code> - This is defining an interface (trait in Clarity syntax). It's like defining what functions a token contract must have.</p>
<p>- The trait defines the SIP-010 standard, which is Stacks' equivalent of ERC-20. <em>Notice how each function has explicit input and output types.</em></p>
<h2 id="heading-data-structures-the-heart-of-our-contract"><strong>Data Structures: The Heart of Our Contract</strong></h2>
<pre><code class="lang-clojure"><span class="hljs-comment">;; title: defi-direct</span>

<span class="hljs-comment">;; SIP-010 Token Trait Definition</span>

(<span class="hljs-name">define-trait</span> sip010-trait
  (
    <span class="hljs-comment">;; Transfer function</span>
    (<span class="hljs-name">transfer</span> (<span class="hljs-name">uint</span> principal principal (<span class="hljs-name">optional</span> principal))
      (<span class="hljs-name">response</span> bool uint))

    <span class="hljs-comment">;; Get balance of a principal</span>
    (<span class="hljs-name">get-balance</span> (<span class="hljs-name">principal</span>)
      (<span class="hljs-name">response</span> uint uint))

    <span class="hljs-comment">;; Get number of decimals</span>
    (<span class="hljs-name">get-decimals</span> ()
      (<span class="hljs-name">response</span> uint uint))

    <span class="hljs-comment">;; Get token symbol</span>
    (<span class="hljs-name">get-symbol</span> ()
      (<span class="hljs-name">response</span> (<span class="hljs-name">string-ascii</span> <span class="hljs-number">32</span>) uint))

    <span class="hljs-comment">;; Get token name</span>
    (<span class="hljs-name">get-name</span> ()
      (<span class="hljs-name">response</span> (<span class="hljs-name">string-ascii</span> <span class="hljs-number">32</span>) uint))
  )
)
</code></pre>
<h3 id="heading-solidity-transaction-struct"><strong>Solidity Transaction Struct</strong></h3>
<pre><code class="lang-solidity"><span class="hljs-comment">// This defines a custom data type. Think of it like a blueprint for transaction data.</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title">Transaction</span> {
    <span class="hljs-keyword">address</span> user; <span class="hljs-comment">// Stores the user's Ethereum address. In Clarity, this becomes principal.</span>
    <span class="hljs-keyword">address</span> token; <span class="hljs-comment">// The token contract address.</span>

    <span class="hljs-keyword">uint256</span> amount; <span class="hljs-comment">// The amount of tokens. uint in Clarity.</span>
    <span class="hljs-keyword">uint256</span> amountSpent; <span class="hljs-comment">// How much was actually spent. uint in Clarity.</span>
    <span class="hljs-keyword">uint256</span> transactionFee; <span class="hljs-comment">// The fee amount. uint in Clarity.</span>
    <span class="hljs-keyword">uint256</span> transactionTimestamp; <span class="hljs-comment">// When the transaction happened. uint in Clarity.</span>

    <span class="hljs-keyword">uint256</span> fiatBankAccountNumber; <span class="hljs-comment">// Bank account number. uint in Clarity.</span>
    <span class="hljs-keyword">string</span> fiatBank; <span class="hljs-comment">// Bank name. (string-ascii 32) in Clarity (limited to 32 characters).</span>
    <span class="hljs-keyword">string</span> recipientName; <span class="hljs-comment">// Recipient's name. (string-ascii 32) in Clarity.</span>
    <span class="hljs-keyword">uint256</span> fiatAmount; <span class="hljs-comment">// Fiat amount. uint in Clarity.</span>

    <span class="hljs-keyword">bool</span> isCompleted; <span class="hljs-comment">// Whether transaction is complete. Also bool in Clarity.</span>
    <span class="hljs-keyword">bool</span> isRefunded; <span class="hljs-comment">// Whether transaction was refunded. Also bool in Clarity.</span>
}
</code></pre>
<p>The <code>Transaction</code> struct is a schema for transaction data, including the user's Ethereum address, token contract address, token amount, amount spent, transaction fee, timestamp, bank account number, bank name, recipient's name, fiat amount, and statuses for completion and refund. In Clarity, addresses are <code>principal</code>, strings are limited to 32 characters, and numeric values are <code>uint</code>.</p>
<h3 id="heading-clarity-transaction-structure"><strong>Clarity Transaction Structure</strong></h3>
<pre><code class="lang-clojure"><span class="hljs-comment">;; Instead of a struct, we define a map directly. </span>
<span class="hljs-comment">;; Maps in Clarity are like dictionaries or hash tables.</span>
(<span class="hljs-name">define-map</span> transactions
  <span class="hljs-comment">;; Key: 32-byte buffer (e.g., transaction ID or hash), (like bytes32 in Solidity).</span>
  ((<span class="hljs-name">buff</span> <span class="hljs-number">32</span>))

  <span class="hljs-comment">;; Value: transaction data - a tuple (similar to a struct) with named fields.</span>
  {
    user: principal,
    token: principal,

    amount: uint,
    amount-spent: uint,
    transaction-fee: uint,
    transaction-timestamp: uint,

    fiat-bank-account-number: uint,
    fiat-bank: (<span class="hljs-name">string-ascii</span> <span class="hljs-number">32</span>),
    recipient-name: (<span class="hljs-name">string-ascii</span> <span class="hljs-number">32</span>),
    fiat-amount: uint,

    is-completed: bool,
    is-refunded: bool
  }
)
</code></pre>
<p>The <code>transactions</code> map in Clarity serves the same purpose as a <code>struct</code> in Solidity….defining a schema to store and retrieve transaction data. Each entry is keyed by a 32-byte buffer (e.g., a hash or unique transaction ID), and the value is a tuple of typed fields. In Clarity, strings are defined with a maximum length <code>(string-ascii 32)</code> and numeric values use the <code>uint</code> type. Overall, while Solidity uses structs inside contracts, Clarity maps are defined at the top level and behave like typed dictionaries, enabling structured and predictable on-chain storage.</p>
<h2 id="heading-storage-where-we-keep-our-data"><strong>Storage: Where We Keep Our Data</strong></h2>
<h3 id="heading-solidity-storage"><strong>Solidity Storage</strong></h3>
<pre><code class="lang-solidity"><span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">bytes32</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> Transaction) <span class="hljs-keyword">public</span> transactions; <span class="hljs-comment">// Maps transaction IDs to transaction data.</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">bytes32</span>[]) <span class="hljs-keyword">public</span> userTransactionIds; <span class="hljs-comment">// Maps user addresses to arrays of their transaction IDs.</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> collectedFees; <span class="hljs-comment">// Maps token addresses to collected fees.</span>
</code></pre>
<p>In Solidity, storage is often structured using <code>mapping</code> types, which act like hash tables for key-value storage on-chain. The <code>transactions</code> mapping associates each <code>bytes32</code> transaction ID with a full <code>Transaction</code> struct, essentially storing detailed transaction records by their unique identifiers. The <code>userTransactionIds</code> mapping links each user's Ethereum address to an array of their transaction IDs, making it easy to query all transactions belonging to a specific user. Lastly, the <code>collectedFees</code> mapping keeps track of fees collected per token address.</p>
<p>Each mapping is marked as <code>public</code>, which automatically creates a getter function, allowing external contracts or frontend apps to access the stored values.</p>
<h3 id="heading-clarity-storage"><strong>Clarity Storage</strong></h3>
<pre><code class="lang-clojure"><span class="hljs-comment">;; Maps are defined with define-map instead of mapping.</span>

<span class="hljs-comment">;; Define a map for storing detailed transaction data</span>
(<span class="hljs-name">define-map</span> transactions
  <span class="hljs-comment">;; Key: 32-byte buffer (e.g., transaction ID)</span>
  ((<span class="hljs-name">buff</span> <span class="hljs-number">32</span>))
  <span class="hljs-comment">;; Value: transaction details</span>
  {
    user: principal,
    token: principal,

    amount: uint,
    amount-spent: uint,
    transaction-fee: uint,
    transaction-timestamp: uint,

    fiat-bank-account-number: uint,
    fiat-bank: (<span class="hljs-name">string-ascii</span> <span class="hljs-number">32</span>),
    recipient-name: (<span class="hljs-name">string-ascii</span> <span class="hljs-number">32</span>),
    fiat-amount: uint,

    is-completed: bool,
    is-refunded: bool
  }
)

<span class="hljs-comment">;; Define a map for storing a list of transaction IDs per user</span>
(<span class="hljs-name">define-map</span> user-transaction-ids
  principal
  (<span class="hljs-name"><span class="hljs-builtin-name">list</span></span> <span class="hljs-number">1000</span> (<span class="hljs-name">buff</span> <span class="hljs-number">32</span>))
)

<span class="hljs-comment">;; Define a map for storing total fees collected per user</span>
(<span class="hljs-name">define-map</span> collected-fees
  principal
  uint
)
</code></pre>
<p>In Clarity, the equivalent storage structure is implemented using <code>define-map</code>. Each map is defined with strict typing for both the key and value. The <code>transactions</code> map stores detailed transaction data keyed by a <code>(buff 32)</code> buffer, similar to a <code>bytes32</code> hash in Solidity. Instead of a <code>struct</code>, Clarity uses tuples with named fields to represent structured data.</p>
<p>The <code>user-transaction-ids</code> map stores a list of up to 1000 transaction IDs per user, using a <code>principal</code> as the key and a bounded list as the value.</p>
<p>The <code>collected-fees</code> map tracks the total amount of fees collected per user (or principal), using a <code>uint</code> value as the accumulator.</p>
<p>Unlike Solidity, Clarity does not have a <code>public</code> keyword, so access to maps must be done explicitly via read functions.</p>
<h2 id="heading-variables-the-simple-stuff"><strong>Variables: The Simple Stuff</strong></h2>
<h3 id="heading-solidity-variables"><strong>Solidity Variables</strong></h3>
<pre><code class="lang-solidity"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> spreadFeePercentage;
<span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> owner;
<span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> transactionManager;
<span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> feeReceiver;
<span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> vaultAddress;
<span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> paused;
</code></pre>
<p>In Solidity, variables like <code>spreadFeePercentage</code>, <code>owner</code>, <code>transactionManager</code>, <code>feeReceiver</code>, <code>vaultAddress</code>, and <code>paused</code> are declared as state variables and often marked <code>public</code>, which automatically creates getter functions for external access. Solidity uses types like <code>address</code>, <code>uint256</code>, and <code>bool</code> without requiring explicit initialization in many cases.</p>
<h3 id="heading-clarity-variables"><strong>Clarity Variables</strong></h3>
<pre><code class="lang-clojure">(<span class="hljs-name">define-data-var</span> owner principal tx-sender)
(<span class="hljs-name">define-data-var</span> paused bool <span class="hljs-literal">false</span>)
(<span class="hljs-name">define-data-var</span> spread-fee-percentage uint u0)
(<span class="hljs-name">define-data-var</span> transaction-manager (<span class="hljs-name">optional</span> principal) none)
(<span class="hljs-name">define-data-var</span> fee-receiver (<span class="hljs-name">optional</span> principal) none)
(<span class="hljs-name">define-data-var</span> vault-address (<span class="hljs-name">optional</span> principal) none)
</code></pre>
<p>In Clarity, similar functionality is achieved using <code>define-data-var</code>, where each variable is initialized with a specific type and value. For example, <code>spread-fee-percentage</code> is a <code>uint</code> initialized to <code>u0</code>, and <code>owner</code> is set to <code>tx-sender</code>, which represents the contract deployer. The variables at the top of the contract are set during deployment, meaning <code>tx-sender</code> is the deployer's address at that time, which is why the owner is set to it. Clarity introduces the concept of <code>(optional principal)</code> for variables like <code>transaction-manager</code>, <code>fee-receiver</code>, and <code>vault-address</code>. This means the variable may or may not hold a value (<code>none</code> by default), clearly indicating whether a principal is present or absent.</p>
<h2 id="heading-error-handling-making-things-safe"><strong>Error Handling: Making Things Safe</strong></h2>
<h3 id="heading-solidity-error-handling"><strong>Solidity Error Handling</strong></h3>
<p>In <strong>Solidity</strong>, access control and validation are commonly handled using <code>modifiers</code> and <code>require</code> statements. For example, the <code>onlyTransactionManager</code> modifier ensures that only the designated manager can call certain functions:</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyTransactionManager</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> transactionManager, <span class="hljs-string">"Not transaction manager"</span>);
<span class="hljs-keyword">_</span>;
}
</code></pre>
<p><strong><em>Usage in functions:</em></strong></p>
<pre><code class="lang-solidity"><span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Amount must be greater than zero"</span>);

<span class="hljs-built_in">require</span>(supportedTokens[token], <span class="hljs-string">"Token not supported"</span>);
</code></pre>
<h3 id="heading-clarity-error-handling"><strong>Clarity Error Handling</strong></h3>
<p>In <strong>Clarity</strong>, similar functionality is achieved using error codes defined with <code>define-constant</code>. Instead of inline strings, errors are declared once and reused throughout the contract:</p>
<pre><code class="lang-clojure"><span class="hljs-comment">;; General Errors</span>
(<span class="hljs-name">define-constant</span> err-fee-too-high (<span class="hljs-name">err</span> u100))
(<span class="hljs-name">define-constant</span> err-not-owner (<span class="hljs-name">err</span> u101))
(<span class="hljs-name">define-constant</span> err-not-tx-manager (<span class="hljs-name">err</span> u102))
(<span class="hljs-name">define-constant</span> err-paused (<span class="hljs-name">err</span> u105))
(<span class="hljs-name">define-constant</span> err-not-paused (<span class="hljs-name">err</span> u106))
(<span class="hljs-name">define-constant</span> err-arithmetic-overflow (<span class="hljs-name">err</span> u118))

<span class="hljs-comment">;; Validation Errors</span>
(<span class="hljs-name">define-constant</span> err-token-not-supported (<span class="hljs-name">err</span> u103))
(<span class="hljs-name">define-constant</span> err-invalid-address (<span class="hljs-name">err</span> u104))
(<span class="hljs-name">define-constant</span> err-invalid-amount (<span class="hljs-name">err</span> u110))
(<span class="hljs-name">define-constant</span> err-invalid-bank-account (<span class="hljs-name">err</span> u111))
(<span class="hljs-name">define-constant</span> err-invalid-bank-name (<span class="hljs-name">err</span> u112))
(<span class="hljs-name">define-constant</span> err-invalid-recipient-name (<span class="hljs-name">err</span> u113))
(<span class="hljs-name">define-constant</span> err-invalid-fiat-amount (<span class="hljs-name">err</span> u117))

<span class="hljs-comment">;; Transaction Errors</span>
(<span class="hljs-name">define-constant</span> err-already-processed (<span class="hljs-name">err</span> u107))
(<span class="hljs-name">define-constant</span> err-amount-mismatch (<span class="hljs-name">err</span> u108))
(<span class="hljs-name">define-constant</span> err-insufficient-balance (<span class="hljs-name">err</span> u109))
(<span class="hljs-name">define-constant</span> err-transaction-not-found (<span class="hljs-name">err</span> u114))
(<span class="hljs-name">define-constant</span> err-transaction-data-error (<span class="hljs-name">err</span> u115))
(<span class="hljs-name">define-constant</span> err-list-too-long (<span class="hljs-name">err</span> u116))
</code></pre>
<p><strong>Why this approach?</strong></p>
<p>- Clarity uses numeric error codes.</p>
<p>- <code>(err u100)</code> creates an error with code 100. The <code>u</code> means unsigned integer.</p>
<p>- We define all errors upfront, making the contract more organized.</p>
<h2 id="heading-transaction-initiation"><strong>Transaction Initiation</strong></h2>
<h3 id="heading-solidity-initiatefiattransaction"><strong>Solidity initiateFiatTransaction</strong></h3>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initiateFiatTransaction</span>(<span class="hljs-params">
    <span class="hljs-keyword">address</span> token,
    <span class="hljs-keyword">uint256</span> amount,
    <span class="hljs-keyword">uint256</span> _fiatBankAccountNumber,
    <span class="hljs-keyword">uint256</span> _fiatAmount,
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _fiatBank,
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _recipientName
</span>)
    <span class="hljs-title"><span class="hljs-keyword">external</span></span>
    <span class="hljs-title">nonReentrant</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">bytes32</span> txId</span>)
</span>{
    <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Amount must be greater than zero"</span>);
    <span class="hljs-built_in">require</span>(supportedTokens[token], <span class="hljs-string">"Token not supported"</span>);
    <span class="hljs-built_in">require</span>(_fiatBankAccountNumber <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Invalid bank account number"</span>);
    <span class="hljs-built_in">require</span>(<span class="hljs-keyword">bytes</span>(_fiatBank).<span class="hljs-built_in">length</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Invalid bank name"</span>);
    <span class="hljs-built_in">require</span>(<span class="hljs-keyword">bytes</span>(_recipientName).<span class="hljs-built_in">length</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Invalid recipient name"</span>);

    <span class="hljs-keyword">uint256</span> feeAmount <span class="hljs-operator">=</span> (amount <span class="hljs-operator">*</span> spreadFeePercentage) <span class="hljs-operator">/</span> <span class="hljs-number">10000</span>;
    <span class="hljs-keyword">uint256</span> totalAmount <span class="hljs-operator">=</span> amount <span class="hljs-operator">+</span> feeAmount;

    IERC20 tokenContract <span class="hljs-operator">=</span> IERC20(token);

    <span class="hljs-built_in">require</span>(tokenContract.balanceOf(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>) <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> totalAmount, <span class="hljs-string">"Insufficient Balance"</span>);
    <span class="hljs-built_in">require</span>(
        tokenContract.transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), totalAmount),
        <span class="hljs-string">"Transfer failed"</span>
    );

    collectedFees[token] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> feeAmount;

    txId <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>,
        token,
        amount,
        <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>
    ));

    transactions[txId] <span class="hljs-operator">=</span> Transaction({
        user: <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>,
        token: token,
        amount: amount,
        amountSpent: <span class="hljs-number">0</span>,
        transactionFee: feeAmount,
        transactionTimestamp: <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>,
        fiatBankAccountNumber: _fiatBankAccountNumber,
        fiatBank: _fiatBank,
        recipientName: _recipientName,
        fiatAmount: _fiatAmount,
        isCompleted: <span class="hljs-literal">false</span>,
        isRefunded: <span class="hljs-literal">false</span>
    });

    userTransactionIds[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].<span class="hljs-built_in">push</span>(txId);

    <span class="hljs-keyword">emit</span> TransactionInitiated(txId, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);

    <span class="hljs-keyword">return</span> txId;
}
</code></pre>
<p><strong>Let's break this down line by line:</strong></p>
<p>1. <strong>Function signature:</strong></p>
<p>- Solidity: <code>function initiateFiatTransaction(...) external nonReentrant whenNotPaused returns (bytes32 txId)</code></p>
<p>- Clarity: <code>(define-public (initiate-fiat-transaction ...))</code> - No need for <code>external</code> or modifiers, Clarity handles this differently.</p>
<p>2. <strong>Validation checks:</strong></p>
<p>- Solidity: <code>require(amount &gt; 0, "Amount must be greater than zero");</code></p>
<p>- Clarity: <code>(asserts! (&gt; amount u0) err-invalid-amount)</code> - <code>asserts!</code> is like <code>require</code>, but it takes an error code instead of a string.</p>
<p>3. <strong>Fee calculation:</strong></p>
<p>- Solidity: <code>uint256 feeAmount = (amount * spreadFeePercentage) / 10000;</code></p>
<p>- Clarity: <code>(fee-amt (/ (* amount fee) u10000))</code> - Notice the prefix notation <code>(* amount fee)</code> instead of <code>amount * fee</code>.</p>
<p>4. <strong>Token transfer:</strong></p>
<p>- Solidity: <code>require(tokenContract.transferFrom(msg.sender, address(this), totalAmount), "Transfer failed");</code></p>
<p>- Clarity: <code>(try! (contract-call? token-contract transfer total-amt tx-sender (as-contract tx-sender) none))</code> - <code>try!</code> is like <code>require</code>, and <code>contract-call?</code> is how you call other contracts.</p>
<p>5. <strong>Transaction ID generation:</strong></p>
<p>- Solidity: <code>txId = keccak256(abi.encodePacked(msg.sender, token, amount, block.timestamp));</code></p>
<p>- Clarity: <code>(tx-id (sha256 (concat (unwrap! (to-consensus-buff? amount) err-transaction-data-error) (unwrap! (to-consensus-buff? now) err-transaction-data-error))))</code> - Much more verbose, but very explicit about what's happening.</p>
<h3 id="heading-clarity-initiate-fiat-transaction"><strong>Clarity initiate-fiat-transaction</strong></h3>
<pre><code class="lang-clojure">(<span class="hljs-name">define-public</span> (<span class="hljs-name">initiate-fiat-transaction</span>
  (<span class="hljs-name">token-contract</span> &lt;sip010-trait&gt;)
  (<span class="hljs-name">amount</span> uint)
  (<span class="hljs-name">fiat-bank-account-number</span> uint)
  (<span class="hljs-name">fiat-amount</span> uint)
  (<span class="hljs-name">fiat-bank</span> (<span class="hljs-name">string-ascii</span> <span class="hljs-number">32</span>))
  (<span class="hljs-name">recipient-name</span> (<span class="hljs-name">string-ascii</span> <span class="hljs-number">32</span>))
)
  (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> (
    (<span class="hljs-name">token</span> (<span class="hljs-name">contract-of</span> token-contract))
    (<span class="hljs-name">is-supported</span> (<span class="hljs-name">default-to</span> <span class="hljs-literal">false</span> (<span class="hljs-name">map-get?</span> supported-tokens token)))
    (<span class="hljs-name">fee</span> (<span class="hljs-name">var-get</span> spread-fee-percentage))
    (<span class="hljs-name">fee-amt</span> (/ (<span class="hljs-name"><span class="hljs-builtin-name">*</span></span> amount fee) u10000))
    (<span class="hljs-name">total-amt</span> (<span class="hljs-name"><span class="hljs-builtin-name">+</span></span> amount fee-amt))
    (<span class="hljs-name">now</span> stacks-block-height)
    (<span class="hljs-name">tx-id</span> (<span class="hljs-name">sha256</span>
             (<span class="hljs-name"><span class="hljs-builtin-name">concat</span></span>
               (<span class="hljs-name">unwrap!</span> (<span class="hljs-name">to-consensus-buff?</span> amount) err-transaction-data-error)
               (<span class="hljs-name">unwrap!</span> (<span class="hljs-name">to-consensus-buff?</span> now) err-transaction-data-error)
             )
           ))
  )
    (<span class="hljs-name">begin</span>
      <span class="hljs-comment">;; Validation</span>
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">not</span></span> (<span class="hljs-name">var-get</span> paused)) err-paused)
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">&gt;</span></span> amount u0) err-invalid-amount)
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">&gt;</span></span> fiat-bank-account-number u0) err-invalid-bank-account)
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">&gt;</span></span> fiat-amount u0) err-invalid-fiat-amount)
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">&gt;</span></span> (<span class="hljs-name">len</span> fiat-bank) u0) err-invalid-bank-name)
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">&gt;</span></span> (<span class="hljs-name">len</span> recipient-name) u0) err-invalid-recipient-name)
      (<span class="hljs-name">asserts!</span> is-supported err-token-not-supported)

      <span class="hljs-comment">;; Checking for overflow</span>
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">&gt;</span></span> total-amt amount) err-arithmetic-overflow)

      <span class="hljs-comment">;; Token transfer</span>
      (<span class="hljs-name">try!</span> (<span class="hljs-name">contract-call?</span> token-contract transfer total-amt tx-sender (<span class="hljs-name">as-contract</span> tx-sender) none))

      <span class="hljs-comment">;; Recording the transaction</span>
      (<span class="hljs-name">map-set</span> transactions tx-id {
        user: tx-sender,
        token: token,
        amount: amount,
        amount-spent: u0,
        transaction-fee: fee-amt,
        transaction-timestamp: now,
        fiat-bank-account-number: fiat-bank-account-number,
        fiat-bank: fiat-bank,
        recipient-name: recipient-name,
        fiat-amount: fiat-amount,
        is-completed: <span class="hljs-literal">false</span>,
        is-refunded: <span class="hljs-literal">false</span>
      })

      <span class="hljs-comment">;; Update user transaction list</span>
      (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> ((<span class="hljs-name">existing-ids</span> (<span class="hljs-name">default-to</span> (<span class="hljs-name"><span class="hljs-builtin-name">list</span></span>) (<span class="hljs-name">map-get?</span> user-transaction-ids tx-sender))))
        (<span class="hljs-name">map-set</span> user-transaction-ids tx-sender
          (<span class="hljs-name">unwrap!</span> (<span class="hljs-name">as-max-len?</span> (<span class="hljs-name">append</span> existing-ids tx-id) u100) (<span class="hljs-name">err</span> u116))
        )
      )

      (<span class="hljs-name">ok</span> tx-id)
    )
  )
)
</code></pre>
<p><strong>Key Clarity differences:</strong></p>
<p>- <code>(let (...))</code> - This is how you declare local variables in Clarity. Everything is immutable!</p>
<p>- <code>(begin ...)</code> - Groups multiple expressions together.</p>
<p>- <code>(map-set ...)</code> - Sets a value in a map.</p>
<p>- <code>(unwrap! ...)</code> - Unwraps an optional value, throwing an error if it's <code>none</code>.</p>
<p>- <code>(as-max-len? ... u1000)</code> - Ensures the list doesn't exceed 100 items. Like I said earlier <em>Clarity</em> is very strict about list sizes!</p>
<h2 id="heading-transaction-completion"><strong>Transaction Completion</strong></h2>
<h3 id="heading-solidity-completetransaction"><strong>Solidity completeTransaction</strong></h3>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">completeTransaction</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> txId, <span class="hljs-keyword">uint256</span> amountSpent</span>)
    <span class="hljs-title"><span class="hljs-keyword">external</span></span>
    <span class="hljs-title">onlyTransactionManager</span>
    <span class="hljs-title">nonReentrant</span>
</span>{
    Transaction <span class="hljs-keyword">storage</span> txn <span class="hljs-operator">=</span> transactions[txId];

    <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>txn.isCompleted <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> <span class="hljs-operator">!</span>txn.isRefunded, <span class="hljs-string">"Transaction already processed"</span>);
    <span class="hljs-built_in">require</span>(amountSpent <span class="hljs-operator">=</span><span class="hljs-operator">=</span> txn.amount, <span class="hljs-string">"Amount spent not equal locked amount"</span>);

    txn.amountSpent <span class="hljs-operator">=</span> amountSpent;
    txn.isCompleted <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;

    <span class="hljs-built_in">require</span>(IERC20(txn.token).<span class="hljs-built_in">transfer</span>(feeReceiver, txn.transactionFee), <span class="hljs-string">"Fee transfer failed"</span>);
    <span class="hljs-built_in">require</span>(IERC20(txn.token).<span class="hljs-built_in">transfer</span>(vaultAddress, amountSpent), <span class="hljs-string">"Transfer failed"</span>);

    <span class="hljs-keyword">emit</span> TransactionCompleted(txId, amountSpent);
}
</code></pre>
<p><strong>Breaking it down:</strong></p>
<p>- <code>Transaction storage txn = transactions[txId];</code> - Gets a reference to the transaction in storage.</p>
<p>- <code>require(!txn.isCompleted &amp;&amp; !txn.isRefunded, "Transaction already processed");</code> - Checks transaction state.</p>
<p>- <code>txn.amountSpent = amountSpent;</code> - Updates the transaction (mutable).</p>
<p>- <code>require(IERC20(txn.token).transfer(...), "Transfer failed");</code> - Transfers tokens.</p>
<h3 id="heading-clarity-complete-transaction"><strong>Clarity complete-transaction</strong></h3>
<pre><code class="lang-clojure">(<span class="hljs-name">define-public</span> (<span class="hljs-name">complete-transaction</span>
  (<span class="hljs-name">token</span> &lt;sip010-trait&gt;) <span class="hljs-comment">;; Require trait reference</span>
  (<span class="hljs-name">tx-id</span> (<span class="hljs-name">buff</span> <span class="hljs-number">32</span>))
  (<span class="hljs-name">amount-spent</span> uint)
)
  (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> (
    (<span class="hljs-name">tx</span> (<span class="hljs-name">map-get?</span> transactions tx-id))
    (<span class="hljs-name">tx-manager</span> (<span class="hljs-name">var-get</span> transaction-manager))
  )
    (<span class="hljs-name">begin</span>
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name">is-some</span> tx-manager) err-not-tx-manager)
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name">is-eq</span> tx-sender (<span class="hljs-name">unwrap!</span> tx-manager err-not-tx-manager)) err-not-tx-manager)
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name">is-some</span> tx) err-transaction-not-found)

      (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> ((<span class="hljs-name">txn</span> (<span class="hljs-name">unwrap!</span> tx err-transaction-data-error)))
        (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">not</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> is-completed txn)) err-already-processed)
        (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">not</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> is-refunded txn)) err-already-processed)
        (<span class="hljs-name">asserts!</span> (<span class="hljs-name">is-eq</span> amount-spent (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> amount txn)) err-amount-mismatch)

        <span class="hljs-comment">;; Verify the token matches stored principal</span>
        (<span class="hljs-name">asserts!</span> (<span class="hljs-name">is-eq</span> (<span class="hljs-name">contract-of</span> token) (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> token txn)) err-token-not-supported)

        <span class="hljs-comment">;; Transfer fee to fee-receiver</span>
        (<span class="hljs-name">try!</span>
          (<span class="hljs-name">contract-call?</span> token transfer
            (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> transaction-fee txn)
            (<span class="hljs-name">as-contract</span> tx-sender)
            (<span class="hljs-name">unwrap!</span> (<span class="hljs-name">var-get</span> fee-receiver) err-invalid-address)
            none
          )
        )

        <span class="hljs-comment">;; Mark transaction as completed</span>
        (<span class="hljs-name">map-set</span> transactions tx-id
          (<span class="hljs-name"><span class="hljs-builtin-name">merge</span></span> txn {
            amount-spent: amount-spent,
            is-completed: <span class="hljs-literal">true</span>
          })
        )

        (<span class="hljs-name">ok</span> <span class="hljs-literal">true</span>)
      )
    )
  )
)
</code></pre>
<p><strong>Key differences:</strong></p>
<p>- <code>(map-get? transactions tx-id)</code> - Returns an optional value. If the transaction doesn't exist, it returns <code>none</code>.</p>
<p>- <code>(is-some tx-manager)</code> - Checks if the optional value is not <code>none</code>.</p>
<p>- <code>(get is-completed txn)</code> - Gets a field from the tuple. Like <code>txn.isCompleted</code> in Solidity.</p>
<p>- <code>(merge txn {...})</code> - Creates a new tuple by merging the old one with new values. Since Clarity is immutable, we can't modify the existing tuple.</p>
<h2 id="heading-refund-function-when-things-go-wrong"><strong>Refund Function: When Things Go Wrong</strong></h2>
<h3 id="heading-solidity-refund"><strong>Solidity refund</strong></h3>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">refund</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> txId</span>)
    <span class="hljs-title"><span class="hljs-keyword">external</span></span>
    <span class="hljs-title">onlyOwner</span>
    <span class="hljs-title">nonReentrant</span>
</span>{
    Transaction <span class="hljs-keyword">storage</span> txn <span class="hljs-operator">=</span> transactions[txId];

    <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>txn.isCompleted <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> <span class="hljs-operator">!</span>txn.isRefunded, <span class="hljs-string">"Transaction already processed"</span>);

    txn.isRefunded <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;

    <span class="hljs-built_in">require</span>(
        IERC20(txn.token).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)) <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> txn.amount <span class="hljs-operator">+</span> txn.transactionFee,
        <span class="hljs-string">"Insufficient balance"</span>
    );

    <span class="hljs-built_in">require</span>(
        IERC20(txn.token).<span class="hljs-built_in">transfer</span>(txn.user, txn.amount <span class="hljs-operator">+</span> txn.transactionFee),
        <span class="hljs-string">"Transfer failed"</span>
    );

    <span class="hljs-keyword">emit</span> TransactionRefunded(txId, txn.amount);
}
</code></pre>
<h3 id="heading-clarity-refund"><strong>Clarity refund</strong></h3>
<pre><code class="lang-clojure">(<span class="hljs-name">define-public</span> (<span class="hljs-name">refund</span>
  (<span class="hljs-name">tx-id</span> (<span class="hljs-name">buff</span> <span class="hljs-number">32</span>))
  (<span class="hljs-name">token-contract</span> &lt;sip010-trait&gt;)
)
  (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> ((<span class="hljs-name">tx</span> (<span class="hljs-name">map-get?</span> transactions tx-id)))
    (<span class="hljs-name">begin</span>
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name">is-eq</span> tx-sender (<span class="hljs-name">var-get</span> owner)) err-not-owner)
      (<span class="hljs-name">asserts!</span> (<span class="hljs-name">is-some</span> tx) err-transaction-not-found)

      (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> ((<span class="hljs-name">txn</span> (<span class="hljs-name">unwrap!</span> tx err-transaction-data-error)))
        (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">not</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> is-completed txn)) err-already-processed)
        (<span class="hljs-name">asserts!</span> (<span class="hljs-name"><span class="hljs-builtin-name">not</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> is-refunded txn)) err-already-processed)

        <span class="hljs-comment">;; Refund amount to user</span>
        (<span class="hljs-name">try!</span>
          (<span class="hljs-name">contract-call?</span> token-contract transfer
            (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> amount txn)
            (<span class="hljs-name">as-contract</span> (<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> user txn))
            (<span class="hljs-name">unwrap!</span> (<span class="hljs-name">var-get</span> vault-address) err-invalid-address)
            none
          )
        )

        <span class="hljs-comment">;; Mark as refunded</span>
        (<span class="hljs-name">map-set</span> transactions tx-id
          (<span class="hljs-name"><span class="hljs-builtin-name">merge</span></span> txn { is-refunded: <span class="hljs-literal">true</span> })
        )

        (<span class="hljs-name">ok</span> <span class="hljs-literal">true</span>)
      )
    )
  )
)
</code></pre>
<p><strong>VERY INTERESTING NOTE:</strong> The Clarity version requires the token contract as a parameter, while Solidity can look it up from storage. This is because Clarity is more explicit about dependencies. Had a hard time figuring it out.</p>
<h2 id="heading-read-only-functions-get-functions"><strong>Read-Only Functions: Get Functions</strong></h2>
<h3 id="heading-solidity-gettransactionsbyaddress"><strong>Solidity getTransactionsByAddress</strong></h3>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTransactionsByAddress</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">Transaction[] <span class="hljs-keyword">memory</span></span>)
</span>{
    <span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">memory</span> txIds <span class="hljs-operator">=</span> userTransactionIds[user];
    Transaction[] <span class="hljs-keyword">memory</span> userTransactions <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Transaction[](txIds.<span class="hljs-built_in">length</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> txIds.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
        userTransactions[i] <span class="hljs-operator">=</span> transactions[txIds[i]];
    }

    <span class="hljs-keyword">return</span> userTransactions;
}
</code></pre>
<h3 id="heading-clarity-get-transaction-ids"><strong>Clarity get-transaction-ids</strong></h3>
<pre><code class="lang-clojure">(<span class="hljs-name">define-read-only</span> (<span class="hljs-name">get-transaction-ids</span> (<span class="hljs-name">user</span> principal))

(<span class="hljs-name">default-to</span> (<span class="hljs-name"><span class="hljs-builtin-name">list</span></span>) (<span class="hljs-name">map-get?</span> user-transaction-ids user))

)
</code></pre>
<p><strong>Big difference:</strong> Clarity doesn't have loops in the same way Solidity does. Instead of iterating through arrays, you'd typically use list operations or handle this differently. The Clarity version just returns the list of transaction IDs, and you'd need to fetch each transaction individually.</p>
<h2 id="heading-testing-making-sure-everything-works"><strong>Testing: Making Sure Everything Works</strong></h2>
<p>Now let's talk about testing! This is where things get really interesting because testing in Clarity is quite different from Solidity. I spent almost seven days writing Clarity tests to get used to it. I must say, the learning curve with Clarity is not too steep because once you get the hang of it, it sticks.</p>
<h3 id="heading-solidity-testing-hardhat"><strong>Solidity Testing (Hardhat)</strong></h3>
<pre><code class="lang-typescript">describe(<span class="hljs-string">"FiatBridge"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> fiatBridge;
  <span class="hljs-keyword">let</span> mockToken;
  <span class="hljs-keyword">let</span> owner;
  <span class="hljs-keyword">let</span> user;
  <span class="hljs-keyword">let</span> txManager;

  beforeEach(<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    [owner, user, txManager] = <span class="hljs-keyword">await</span> ethers.getSigners();

    <span class="hljs-keyword">const</span> MockToken = <span class="hljs-keyword">await</span> ethers.getContractFactory(<span class="hljs-string">"MockERC20"</span>);
    mockToken = <span class="hljs-keyword">await</span> MockToken.deploy();

    <span class="hljs-keyword">const</span> FiatBridge = <span class="hljs-keyword">await</span> ethers.getContractFactory(<span class="hljs-string">"FiatBridge"</span>);
    fiatBridge = <span class="hljs-keyword">await</span> FiatBridge.deploy(
      <span class="hljs-number">100</span>,                 <span class="hljs-comment">// spreadFeePercentage</span>
      txManager.address,   <span class="hljs-comment">// transactionManager</span>
      owner.address,       <span class="hljs-comment">// feeReceiver</span>
      owner.address        <span class="hljs-comment">// vaultAddress</span>
    );

    <span class="hljs-keyword">await</span> mockToken.mint(user.address, ethers.utils.parseEther(<span class="hljs-string">"1000"</span>));
    <span class="hljs-keyword">await</span> fiatBridge.addSupportedToken(mockToken.address);
  });

  it(<span class="hljs-string">"should initiate transaction"</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">await</span> mockToken
      .connect(user)
      .approve(fiatBridge.address, ethers.utils.parseEther(<span class="hljs-string">"100"</span>));

    <span class="hljs-keyword">const</span> tx = <span class="hljs-keyword">await</span> fiatBridge.connect(user).initiateFiatTransaction(
      mockToken.address,
      ethers.utils.parseEther(<span class="hljs-string">"100"</span>),
      <span class="hljs-number">12345678</span>,
      <span class="hljs-number">50000</span>,
      <span class="hljs-string">"Test Bank"</span>,
      <span class="hljs-string">"John Doe"</span>
    );

    expect(tx).to.emit(fiatBridge, <span class="hljs-string">"TransactionInitiated"</span>);
  });
});
</code></pre>
<h3 id="heading-clarity-testing-vitest"><strong>Clarity Testing (Vitest)</strong></h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { describe, expect, it, beforeEach } <span class="hljs-keyword">from</span> <span class="hljs-string">"vitest"</span>;
<span class="hljs-keyword">import</span> { Cl } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stacks/transactions"</span>;

<span class="hljs-keyword">const</span> accounts = simnet.getAccounts();

<span class="hljs-keyword">const</span> owner = accounts.get(<span class="hljs-string">"deployer"</span>)!;
<span class="hljs-keyword">const</span> tx_manager = accounts.get(<span class="hljs-string">"wallet_1"</span>)!;
<span class="hljs-keyword">const</span> fee_recv = accounts.get(<span class="hljs-string">"wallet_2"</span>)!;
<span class="hljs-keyword">const</span> vault = accounts.get(<span class="hljs-string">"wallet_3"</span>)!;
<span class="hljs-keyword">const</span> user = accounts.get(<span class="hljs-string">"wallet_4"</span>)!;

describe(<span class="hljs-string">"defi-direct: unit tests"</span>, <span class="hljs-function">() =&gt;</span> {
  beforeEach(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fee = Cl.uint(<span class="hljs-number">100</span>);

    simnet.callPublicFn(
      <span class="hljs-string">"defi-direct"</span>,
      <span class="hljs-string">"initializer"</span>,
      [
        fee,
        Cl.principal(tx_manager),
        Cl.principal(fee_recv),
        Cl.principal(vault),
      ],
      owner
    );
  });

  it(<span class="hljs-string">"should initiate fiat transaction successfully"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> amount = Cl.uint(<span class="hljs-number">1000</span>);
    <span class="hljs-keyword">const</span> fiatBankAccount = Cl.uint(<span class="hljs-number">12345678</span>);
    <span class="hljs-keyword">const</span> fiatAmount = Cl.uint(<span class="hljs-number">500</span>);
    <span class="hljs-keyword">const</span> fiatBank = Cl.stringAscii(<span class="hljs-string">"TestBank"</span>);
    <span class="hljs-keyword">const</span> recipientName = Cl.stringAscii(<span class="hljs-string">"Alice"</span>);

    <span class="hljs-keyword">const</span> { result } = simnet.callPublicFn(
      <span class="hljs-string">"defi-direct"</span>,
      <span class="hljs-string">"initiate-fiat-transaction"</span>,
      [
        Cl.contractPrincipal(owner, mockTokenContract),
        amount,
        fiatBankAccount,
        fiatAmount,
        fiatBank,
        recipientName,
      ],
      user
    );

    <span class="hljs-keyword">if</span> (result.type === <span class="hljs-string">"ok"</span>) {
      expect(result.value).toBeDefined();

      <span class="hljs-keyword">const</span> txId = result.value;

      <span class="hljs-keyword">const</span> { result: txResult } = simnet.callReadOnlyFn(
        <span class="hljs-string">"defi-direct"</span>,
        <span class="hljs-string">"get-transaction"</span>,
        [txId],
        owner
      );

      expect(txResult.value).toBeDefined();
    }
  });
});
</code></pre>
<p><strong>Key testing differences:</strong></p>
<p>- <strong>Simnet vs Hardhat</strong>: Clarity uses a simulated network called "simnet" instead of Hardhat's local blockchain.</p>
<p>- <strong>Account management</strong>: Instead of <code>ethers.getSigners()</code>, you get accounts from <code>simnet.getAccounts()</code>.</p>
<p>- <strong>Function calls</strong>: <code>simnet.callPublicFn()</code> instead of <code>contract.function()</code>.</p>
<p>- <strong>Type handling</strong>: Clarity uses <code>Cl.uint()</code>, <code>Cl.principal()</code>, etc. to create typed values.</p>
<p>- <strong>Result handling</strong>: Clarity functions return <code>(ok value)</code> or <code>(err code)</code>, so you check <code>result.type</code>.</p>
<h2 id="heading-integration-testing"><strong>Integration Testing</strong></h2>
<h3 id="heading-clarity-integration-test"><strong>Clarity Integration Test</strong></h3>
<pre><code class="lang-typescript">describe(<span class="hljs-string">"defi-direct: integration tests"</span>, <span class="hljs-function">() =&gt;</span> {
  beforeEach(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Mint tokens to user</span>
    simnet.callPublicFn(
      <span class="hljs-string">"mock-sip010"</span>,
      <span class="hljs-string">"mint"</span>,
      [Cl.uint(<span class="hljs-number">100000</span>), Cl.principal(user)],
      owner
    );

    <span class="hljs-comment">// Initialize contract</span>
    <span class="hljs-keyword">const</span> fee = Cl.uint(<span class="hljs-number">250</span>);
    simnet.callPublicFn(
      <span class="hljs-string">"defi-direct"</span>,
      <span class="hljs-string">"initializer"</span>,
      [
        fee,
        Cl.principal(tx_manager),
        Cl.principal(fee_recv),
        Cl.principal(vault),
      ],
      owner
    );

    <span class="hljs-comment">// Add supported token</span>
    <span class="hljs-keyword">const</span> mockTokenPrincipal = <span class="hljs-string">`<span class="hljs-subst">${owner}</span>.<span class="hljs-subst">${mockTokenContract}</span>`</span>;
    simnet.callPublicFn(
      <span class="hljs-string">"defi-direct"</span>,
      <span class="hljs-string">"add-supported-token"</span>,
      [Cl.principal(mockTokenPrincipal)],
      owner
    );
  });

  it(<span class="hljs-string">"should reject transaction when paused"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Pause the contract</span>
    simnet.callPublicFn(<span class="hljs-string">"defi-direct"</span>, <span class="hljs-string">"pause"</span>, [], owner);

    <span class="hljs-keyword">const</span> { result } = simnet.callPublicFn(
      <span class="hljs-string">"defi-direct"</span>,
      <span class="hljs-string">"initiate-fiat-transaction"</span>,
      [
        Cl.contractPrincipal(owner, mockTokenContract),
        Cl.uint(<span class="hljs-number">1000</span>),
        Cl.uint(<span class="hljs-number">12345678</span>),
        Cl.uint(<span class="hljs-number">500</span>),
        Cl.stringAscii(<span class="hljs-string">"Test Bank"</span>),
        Cl.stringAscii(<span class="hljs-string">"John Doe"</span>),
      ],
      user
    );

    expect(result.type).toBe(<span class="hljs-string">"err"</span>);
    expect(result.value.value).toBe(<span class="hljs-number">105n</span>); <span class="hljs-comment">// err-paused</span>
  });
});
</code></pre>
<h2 id="heading-the-big-differences-what-makes-clarity-special"><strong>The Big Differences: What Makes Clarity Special</strong></h2>
<h3 id="heading-1-immutability"><strong>1. Immutability</strong></h3>
<p>In Solidity, you can modify variables directly:</p>
<p><code>txn.isCompleted = true;</code></p>
<p>In Clarity, you create new values:</p>
<p><code>(map-set transactions tx-id (merge txn { is-completed: true }))</code></p>
<h3 id="heading-2-explicit-error-handling"><strong>2. Explicit Error Handling</strong></h3>
<p>Solidity uses strings:</p>
<p><code>require(amount &gt; 0, "Amount must be greater than zero");</code></p>
<p>Clarity uses codes:</p>
<p><code>(asserts! (&gt; amount u0) err-invalid-amount</code></p>
<h3 id="heading-3-no-loops"><strong>3. No Loops</strong></h3>
<p>Solidity can iterate:</p>
<p><code>for (uint256 i = 0; i &lt; txIds.length; i++) {</code></p>
<p><code>userTransactions[i] = transactions[txIds[i]];</code></p>
<p><code>}</code></p>
<p>Clarity uses list operations:</p>
<p><code>(append existing-ids tx-id)</code></p>
<h3 id="heading-4-trait-based-interfaces"><strong>4. Trait-Based Interfaces</strong></h3>
<p>Solidity uses interfaces:</p>
<p><code>interface IERC20 {</code></p>
<p><code>function transfer(address to, uint256 amount) external returns (bool);</code></p>
<p><code>}</code></p>
<p>Clarity uses traits:</p>
<p><code>(define-trait sip010-trait (</code></p>
<p><code>(transfer (uint principal principal (optional principal)) (response bool uint))</code></p>
<p><code>))</code></p>
<h3 id="heading-5-optional-values"><strong>5. Optional Values</strong></h3>
<p>Solidity uses zero addresses:</p>
<p><code>address public feeReceiver; // might be address(0)</code></p>
<p>Clarity uses optionals:</p>
<p><code>(define-data-var fee-receiver (optional principal) none)</code></p>
<h2 id="heading-performance-and-gas-considerations"><strong>Performance and Gas Considerations</strong></h2>
<h2 id="heading-security-differences"><strong>Security Differences</strong></h2>
<h3 id="heading-solidity-security-concerns"><strong>Solidity Security Concerns</strong></h3>
<p>- Reentrancy attacks (need <code>ReentrancyGuard</code>)</p>
<p>- Integer overflow/underflow (need SafeMath or ^0.8.0)</p>
<p>- Access control (need <code>onlyOwner</code> modifiers)</p>
<h3 id="heading-clarity-security-features"><strong>Clarity Security Features</strong></h3>
<p>- No reentrancy possible (by design)</p>
<p>- No integer overflow/underflow</p>
<p>- Explicit access control with <code>asserts!</code></p>
<h2 id="heading-lessons-learned-the-good-the-bad-and-the-ugly"><strong>Lessons Learned: The Good, The Bad, and The Ugly</strong></h2>
<h3 id="heading-the-good"><strong>The Good</strong></h3>
<p>1. <strong>Clarity is safer by design</strong> - No reentrancy, no overflow, no hidden surprises</p>
<p>2. <strong>Better error handling</strong> - Numeric error codes are more efficient</p>
<p>3. <strong>Explicit dependencies</strong> - You can't accidentally call the wrong contract</p>
<h3 id="heading-the-bad"><strong>The Bad</strong></h3>
<p>1. <strong>More verbose</strong> - Everything needs to be explicit</p>
<p>2. <strong>No loops</strong> - Some operations are more complex</p>
<p>3. <strong>Learning curve</strong> - Lisp-like syntax is unfamiliar to most developers, making the learning curve quite steep.</p>
<h3 id="heading-the-ugly"><strong>The Ugly</strong></h3>
<p>1. <strong>Immutable everything</strong> - Can't modify existing data, only create new</p>
<p>2. <strong>List size limits</strong> - Everything has explicit size constraints</p>
<p>3. <strong>Verbose error handling</strong> - Need to define every possible error</p>
<p>4. <strong>No inheritance</strong> - Can't extend contracts like in Solidity</p>
<h3 id="heading-conclusion-was-it-worth-it"><strong>Conclusion: Was It Worth It?</strong></h3>
<p>Honestly? It depends on what you're building. For DeFi Direct, the security benefits of Clarity are huge. No reentrancy attacks, no overflow issues, no hidden surprises. But the development experience is definitely more challenging.</p>
<p>The biggest takeaway is that Clarity forces you to think differently about smart contracts. It's not just a different syntax - it's a different philosophy. Solidity says "here's a powerful language, be careful with it." Clarity says "here's a safe language, we'll prevent you from making mistakes."</p>
<p>For a DeFi protocol handling real money, that safety is worth the extra verbosity</p>
<p>The testing experience is also quite different. Clarity's simnet is more integrated with the development environment, but the testing patterns are less familiar to most developers.</p>
<p>In the end, both languages can create the same features. It just depends on whether you prefer the flexibility of Solidity or the safety and predictability of Clarity.</p>
<p>For DeFi Direct, I'm happy with the Clarity version. It's more secure, more predictable, and gives me confidence that the contract will behave exactly as expected.</p>
<p><strong><em>And that's my journey from Solidity to Clarity! I hope you enjoyed this deep dive into the differences between these two smart contract languages. If you're considering making the same switch, just remember: it's not just a syntax change, it's a whole new way of thinking about smart contracts. It really opens your mind.</em></strong></p>
]]></content:encoded></item></channel></rss>