<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rayQu</title>
    <description>The latest articles on DEV Community by rayQu (@rollingindo).</description>
    <link>https://dev.to/rollingindo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2700932%2Ff08a1667-4508-436f-82f9-75f307242ab5.png</url>
      <title>DEV Community: rayQu</title>
      <link>https://dev.to/rollingindo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rollingindo"/>
    <language>en</language>
    <item>
      <title>Building a Confidential Bug Bounty Platform with Oasis Sapphire</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Sun, 11 Oct 2026 06:47:52 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-bug-bounty-platform-with-oasis-sapphire-1al6</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-bug-bounty-platform-with-oasis-sapphire-1al6</guid>
      <description>&lt;h1&gt;
  
  
  Building a Confidential Bug Bounty Platform with Oasis Sapphire
&lt;/h1&gt;

&lt;h3&gt;
  
  
  An advanced tutorial on private vulnerability reports, confidential triage, and verifiable bounty payouts
&lt;/h3&gt;

&lt;p&gt;Bug bounty platforms have a difficult security problem.&lt;/p&gt;

&lt;p&gt;A researcher discovers a vulnerability in a smart contract and submits a report. The report may contain exploit steps, proof-of-concept code, affected endpoints, or details that would allow someone else to reproduce the issue.&lt;/p&gt;

&lt;p&gt;If the report becomes public too early, the disclosure process can turn into an attacker's instruction manual.&lt;/p&gt;

&lt;p&gt;But making everything private creates another problem: researchers need confidence that their submissions will be timestamped, evaluated fairly, and rewarded according to clear rules.&lt;/p&gt;

&lt;p&gt;This is an interesting use case for &lt;strong&gt;Oasis Sapphire&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sapphire provides an EVM-compatible environment for confidential smart contract state and execution. Instead of putting sensitive report contents directly into public contract storage or events, we can design a system where reports remain confidential while submission commitments, bounty rules, and final payout records can be independently verified.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll design an advanced confidential bug bounty platform using Solidity and Oasis Sapphire.&lt;/p&gt;

&lt;p&gt;We'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confidential vulnerability submissions&lt;/li&gt;
&lt;li&gt;Commitments that provide evidence of a report's integrity&lt;/li&gt;
&lt;li&gt;Private triage and severity assessment&lt;/li&gt;
&lt;li&gt;Bounty policy enforcement&lt;/li&gt;
&lt;li&gt;Duplicate-submission handling&lt;/li&gt;
&lt;li&gt;Controlled disclosure&lt;/li&gt;
&lt;li&gt;Verifiable payout records&lt;/li&gt;
&lt;li&gt;Threat modeling and production considerations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key design principle is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the vulnerability confidential until disclosure is safe, but make the bounty process accountable from the beginning.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Architecture: Separate the Report from Its Proof
&lt;/h2&gt;

&lt;p&gt;The first mistake would be to put the entire vulnerability report onchain.&lt;/p&gt;

&lt;p&gt;Even if a Solidity variable is marked &lt;code&gt;private&lt;/code&gt;, that keyword does not provide confidentiality on an ordinary public EVM chain. Sapphire's confidential execution environment gives us a different foundation, but we still need to avoid accidentally leaking sensitive data through events, public interfaces, or transaction metadata.&lt;/p&gt;

&lt;p&gt;We'll separate the system into three components.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    R[Security Researcher] --&amp;gt;|Encrypted submission| S[Sapphire Contract]
    S --&amp;gt; C[Confidential Report State]
    S --&amp;gt; P[Public Commitment and Status]
    T[Authorized Triage Team] --&amp;gt;|Authenticated review| S
    S --&amp;gt;|Approved decision| B[Bounty Policy]
    B --&amp;gt;|Verified payout authorization| W[Treasury or Payout Contract]
    W --&amp;gt; L[Public Settlement Record]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The report contains the technical details of the vulnerability. The commitment gives the researcher evidence that a specific report was submitted. The payout record makes the financial outcome auditable without publishing the exploit itself.&lt;/p&gt;

&lt;p&gt;These are separate guarantees.&lt;/p&gt;

&lt;p&gt;A commitment does not prove that a report is valid. A confidential contract does not automatically make every transaction field private. A payout record does not prove that the triage decision was correct.&lt;/p&gt;

&lt;p&gt;The system must make those boundaries explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Define the Submission Lifecycle
&lt;/h2&gt;

&lt;p&gt;A bounty platform needs a lifecycle that prevents reports from jumping directly from submission to payout.&lt;/p&gt;

&lt;p&gt;We'll use the following states:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;stateDiagram-v2
    [*] --&amp;gt; Submitted
    Submitted --&amp;gt; UnderReview
    UnderReview --&amp;gt; Duplicate
    UnderReview --&amp;gt; Rejected
    UnderReview --&amp;gt; Accepted
    Accepted --&amp;gt; RewardAuthorized
    RewardAuthorized --&amp;gt; Paid
    Accepted --&amp;gt; DisclosureReady
    Paid --&amp;gt; DisclosureReady
    DisclosureReady --&amp;gt; Disclosed&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;A real implementation should define whether payment is required before disclosure, how disputed reports are handled, and what happens if a project never responds.&lt;/p&gt;

&lt;p&gt;For this tutorial, the important distinction is between &lt;strong&gt;report confidentiality&lt;/strong&gt; and &lt;strong&gt;process transparency&lt;/strong&gt;. We can keep the report private while exposing a limited, non-sensitive status and eventual settlement record.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Project Structure
&lt;/h2&gt;

&lt;p&gt;We'll use Solidity, Hardhat, and TypeScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confidential-bounty/
├── contracts/
│   ├── ConfidentialBounty.sol
│   ├── BountyTreasury.sol
│   └── interfaces/
│       └── IBountyPolicy.sol
├── scripts/
│   ├── deploy.ts
│   └── configure-policy.ts
├── test/
│   ├── ConfidentialBounty.ts
│   └── BountyTreasury.ts
├── app/
│   ├── submit-report.ts
│   └── reviewer-client.ts
├── docs/
│   ├── threat-model.md
│   └── disclosure-policy.md
├── hardhat.config.ts
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract handles submission state and policy enforcement. The client is responsible for encrypting and submitting the report through a Sapphire-compatible transaction flow. The treasury handles settlement separately so the bounty system doesn't need unrestricted custody of funds.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Model the Report Carefully
&lt;/h2&gt;

&lt;p&gt;A report needs more than a string.&lt;/p&gt;

&lt;p&gt;At minimum, it needs an identifier, a commitment, a submitter, a status, and a bounty policy version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract ConfidentialBounty {
    enum Status {
        None,
        Submitted,
        UnderReview,
        Duplicate,
        Rejected,
        Accepted,
        RewardAuthorized,
        Paid,
        Disclosed
    }

    struct Report {
        bytes32 commitment;
        address researcher;
        Status status;
        uint32 policyVersion;
        uint64 submittedAt;
    }

    mapping(bytes32 =&amp;gt; Report) private reports;

    event ReportRegistered(
        bytes32 indexed reportId,
        bytes32 commitment,
        uint32 policyVersion
    );

    function registerReport(
        bytes32 reportId,
        bytes32 commitment,
        uint32 policyVersion
    ) external {
        require(reportId != bytes32(0), "Invalid report ID");
        require(commitment != bytes32(0), "Invalid commitment");
        require(
            reports[reportId].status == Status.None,
            "Report already exists"
        );

        reports[reportId] = Report({
            commitment: commitment,
            researcher: msg.sender,
            status: Status.Submitted,
            policyVersion: policyVersion,
            submittedAt: uint64(block.timestamp)
        });

        emit ReportRegistered(
            reportId,
            commitment,
            policyVersion
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a starting point, not a complete production contract.&lt;/p&gt;

&lt;p&gt;The example deliberately keeps the report body out of the event. The commitment can help establish that a particular report corresponds to a registered submission, but the report ID and submitter address are still visible in the event.&lt;/p&gt;

&lt;p&gt;If submitter identity or submission timing is sensitive, even this metadata requires additional design.&lt;/p&gt;

&lt;p&gt;On Sapphire, the confidential report body should be stored and processed through confidential contract state, while the public interface should reveal only information that the disclosure policy explicitly permits.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Commit to the Report Without Publishing It
&lt;/h2&gt;

&lt;p&gt;How can a researcher prove that a report hasn't been silently replaced after submission?&lt;/p&gt;

&lt;p&gt;A useful starting point is a cryptographic commitment.&lt;/p&gt;

&lt;p&gt;Let:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;report&lt;/code&gt; be the complete report content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;salt&lt;/code&gt; be a high-entropy random secret&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;version&lt;/code&gt; identify the report format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The researcher computes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commitment = keccak256(
    encode(version, report, salt)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commitment can be registered with the report ID. Later, the researcher can reveal the report and salt to demonstrate that the revealed content matches the original commitment.&lt;/p&gt;

&lt;p&gt;However, there is a critical caveat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hash does not automatically protect low-entropy information.&lt;/strong&gt; If the report is predictable, someone may guess candidate contents and compare their hashes. The random salt must be generated securely and kept secret until disclosure.&lt;/p&gt;

&lt;p&gt;The client should use a canonical encoding format, and the contract should define exactly which fields are included in the commitment. Otherwise, different encodings of the same logical report may produce inconsistent commitments.&lt;/p&gt;

&lt;p&gt;For reports containing confidential source code or exploit details, the commitment should not replace encrypted storage. It provides an integrity mechanism, not a confidentiality mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Enforce Bounty Rules with Versioned Policies
&lt;/h2&gt;

&lt;p&gt;Bounty programs often change their reward limits, supported targets, and severity definitions.&lt;/p&gt;

&lt;p&gt;If a policy changes while a report is under review, which version should apply?&lt;/p&gt;

&lt;p&gt;The answer should be explicit.&lt;/p&gt;

&lt;p&gt;Each report should reference a policy version that was active when it was submitted. The policy should define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eligible contracts or systems&lt;/li&gt;
&lt;li&gt;Severity categories&lt;/li&gt;
&lt;li&gt;Reward ranges&lt;/li&gt;
&lt;li&gt;Duplicate-report rules&lt;/li&gt;
&lt;li&gt;Exclusions&lt;/li&gt;
&lt;li&gt;Disclosure requirements&lt;/li&gt;
&lt;li&gt;Review deadlines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct BountyPolicy {
    uint128 criticalMax;
    uint128 highMax;
    uint128 mediumMax;
    uint128 lowMax;
    uint64 reviewDeadline;
    bool active;
}

mapping(uint32 =&amp;gt; BountyPolicy) private policies;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, changing a policy should create a new version rather than silently rewriting the terms associated with existing reports.&lt;/p&gt;

&lt;p&gt;The contract should validate that the selected policy version exists and that the report references a version permitted by the program.&lt;/p&gt;

&lt;p&gt;The reward authorization process should also verify that the report is in an eligible state, the proposed reward is within the relevant limit, and the request has not already been used.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Keep Triage Confidential, but Make It Accountable
&lt;/h2&gt;

&lt;p&gt;A confidential report can contain information that only a small review team should access.&lt;/p&gt;

&lt;p&gt;That does not mean the triage process should be an unrestricted black box.&lt;/p&gt;

&lt;p&gt;A production design should separate the permissions to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read a report.&lt;/li&gt;
&lt;li&gt;Change its status.&lt;/li&gt;
&lt;li&gt;Assign a severity.&lt;/li&gt;
&lt;li&gt;Authorize a reward.&lt;/li&gt;
&lt;li&gt;Approve disclosure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These permissions should not automatically belong to the same address.&lt;/p&gt;

&lt;p&gt;A multisig or governance-controlled role can manage reviewer membership, while a dedicated authorization path enforces reward limits. For more complex offchain analysis, a ROFL application could perform selected computations inside a trusted execution environment and provide evidence about the computation's origin.&lt;/p&gt;

&lt;p&gt;ROFL should not be treated as a magical correctness oracle. Remote attestation can provide evidence about the environment and code being executed, but it does not prove that the underlying vulnerability assessment is objectively correct.&lt;/p&gt;

&lt;p&gt;A reviewer still needs sound security reasoning, and the protocol needs a clear process for disputes.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. A Safer Disclosure Workflow
&lt;/h2&gt;

&lt;p&gt;Disclosure is one of the most important parts of the system.&lt;/p&gt;

&lt;p&gt;A report should not become public just because its status changes to &lt;code&gt;Accepted&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, define a separate disclosure process with explicit conditions:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant R as Researcher
    participant C as Sapphire Contract
    participant V as Reviewer
    participant T as Treasury
    participant P as Project Maintainer

    R-&amp;gt;&amp;gt;C: Submit encrypted report
    C--&amp;gt;&amp;gt;R: Report ID and commitment
    V-&amp;gt;&amp;gt;C: Request authorized review
    V-&amp;gt;&amp;gt;C: Record review decision
    C-&amp;gt;&amp;gt;C: Check policy and status
    C-&amp;gt;&amp;gt;T: Authorize eligible reward
    T--&amp;gt;&amp;gt;R: Pay researcher
    P-&amp;gt;&amp;gt;P: Patch and validate vulnerability
    P-&amp;gt;&amp;gt;C: Confirm disclosure readiness
    R-&amp;gt;&amp;gt;P: Coordinate responsible disclosure&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This is a conceptual workflow. A production system must implement authenticated reviewer access, authorization checks, deadlines, dispute resolution, and secure report delivery.&lt;/p&gt;

&lt;p&gt;In particular, a public function that returns a report body would undermine confidentiality. Reviewer access should use Sapphire's supported confidential call and authentication patterns, and sensitive report data should never be emitted in ordinary public events.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Threat Model
&lt;/h2&gt;

&lt;p&gt;Before deployment, consider at least the following threats.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threat&lt;/th&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Report contents leak through events&lt;/td&gt;
&lt;td&gt;Emit only approved metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Report is replaced after submission&lt;/td&gt;
&lt;td&gt;Use a canonical, salted commitment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Researcher submits the same report repeatedly&lt;/td&gt;
&lt;td&gt;Define duplicate rules and use stable report identifiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewer abuses privileged access&lt;/td&gt;
&lt;td&gt;Separate roles, log approved actions, and limit permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reward is paid twice&lt;/td&gt;
&lt;td&gt;Use unique authorization IDs and replay protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy changes mid-review&lt;/td&gt;
&lt;td&gt;Pin each report to an explicit policy version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exploit becomes public too early&lt;/td&gt;
&lt;td&gt;Define access control and staged disclosure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata reveals a sensitive incident&lt;/td&gt;
&lt;td&gt;Minimize public fields and evaluate timing leakage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROFL computation is treated as unquestionable&lt;/td&gt;
&lt;td&gt;Separate attestation evidence from correctness claims&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Confidential computing also has limitations. Transaction timing, gas usage, input size, and observable state transitions can reveal information. Sapphire developers should review the platform's security guidance and avoid assuming that encrypted state eliminates every side channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. What Makes This Useful Beyond Bug Bounties?
&lt;/h2&gt;

&lt;p&gt;The same design pattern can support other applications that need confidential submissions and accountable decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private security audits&lt;/li&gt;
&lt;li&gt;Confidential vulnerability disclosure&lt;/li&gt;
&lt;li&gt;Responsible AI incident reporting&lt;/li&gt;
&lt;li&gt;Private compliance reports&lt;/li&gt;
&lt;li&gt;Confidential grant applications&lt;/li&gt;
&lt;li&gt;Whistleblower submission systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In each case, the core problem is similar: sensitive information must be available to authorized reviewers without becoming public by default, while the process needs evidence, permissions, and a clear outcome.&lt;/p&gt;

&lt;p&gt;The most important architectural decision is to separate the &lt;strong&gt;confidential content&lt;/strong&gt;, the &lt;strong&gt;public evidence&lt;/strong&gt;, and the &lt;strong&gt;authority to act on a submission&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Oasis Sapphire provides a confidential EVM environment for building this kind of application. ROFL can extend the architecture when verifiable offchain computation is useful. Neither removes the need for careful access control, cryptographic design, or a well-defined disclosure process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A bug bounty platform is a useful example of why confidentiality and transparency don't have to be opposites.&lt;/p&gt;

&lt;p&gt;The vulnerability report can remain confidential. The submission commitment can provide evidence of integrity. The policy can constrain the reward. The settlement can be independently verified. And disclosure can happen only when the appropriate conditions are met.&lt;/p&gt;

&lt;p&gt;That is a much more useful model than publishing every detail onchain or asking everyone to trust a private database.&lt;/p&gt;

&lt;p&gt;The goal is not to make the entire process invisible.&lt;/p&gt;

&lt;p&gt;It is to make sensitive information private while keeping important decisions accountable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Oasis Sapphire: &lt;a href="https://docs.oasis.io/build/sapphire/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sapphire developer documentation: &lt;a href="https://docs.oasis.io/build/sapphire/develop/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Oasis ROFL: &lt;a href="https://docs.oasis.io/build/rofl/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/rofl/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sapphire encrypted events: &lt;a href="https://docs.oasis.io/build/sapphire/develop/encrypted-events/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/encrypted-events/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Oasis security documentation: &lt;a href="https://docs.oasis.io/build/sapphire/develop/security/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/security/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web3</category>
      <category>blockchain</category>
      <category>privacy</category>
      <category>ai</category>
    </item>
    <item>
      <title>Build a Private Onchain Feedback Box with Oasis Sapphire</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Thu, 24 Sep 2026 05:33:36 +0000</pubDate>
      <link>https://dev.to/rollingindo/build-a-private-onchain-feedback-box-with-oasis-sapphire-aa4</link>
      <guid>https://dev.to/rollingindo/build-a-private-onchain-feedback-box-with-oasis-sapphire-aa4</guid>
      <description>&lt;p&gt;What if you wanted to collect honest feedback onchain without publishing everyone's answers?&lt;/p&gt;

&lt;p&gt;A normal Ethereum smart contract is a strange place for something like this.&lt;/p&gt;

&lt;p&gt;You can store feedback cheaply and transparently, but anyone can inspect the contract's storage or events. That is useful for many applications, but not for anonymous feedback, internal surveys, product reviews, private ratings, or user research.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Oasis Sapphire&lt;/strong&gt; becomes interesting.&lt;/p&gt;

&lt;p&gt;Sapphire is an EVM-compatible confidential environment where contract state is encrypted and confidential computation happens inside Oasis infrastructure. You can therefore keep sensitive application data private while still using familiar Solidity and EVM tooling.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll build a small &lt;strong&gt;private feedback box&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Users will be able to submit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a 1–5 rating&lt;/li&gt;
&lt;li&gt;a text comment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The individual comments will remain confidential.&lt;/p&gt;

&lt;p&gt;The contract will expose only aggregate information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;number of submissions&lt;/li&gt;
&lt;li&gt;total rating&lt;/li&gt;
&lt;li&gt;average rating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the application can answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What do users think?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;without automatically publishing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What did Alice specifically say?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;The architecture is intentionally small.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌──────────────────────┐
                    │      User Wallet     │
                    └──────────┬───────────┘
                               │
                       Encrypted TX
                               │
                               ▼
                    ┌──────────────────────┐
                    │   Oasis Sapphire     │
                    │                      │
                    │  Private feedback    │
                    │  + aggregate stats   │
                    └──────────┬───────────┘
                               │
                 ┌─────────────┴─────────────┐
                 │                           │
                 ▼                           ▼
          Private comments            Public aggregates
          + ratings                   count / total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important design decision is that we don't emit the feedback itself as a normal Solidity event.&lt;/p&gt;

&lt;p&gt;That's important because &lt;strong&gt;standard EVM events are public on Sapphire too&lt;/strong&gt;. If you put sensitive feedback directly into an event, you can undermine the confidentiality of the underlying state. Oasis provides encrypted events for applications that specifically need confidential event payloads.&lt;/p&gt;

&lt;p&gt;For our simple application, we'll just keep the feedback in contract state.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Create the Project
&lt;/h1&gt;

&lt;p&gt;We'll use Hardhat and TypeScript.&lt;/p&gt;

&lt;p&gt;Create a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;private-feedback-box
&lt;span class="nb"&gt;cd &lt;/span&gt;private-feedback-box

npm init &lt;span class="nt"&gt;-y&lt;/span&gt;

npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  hardhat &lt;span class="se"&gt;\&lt;/span&gt;
  @nomicfoundation/hardhat-toolbox &lt;span class="se"&gt;\&lt;/span&gt;
  typescript &lt;span class="se"&gt;\&lt;/span&gt;
  ts-node

npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  @oasisprotocol/sapphire-contracts &lt;span class="se"&gt;\&lt;/span&gt;
  @oasisprotocol/sapphire-paratime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oasis Sapphire is EVM-compatible, so existing Ethereum development tools such as Hardhat can be used. The main addition is Oasis' Sapphire tooling for confidential transactions and contract functionality.&lt;/p&gt;

&lt;p&gt;Your project can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private-feedback-box/
│
├── contracts/
│   └── FeedbackBox.sol
│
├── scripts/
│   └── deploy.ts
│
├── test/
│   └── FeedbackBox.ts
│
├── hardhat.config.ts
├── package.json
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  2. Configure Sapphire
&lt;/h1&gt;

&lt;p&gt;Create &lt;code&gt;hardhat.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HardhatUserConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hardhat/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@oasisprotocol/sapphire-hardhat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@nomicfoundation/hardhat-toolbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRIVATE_KEY&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;mnemonic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test test test test test test test test test test test junk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m/44'/60'/0'/0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;initialIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HardhatUserConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;solidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.8.24&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;evmVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paris&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sapphire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://sapphire.oasis.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x5afe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sapphire-testnet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://testnet.sapphire.oasis.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x5aff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sapphire-localnet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8545&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x5afd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official Sapphire quickstart currently uses these Sapphire network identifiers and RPC endpoints for mainnet, testnet, and localnet.&lt;/p&gt;

&lt;p&gt;The important part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@oasisprotocol/sapphire-hardhat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the Hardhat environment to apply Sapphire's transaction handling automatically.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Write the Feedback Contract
&lt;/h1&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contracts/FeedbackBox.sol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract FeedbackBox {
    struct Feedback {
        uint8 rating;
        string comment;
        uint256 timestamp;
    }

    mapping(address =&amp;gt; Feedback) private feedback;

    uint256 private submissionCount;
    uint256 private totalRating;

    event FeedbackSubmitted(address indexed user);

    function submitFeedback(
        uint8 rating,
        string calldata comment
    ) external {
        require(rating &amp;gt;= 1 &amp;amp;&amp;amp; rating &amp;lt;= 5, "rating must be 1-5");
        require(bytes(comment).length &amp;gt; 0, "comment required");

        require(
            feedback[msg.sender].timestamp == 0,
            "feedback already submitted"
        );

        feedback[msg.sender] = Feedback({
            rating: rating,
            comment: comment,
            timestamp: block.timestamp
        });

        submissionCount++;
        totalRating += rating;

        emit FeedbackSubmitted(msg.sender);
    }

    function getMyFeedback()
        external
        view
        returns (
            uint8 rating,
            string memory comment,
            uint256 timestamp
        )
    {
        Feedback memory item = feedback[msg.sender];

        require(item.timestamp != 0, "no feedback");

        return (
            item.rating,
            item.comment,
            item.timestamp
        );
    }

    function getStats()
        external
        view
        returns (
            uint256 count,
            uint256 total,
            uint256 average
        )
    {
        count = submissionCount;
        total = totalRating;

        if (count &amp;gt; 0) {
            average = total / count;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this looks almost identical to a normal Solidity contract.&lt;/p&gt;

&lt;p&gt;That's intentional.&lt;/p&gt;

&lt;p&gt;The interesting part is not a new programming language.&lt;/p&gt;

&lt;p&gt;It's the execution environment.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Why &lt;code&gt;private&lt;/code&gt; Matters Differently Here
&lt;/h1&gt;

&lt;p&gt;On Ethereum, developers sometimes misunderstand Solidity's &lt;code&gt;private&lt;/code&gt; keyword.&lt;/p&gt;

&lt;p&gt;This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(address =&amp;gt; Feedback) private feedback;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does &lt;strong&gt;not&lt;/strong&gt; make the data cryptographically private on Ethereum.&lt;/p&gt;

&lt;p&gt;It only prevents other Solidity contracts from directly accessing the variable.&lt;/p&gt;

&lt;p&gt;Blockchain observers can still inspect Ethereum's underlying state.&lt;/p&gt;

&lt;p&gt;Sapphire changes the model.&lt;/p&gt;

&lt;p&gt;Sapphire's contract state is encrypted, and the contract running inside the confidential environment is the entity that can request the relevant state encryption key.&lt;/p&gt;

&lt;p&gt;That means our feedback mapping can actually contain confidential application data.&lt;/p&gt;

&lt;p&gt;So this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(address =&amp;gt; Feedback) private feedback;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is meaningful for confidentiality when deployed on Sapphire.&lt;/p&gt;

&lt;p&gt;But there is an important detail.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Don't Accidentally Publish the Feedback
&lt;/h1&gt;

&lt;p&gt;This event is okay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event FeedbackSubmitted(address indexed user);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells observers that an address submitted feedback.&lt;/p&gt;

&lt;p&gt;But this would be a bad idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event FeedbackSubmitted(
    address indexed user,
    uint8 rating,
    string comment
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because normal event payloads are public.&lt;/p&gt;

&lt;p&gt;Sapphire's documentation explicitly notes that Solidity events behave like Ethereum events, meaning event data is not automatically confidential.&lt;/p&gt;

&lt;p&gt;If you need confidential event data, Sapphire supports encrypted event payloads.&lt;/p&gt;

&lt;p&gt;For our beginner example, however, we simply don't put the sensitive information into the event.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Add a Simple Deployment Script
&lt;/h1&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/deploy.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hardhat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FeedbackBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeedbackBox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feedbackBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;FeedbackBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;feedbackBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForDeployment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeedbackBox deployed to:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;feedbackBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAddress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then deploy to Sapphire Testnet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat run scripts/deploy.ts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; sapphire-testnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before deploying to testnet, you'll need Sapphire testnet tokens from the Oasis faucet.&lt;/p&gt;

&lt;p&gt;For rapid local development, Oasis also provides a Sapphire Localnet that runs in Docker. The official documentation recommends Localnet or Sapphire Testnet when testing confidential features.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Submit Private Feedback
&lt;/h1&gt;

&lt;p&gt;You can interact with the contract from a Hardhat task, script, or frontend.&lt;/p&gt;

&lt;p&gt;A simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feedbackBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeedbackBox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;CONTRACT_ADDRESS&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;feedbackBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitFeedback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The application is fast and easy to use.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feedback submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that the transaction containing the comment should be sent through Sapphire's confidential transaction tooling.&lt;/p&gt;

&lt;p&gt;For browser applications, Oasis provides wrappers for common EVM libraries including Ethers, Viem, and Wagmi.&lt;/p&gt;

&lt;p&gt;For example, the Sapphire TypeScript wrapper can wrap an injected wallet provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;wrapEthereumProvider&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@oasisprotocol/sapphire-paratime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;wrapEthereumProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ethereum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the layer that makes it practical to use normal Web3 wallets while still sending confidential transactions.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Reading the Statistics
&lt;/h1&gt;

&lt;p&gt;Now we have an interesting separation.&lt;/p&gt;

&lt;p&gt;Anyone can call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getStats()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and retrieve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Submissions: 143
Total rating: 587
Average: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that doesn't reveal every individual comment.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   Feedback
                       │
             ┌─────────┴─────────┐
             │                   │
             ▼                   ▼
       Private state         Aggregates
             │                   │
             │                   └── count
             │                   └── total
             │                   └── average
             │
             └── rating
             └── comment
             └── timestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a useful pattern far beyond feedback.&lt;/p&gt;

&lt;p&gt;The same architecture can work for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private product reviews&lt;/li&gt;
&lt;li&gt;internal company surveys&lt;/li&gt;
&lt;li&gt;anonymous community feedback&lt;/li&gt;
&lt;li&gt;private ratings&lt;/li&gt;
&lt;li&gt;confidential research&lt;/li&gt;
&lt;li&gt;user preference storage&lt;/li&gt;
&lt;li&gt;private application configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The general pattern is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Store sensitive information privately. Expose only the derived information that needs to be public.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  9. What About &lt;code&gt;getMyFeedback()&lt;/code&gt;?
&lt;/h1&gt;

&lt;p&gt;There's one subtle Sapphire concept worth understanding.&lt;/p&gt;

&lt;p&gt;This function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getMyFeedback()
    external
    view
    returns (...)
{
    Feedback memory item = feedback[msg.sender];

    require(item.timestamp != 0, "no feedback");

    return (...);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;works naturally for authenticated calls.&lt;/p&gt;

&lt;p&gt;But a plain &lt;code&gt;eth_call&lt;/code&gt; on Sapphire does not automatically give the contract an arbitrary authenticated &lt;code&gt;msg.sender&lt;/code&gt;. For unauthenticated view calls, Sapphire uses the zero address.&lt;/p&gt;

&lt;p&gt;This is important because confidential data requires stronger access control than a traditional public EVM application.&lt;/p&gt;

&lt;p&gt;For frontend applications, Oasis documents &lt;strong&gt;SIWE-based authenticated view calls&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For backend services, Sapphire also supports signed queries using EIP-712.&lt;/p&gt;

&lt;p&gt;So a production version of this feedback box would typically use authenticated view calls when users need to retrieve their private feedback.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. A Minimal Test
&lt;/h1&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test/FeedbackBox.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hardhat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeedbackBox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stores feedback and updates statistics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSigners&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FeedbackBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeedbackBox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;FeedbackBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForDeployment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitFeedback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Excellent experience&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStats&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;average&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prevents duplicate submissions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FeedbackBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeedbackBox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;FeedbackBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForDeployment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitFeedback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good experience&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitFeedback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Trying again&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revertedWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;feedback already submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For testing Sapphire-specific confidentiality, use Sapphire Localnet or Sapphire Testnet rather than relying only on a standard Ethereum-style local chain. Oasis specifically recommends a Sapphire-supported network for testing confidential features.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. One Important Production Improvement
&lt;/h1&gt;

&lt;p&gt;Our example intentionally keeps the contract simple.&lt;/p&gt;

&lt;p&gt;A production application should think carefully about &lt;strong&gt;metadata leakage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Confidentiality does not necessarily hide everything about execution.&lt;/p&gt;

&lt;p&gt;Oasis documents several potential side channels, including gas usage, execution time, storage-access patterns, and transaction size. The size of encrypted state is also not automatically hidden.&lt;/p&gt;

&lt;p&gt;For example, this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (bytes(comment).length &amp;gt; 1000) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could potentially create observable differences in execution.&lt;/p&gt;

&lt;p&gt;Likewise, variable-length sensitive inputs can expose their size.&lt;/p&gt;

&lt;p&gt;For higher-security applications, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fixed-size inputs&lt;/li&gt;
&lt;li&gt;padding&lt;/li&gt;
&lt;li&gt;bucketized sizes&lt;/li&gt;
&lt;li&gt;constant-ish execution paths&lt;/li&gt;
&lt;li&gt;avoiding sensitive values in normal events&lt;/li&gt;
&lt;li&gt;carefully designed access control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These considerations become increasingly important as an application moves from a demo to production.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Why This Pattern Is Interesting
&lt;/h1&gt;

&lt;p&gt;The feedback box looks almost trivial.&lt;/p&gt;

&lt;p&gt;That's actually the point.&lt;/p&gt;

&lt;p&gt;A blockchain doesn't have to put every piece of application data into public state.&lt;/p&gt;

&lt;p&gt;Instead, you can divide application information into two categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  Public information
&lt;/h3&gt;

&lt;p&gt;Things users need to verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How many submissions?
What is the average rating?
Has this address submitted?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Private information
&lt;/h3&gt;

&lt;p&gt;Things the application needs but the entire network doesn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What did the user write?
What rating did they give?
What private metadata is attached?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sapphire gives developers an environment where that distinction can exist inside an EVM-compatible application.&lt;/p&gt;

&lt;p&gt;You don't have to abandon Solidity.&lt;/p&gt;

&lt;p&gt;You don't need to invent a new smart-contract language.&lt;/p&gt;

&lt;p&gt;You simply design the application around confidential state from the beginning.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Architecture
&lt;/h1&gt;

&lt;p&gt;The finished application is conceptually simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 │
 │ encrypted transaction
 ▼
┌──────────────────────────────┐
│       Oasis Sapphire         │
│                              │
│  ┌────────────────────────┐  │
│  │ Confidential Feedback  │  │
│  │                        │  │
│  │ rating                 │  │
│  │ comment                │  │
│  │ timestamp              │  │
│  └────────────────────────┘  │
│                              │
│  ┌────────────────────────┐  │
│  │ Public Aggregates      │  │
│  │                        │  │
│  │ count                  │  │
│  │ total                  │  │
│  │ average                │  │
│  └────────────────────────┘  │
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bigger lesson isn't the feedback form.&lt;/p&gt;

&lt;p&gt;It's the architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blockchain applications don't always need to choose between complete transparency and complete centralization.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With confidential execution, an application can keep sensitive state private while still using smart contracts, wallets, and verifiable onchain state.&lt;/p&gt;

&lt;p&gt;That opens up a surprisingly large design space for applications that previously didn't make much sense on public blockchains.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Can Build From Here
&lt;/h2&gt;

&lt;p&gt;Once the basic feedback box works, several extensions become possible:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anonymous mode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Allow users to submit feedback without storing their wallet address alongside the feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple surveys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create separate confidential surveys with different access policies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private categories&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Store additional confidential metadata such as department, product version, or region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encrypted events&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use Sapphire's encrypted-event functionality when an off-chain service needs to consume confidential feedback without exposing the plaintext onchain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authenticated dashboards&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add SIWE authentication so authorized users can retrieve confidential records through a frontend.&lt;/p&gt;

&lt;p&gt;And that's where this small tutorial becomes a reusable pattern:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep raw data confidential. Publish only the information that actually needs to be public.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is one of the simplest ways to start thinking differently about what an onchain application can be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Oasis Sapphire&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oasis.io/build/sapphire/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sapphire Developer Documentation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oasis.io/build/sapphire/develop/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sapphire Quickstart&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oasis.io/build/sapphire/quickstart/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/quickstart/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;View-Call Authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oasis.io/build/sapphire/develop/authentication/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/authentication/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encrypted Events&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oasis.io/build/sapphire/develop/encrypted-events/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/encrypted-events/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing Sapphire Applications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oasis.io/build/sapphire/develop/testing/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/testing/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Confidential On-Chain Credit Underwriting Engine with Oasis Sapphire + ROFL</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Wed, 09 Sep 2026 14:44:56 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-on-chain-credit-underwriting-engine-with-oasis-sapphire-rofl-3300</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-on-chain-credit-underwriting-engine-with-oasis-sapphire-rofl-3300</guid>
      <description>&lt;p&gt;What if a DeFi application could evaluate a wallet's creditworthiness without publishing the credit score, risk thresholds, underwriting rules, or internal decision variables on-chain?&lt;/p&gt;

&lt;p&gt;That is the problem we are going to solve.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a &lt;strong&gt;confidential on-chain credit underwriting engine&lt;/strong&gt; using &lt;strong&gt;Oasis Sapphire&lt;/strong&gt;, &lt;strong&gt;Oasis ROFL&lt;/strong&gt;, and &lt;strong&gt;Base&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The application will have a deliberately split architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base&lt;/strong&gt; remains the public source of collateral and lending activity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ROFL&lt;/strong&gt; collects public blockchain data and performs the risk calculation inside a Trusted Execution Environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sapphire&lt;/strong&gt; stores the confidential underwriting state.&lt;/li&gt;
&lt;li&gt;A Sapphire smart contract decides whether a borrower is eligible for a credit limit.&lt;/li&gt;
&lt;li&gt;The borrower's exact score, policy thresholds, and internal risk parameters are never published as ordinary blockchain state.&lt;/li&gt;
&lt;li&gt;The final authorization can be consumed by another application without exposing the complete underwriting model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not intended to be a production lending protocol. It is an architectural reference showing how confidential computation can be combined with public blockchain state.&lt;/p&gt;

&lt;p&gt;The interesting part is not hiding the blockchain.&lt;/p&gt;

&lt;p&gt;The interesting part is deciding &lt;strong&gt;which parts should be public, which parts should be confidential, and which results should be verifiable without revealing the inputs that produced them.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a crypto-native merchant financing protocol.&lt;/p&gt;

&lt;p&gt;A merchant deposits collateral on Base and wants access to a short-term credit line.&lt;/p&gt;

&lt;p&gt;A traditional DeFi lending protocol might calculate something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;collateral value
+
borrowed value
+
liquidation history
+
wallet activity
=
credit limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that the underwriting process itself can become public information.&lt;/p&gt;

&lt;p&gt;Suppose the protocol uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;minimum_score = 720
max_utilization = 65%
max_drawdown = 30%
risk_weight = 0.42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and publishes all of those values on-chain.&lt;/p&gt;

&lt;p&gt;An adversary can now reverse-engineer the protocol.&lt;/p&gt;

&lt;p&gt;Worse, if the protocol stores a user's individual score publicly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wallet:
0x1234...

score:
783

creditLimit:
$42,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the blockchain becomes a public credit database.&lt;/p&gt;

&lt;p&gt;That is undesirable for several reasons.&lt;/p&gt;

&lt;p&gt;A borrower may not want every observer to know their risk classification.&lt;/p&gt;

&lt;p&gt;A protocol may not want competitors to know its underwriting methodology.&lt;/p&gt;

&lt;p&gt;A risk provider may not want its proprietary model exposed.&lt;/p&gt;

&lt;p&gt;And a sophisticated attacker may deliberately optimize behavior against the published thresholds.&lt;/p&gt;

&lt;p&gt;The goal, therefore, is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Make the blockchain private."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep the source data and underwriting logic confidential while publishing only the minimum information required to authorize a financial action.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much more interesting architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. What We Are Building
&lt;/h1&gt;

&lt;p&gt;Our example protocol will be called &lt;strong&gt;PrivateCredit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A user interacts with the system through a normal wallet.&lt;/p&gt;

&lt;p&gt;The user has collateral and activity on Base.&lt;/p&gt;

&lt;p&gt;The application periodically requests an underwriting decision.&lt;/p&gt;

&lt;p&gt;The ROFL worker reads public blockchain information, calculates a risk profile inside its TEE, and submits the resulting authorization to Sapphire.&lt;/p&gt;

&lt;p&gt;Sapphire stores the confidential result and exposes only narrowly scoped functions to consuming applications.&lt;/p&gt;

&lt;p&gt;The simplified flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR

    U[Borrower Wallet]

    B[Base]

    R[ROFL Risk Engine]

    S[Sapphire]

    C[Credit Controller]

    L[Lending / Merchant App]

    U --&amp;gt;|Collateral + activity| B
    B --&amp;gt;|Public blockchain state| R

    R --&amp;gt;|Confidential underwriting result| S

    S --&amp;gt; C

    C --&amp;gt;|Authorized credit limit| L

    L --&amp;gt;|Credit action| U&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;There is an important boundary here.&lt;/p&gt;

&lt;p&gt;Base does &lt;strong&gt;not&lt;/strong&gt; become confidential.&lt;/p&gt;

&lt;p&gt;Anyone can still inspect the borrower's public transactions.&lt;/p&gt;

&lt;p&gt;Instead, we are protecting the &lt;strong&gt;derived intelligence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That distinction is fundamental.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Why ROFL + Sapphire?
&lt;/h1&gt;

&lt;p&gt;Sapphire is an EVM-compatible confidential runtime designed for confidential smart-contract state, encrypted transactions, and confidential computation.&lt;/p&gt;

&lt;p&gt;ROFL extends that model to off-chain workloads.&lt;/p&gt;

&lt;p&gt;A ROFL application executes inside a Trusted Execution Environment and can interact with external systems while retaining confidentiality and producing authenticated interactions with the Oasis ecosystem.&lt;/p&gt;

&lt;p&gt;That gives us two different computational environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sapphire
&lt;/h3&gt;

&lt;p&gt;Use Sapphire for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confidential contract state&lt;/li&gt;
&lt;li&gt;authorization&lt;/li&gt;
&lt;li&gt;encrypted inputs&lt;/li&gt;
&lt;li&gt;access-controlled outputs&lt;/li&gt;
&lt;li&gt;deterministic policy enforcement&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ROFL
&lt;/h3&gt;

&lt;p&gt;Use ROFL for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetching Base data&lt;/li&gt;
&lt;li&gt;aggregating many transactions&lt;/li&gt;
&lt;li&gt;calling external APIs&lt;/li&gt;
&lt;li&gt;running more expensive calculations&lt;/li&gt;
&lt;li&gt;maintaining private model parameters&lt;/li&gt;
&lt;li&gt;processing data that would be awkward or expensive on-chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is important.&lt;/p&gt;

&lt;p&gt;You don't want to perform a large historical risk calculation directly inside a smart contract.&lt;/p&gt;

&lt;p&gt;And you don't want the final authorization decision to exist only inside an off-chain server.&lt;/p&gt;

&lt;p&gt;So we use both.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Threat Model
&lt;/h1&gt;

&lt;p&gt;Before writing code, define what we are protecting.&lt;/p&gt;

&lt;p&gt;Our adversaries include:&lt;/p&gt;

&lt;h3&gt;
  
  
  Blockchain observers
&lt;/h3&gt;

&lt;p&gt;They can see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base transactions&lt;/li&gt;
&lt;li&gt;contract addresses&lt;/li&gt;
&lt;li&gt;public balances&lt;/li&gt;
&lt;li&gt;public collateral&lt;/li&gt;
&lt;li&gt;public borrowing activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We cannot hide those from someone who can inspect Base.&lt;/p&gt;

&lt;h3&gt;
  
  
  ROFL host/operator
&lt;/h3&gt;

&lt;p&gt;The application executes inside a TEE.&lt;/p&gt;

&lt;p&gt;The architecture therefore relies on the TEE and its attestation model rather than assuming that the underlying host operator should see the application's secrets.&lt;/p&gt;

&lt;p&gt;ROFL's security model is based around TEEs, remote attestation, Oasis consensus, and registered applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application users
&lt;/h3&gt;

&lt;p&gt;Users should not automatically receive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the complete risk model&lt;/li&gt;
&lt;li&gt;other users' scores&lt;/li&gt;
&lt;li&gt;internal thresholds&lt;/li&gt;
&lt;li&gt;historical underwriting decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Our own frontend
&lt;/h3&gt;

&lt;p&gt;The frontend should not become a privileged database.&lt;/p&gt;

&lt;p&gt;This is why sensitive state belongs in the confidential execution layer rather than simply being hidden behind a web API.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. What We Are NOT Protecting
&lt;/h1&gt;

&lt;p&gt;This is equally important.&lt;/p&gt;

&lt;p&gt;If a user's collateral is on Base, the collateral transaction is public.&lt;/p&gt;

&lt;p&gt;If the user interacts with a public lending protocol, those interactions are public.&lt;/p&gt;

&lt;p&gt;Sapphire does not magically erase public-chain history.&lt;/p&gt;

&lt;p&gt;Our confidentiality boundary begins when public information is ingested into the underwriting system.&lt;/p&gt;

&lt;p&gt;Think about the pipeline like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUBLIC
    |
    | Base transactions
    v
+--------------------+
| ROFL TEE            |
|                    |
| private features   |
| risk model         |
| policy parameters  |
+--------------------+
    |
    | authorized result
    v
+--------------------+
| Sapphire           |
|                    |
| private score      |
| credit limit       |
| expiry             |
+--------------------+
    |
    | minimal output
    v
CONSUMING APPLICATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the architecture we want.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Repository Structure
&lt;/h1&gt;

&lt;p&gt;The repository for the tutorial can be organized like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private-credit/
│
├── contracts/
│   ├── PrivateCredit.sol
│   ├── CreditController.sol
│   ├── PolicyRegistry.sol
│   └── interfaces/
│       └── IPrivateCredit.sol
│
├── rofl/
│   ├── src/
│   │   ├── index.ts
│   │   ├── base.ts
│   │   ├── scoring.ts
│   │   ├── features.ts
│   │   └── sapphire.ts
│   │
│   ├── Dockerfile
│   └── package.json
│
├── scripts/
│   ├── deploy.ts
│   ├── seed.ts
│   └── request-underwriting.ts
│
├── test/
│   ├── PrivateCredit.t.sol
│   ├── PolicyRegistry.t.sol
│   └── integration.t.sol
│
├── frontend/
│   └── ...
│
├── docker-compose.yml
├── hardhat.config.ts
├── foundry.toml
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important design decision is that the &lt;strong&gt;risk engine and the smart contract are separate components&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The smart contract does not trust arbitrary off-chain results.&lt;/p&gt;

&lt;p&gt;It verifies that the caller is an authorized ROFL application.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. The Confidential Contract
&lt;/h1&gt;

&lt;p&gt;Let's start with the Sapphire contract.&lt;/p&gt;

&lt;p&gt;The contract needs to maintain a confidential credit record.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct CreditRecord {
    uint256 limit;
    uint256 score;
    uint64 expiresAt;
    uint64 nonce;
    bool active;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We deliberately do not emit the score in a normal public event.&lt;/p&gt;

&lt;p&gt;Instead, the record remains inside Sapphire's confidential state.&lt;/p&gt;

&lt;p&gt;A simplified contract looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract PrivateCredit {
    struct CreditRecord {
        uint256 limit;
        uint256 score;
        uint64 expiresAt;
        uint64 nonce;
        bool active;
    }

    mapping(address =&amp;gt; CreditRecord) private records;

    address public roflApp;

    modifier onlyROFL() {
        require(msg.sender == roflApp, "not authorized");
        _;
    }

    constructor(address _roflApp) {
        roflApp = _roflApp;
    }

    function updateCredit(
        address borrower,
        uint256 score,
        uint256 limit,
        uint64 expiresAt
    ) external onlyROFL {
        CreditRecord storage record = records[borrower];

        record.score = score;
        record.limit = limit;
        record.expiresAt = expiresAt;
        record.nonce++;
        record.active = true;
    }

    function revokeCredit(
        address borrower
    ) external onlyROFL {
        records[borrower].active = false;
    }

    function getCreditLimit(
        address borrower
    ) external view returns (uint256) {
        CreditRecord memory record = records[borrower];

        require(record.active, "inactive");
        require(block.timestamp &amp;lt;= record.expiresAt, "expired");

        return record.limit;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simplified.&lt;/p&gt;

&lt;p&gt;A production version should use Sapphire's recommended mechanisms for authenticating ROFL-originated calls rather than treating an ordinary &lt;code&gt;msg.sender&lt;/code&gt; check as sufficient.&lt;/p&gt;

&lt;p&gt;The important architectural idea is that &lt;strong&gt;the authorization boundary belongs to the confidential runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;ROFL's integration with Sapphire allows contracts to verify that transactions originate from registered ROFL applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Don't Store a "Global Score"
&lt;/h1&gt;

&lt;p&gt;One easy mistake would be to create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(address =&amp;gt; uint256) public score;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and call it a privacy solution.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;public&lt;/code&gt; getter would make the value readable.&lt;/p&gt;

&lt;p&gt;Even without &lt;code&gt;public&lt;/code&gt;, you need to carefully consider which functions expose confidential information.&lt;/p&gt;

&lt;p&gt;Instead, the application should expose &lt;strong&gt;purpose-specific queries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function canBorrow(
    address borrower,
    uint256 amount
) external view returns (bool) {
    CreditRecord memory record = records[borrower];

    if (!record.active) {
        return false;
    }

    if (block.timestamp &amp;gt; record.expiresAt) {
        return false;
    }

    return amount &amp;lt;= record.limit;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The consuming application learns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;score = 813
limit = $47,391
riskWeight = 0.371
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very important privacy pattern:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Expose decisions rather than sensitive intermediate state whenever possible.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  9. The Feature Pipeline
&lt;/h1&gt;

&lt;p&gt;Now we move to ROFL.&lt;/p&gt;

&lt;p&gt;The risk engine needs public information.&lt;/p&gt;

&lt;p&gt;Suppose our underwriting model uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wallet_age
transaction_count
collateral_value
borrow_utilization
liquidation_count
repayment_ratio
portfolio_volatility
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ROFL application can query Base RPC endpoints and construct a feature vector.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;BorrowerFeatures&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;walletAgeDays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transactionCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;collateralUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;utilization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;liquidations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;repaymentRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;volatility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The feature extraction layer should be deterministic.&lt;/p&gt;

&lt;p&gt;That makes debugging substantially easier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeFeatures&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BorrowerFeatures&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;walletAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletAgeDays&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3650&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionCount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;collateral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collateralUsd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;utilization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;utilization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;liquidations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;liquidations&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;repayment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repaymentRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;volatility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;volatility&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that none of this is performed on Base.&lt;/p&gt;

&lt;p&gt;It happens inside the ROFL application.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. The Risk Model
&lt;/h1&gt;

&lt;p&gt;For this tutorial, use a deliberately simple model.&lt;/p&gt;

&lt;p&gt;The point isn't to invent a sophisticated credit algorithm.&lt;/p&gt;

&lt;p&gt;The point is to demonstrate where the algorithm lives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;normalizeFeatures&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletAge&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collateral&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repayment&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;utilization&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;liquidations&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;volatility&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production system would likely use a much more sophisticated model.&lt;/p&gt;

&lt;p&gt;It might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple assets&lt;/li&gt;
&lt;li&gt;liquidation distance&lt;/li&gt;
&lt;li&gt;realized volatility&lt;/li&gt;
&lt;li&gt;counterparty exposure&lt;/li&gt;
&lt;li&gt;stablecoin concentration&lt;/li&gt;
&lt;li&gt;protocol-specific risk&lt;/li&gt;
&lt;li&gt;historical repayment behavior&lt;/li&gt;
&lt;li&gt;oracle confidence&lt;/li&gt;
&lt;li&gt;time-weighted collateral&lt;/li&gt;
&lt;li&gt;anomaly detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the important thing is that the model does not need to become blockchain state.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. Keeping the Policy Confidential
&lt;/h1&gt;

&lt;p&gt;Now comes the interesting part.&lt;/p&gt;

&lt;p&gt;Suppose the protocol has these rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;minimumScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;720&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maximumUtilization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maximumLimitUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;liquidationPenalty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scoreVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-09&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publishing this policy would allow users to optimize directly against it.&lt;/p&gt;

&lt;p&gt;Instead, it remains inside the ROFL application.&lt;/p&gt;

&lt;p&gt;The underwriting function becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;underwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BorrowerFeatures&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeFeatures&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;POLICY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minimumScore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;score&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;utilizationPenalty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;utilization&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;POLICY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maximumLimitUsd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;POLICY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maximumLimitUsd&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;utilizationPenalty&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;limit&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, this is illustrative rather than production-grade underwriting.&lt;/p&gt;

&lt;p&gt;The important part is the confidentiality boundary.&lt;/p&gt;

&lt;p&gt;The user does not need to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;minimumScore = 720
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They only need to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;approved = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;approved = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  12. Why Not Just Use a Server?
&lt;/h1&gt;

&lt;p&gt;A reasonable question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not put this behind an HTTPS API?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You could.&lt;/p&gt;

&lt;p&gt;But then the trust model becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user
 |
 v
centralized server
 |
 v
database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user must trust the server operator to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run the correct code&lt;/li&gt;
&lt;li&gt;protect the database&lt;/li&gt;
&lt;li&gt;protect API credentials&lt;/li&gt;
&lt;li&gt;not alter decisions&lt;/li&gt;
&lt;li&gt;not leak scores&lt;/li&gt;
&lt;li&gt;not secretly change policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ROFL gives us a different architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user
 |
 v
Oasis network
 |
 +--&amp;gt; registered ROFL application
          |
          v
        TEE
          |
          v
      risk engine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ROFL model is specifically designed for arbitrary off-chain computation that can interact with external resources while retaining confidentiality and providing verifiable integration with the Oasis ecosystem.&lt;/p&gt;

&lt;p&gt;It doesn't mean "trust disappears."&lt;/p&gt;

&lt;p&gt;It means the trust assumptions become explicit and tied to the TEE, attestation, application registration, and Oasis infrastructure.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. Reading Base from ROFL
&lt;/h1&gt;

&lt;p&gt;The ROFL application needs an RPC connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPublicClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viem/chains&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPublicClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;http&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_RPC_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then inspect contracts.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collateral&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;baseClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;COLLATERAL_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;collateralAbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;balanceOf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;borrower&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key architectural point is that the ROFL application can communicate with external networks.&lt;/p&gt;

&lt;p&gt;ROFL applications can use authenticated network connections and can incorporate light clients or HTTPS/TLS connections depending on the application.&lt;/p&gt;

&lt;p&gt;That makes ROFL especially useful for applications where the input data does not originate entirely inside Oasis.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. Building the Underwriting Worker
&lt;/h1&gt;

&lt;p&gt;The worker ties everything together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processBorrower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;borrower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;collectFeatures&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;borrower&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;underwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;revokeCredit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;borrower&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateCredit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;borrower&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production version should additionally include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;idempotency&lt;/li&gt;
&lt;li&gt;nonce handling&lt;/li&gt;
&lt;li&gt;stale-data detection&lt;/li&gt;
&lt;li&gt;retry logic&lt;/li&gt;
&lt;li&gt;RPC failover&lt;/li&gt;
&lt;li&gt;transaction confirmation&lt;/li&gt;
&lt;li&gt;policy versioning&lt;/li&gt;
&lt;li&gt;model versioning&lt;/li&gt;
&lt;li&gt;attestation-aware deployment&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simple worker is enough to demonstrate the architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Never Let a Stale Risk Decision Live Forever
&lt;/h1&gt;

&lt;p&gt;One of the easiest mistakes in automated credit systems is forgetting that risk changes.&lt;/p&gt;

&lt;p&gt;Suppose a borrower receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;creditLimit = $40,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;at 12:00.&lt;/p&gt;

&lt;p&gt;At 12:10, their collateral collapses.&lt;/p&gt;

&lt;p&gt;The previous authorization should not remain valid indefinitely.&lt;/p&gt;

&lt;p&gt;This is why our record contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint64 expiresAt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The consuming application can enforce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    block.timestamp &amp;lt;= record.expiresAt,
    "credit expired"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a temporal trust boundary.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This address is trusted."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;we have:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This address was authorized for this amount until this time."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is substantially safer.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. Model Versioning
&lt;/h1&gt;

&lt;p&gt;Now consider the underwriting model itself.&lt;/p&gt;

&lt;p&gt;Suppose version 1 is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model = credit-v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and version 2 changes the scoring function.&lt;/p&gt;

&lt;p&gt;If the system does not track model versions, historical decisions become difficult to interpret.&lt;/p&gt;

&lt;p&gt;So add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct CreditRecord {
    uint256 limit;
    uint256 score;
    uint64 expiresAt;
    uint64 nonce;
    bytes32 policyHash;
    bytes32 modelHash;
    bool active;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The score remains confidential.&lt;/p&gt;

&lt;p&gt;But the cryptographic identifiers of the policy/model can be retained as provenance metadata.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;modelHash:
0x9f83...

policyHash:
0x72a1...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you an important property:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The implementation can change without making historical authorization impossible to audit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can distinguish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What was decided?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which policy generated that decision?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without necessarily publishing the policy itself.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. A Better Architecture
&lt;/h1&gt;

&lt;p&gt;At this point our architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TB

    BASE[Base]

    RPC[Base RPC / Indexer]

    ROFL[ROFL TEE]

    FEATURES[Feature Extraction]

    MODEL[Private Risk Model]

    POLICY[Private Policy]

    SAPPHIRE[Sapphire]

    CREDIT[Private Credit Controller]

    APP[Merchant / Lending App]

    BASE --&amp;gt; RPC
    RPC --&amp;gt; ROFL

    ROFL --&amp;gt; FEATURES
    FEATURES --&amp;gt; MODEL
    MODEL --&amp;gt; POLICY

    POLICY --&amp;gt;|Decision| SAPPHIRE

    SAPPHIRE --&amp;gt; CREDIT
    CREDIT --&amp;gt; APP&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;There is a subtle but important property here.&lt;/p&gt;

&lt;p&gt;The public blockchain is used as the &lt;strong&gt;source of evidence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is not used as the &lt;strong&gt;storage layer for private underwriting state&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. Confidential Events
&lt;/h1&gt;

&lt;p&gt;Sometimes an application needs event-driven architecture.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event CreditDecision(
    address indexed borrower,
    uint256 limit
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That would leak the limit.&lt;/p&gt;

&lt;p&gt;Instead, Sapphire supports encrypted event payloads while keeping event topics available for indexing and off-chain triggering.&lt;/p&gt;

&lt;p&gt;That lets us design an event such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;topic:
CreditDecision(address)

encrypted payload:
{
    limit,
    score,
    expiresAt,
    modelHash
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An authorized consumer can decrypt the payload.&lt;/p&gt;

&lt;p&gt;An arbitrary blockchain observer cannot simply read the sensitive values from the event.&lt;/p&gt;

&lt;p&gt;This is a useful pattern for systems that still need event-driven infrastructure without turning every event into a public data leak.&lt;/p&gt;




&lt;h1&gt;
  
  
  19. Don't Encrypt Everything
&lt;/h1&gt;

&lt;p&gt;A common privacy-design mistake is assuming:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;More encryption = better architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not necessarily.&lt;/p&gt;

&lt;p&gt;Some data should remain public.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CreditController deployed
ROFL application registered
Policy version changed
Model version changed
Credit authorization revoked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These facts can often be public.&lt;/p&gt;

&lt;p&gt;What should remain confidential are values such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;individual score
risk features
private policy thresholds
exact credit limit
sensitive model parameters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct architecture is therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUBLIC METADATA
        +
CONFIDENTIAL STATE
        +
MINIMAL DISCLOSURE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ENCRYPT EVERYTHING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  20. Authorization Is More Important Than Encryption
&lt;/h1&gt;

&lt;p&gt;Suppose we correctly encrypt the credit record.&lt;/p&gt;

&lt;p&gt;But then we expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getScore(address borrower)
    external
    returns (uint256)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Privacy is gone.&lt;/p&gt;

&lt;p&gt;This is why confidentiality has to be treated as an &lt;strong&gt;application-level authorization problem&lt;/strong&gt;, not just a cryptography problem.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who can write the state?&lt;/li&gt;
&lt;li&gt;Who can read the state?&lt;/li&gt;
&lt;li&gt;Who can cause a decision?&lt;/li&gt;
&lt;li&gt;Who can revoke a decision?&lt;/li&gt;
&lt;li&gt;What can a caller infer from success/failure?&lt;/li&gt;
&lt;li&gt;What information leaks through events?&lt;/li&gt;
&lt;li&gt;What information leaks through timing?&lt;/li&gt;
&lt;li&gt;What information leaks through transaction metadata?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last question is particularly important.&lt;/p&gt;

&lt;p&gt;A confidential contract does not automatically make the surrounding application metadata confidential.&lt;/p&gt;




&lt;h1&gt;
  
  
  21. The Credit Controller
&lt;/h1&gt;

&lt;p&gt;Now suppose a lending application wants to check whether a borrower can draw $10,000.&lt;/p&gt;

&lt;p&gt;It doesn't need the score.&lt;/p&gt;

&lt;p&gt;It needs a decision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function canDraw(
    address borrower,
    uint256 amount
) external view returns (bool) {

    CreditRecord memory record =
        records[borrower];

    if (!record.active) {
        return false;
    }

    if (block.timestamp &amp;gt; record.expiresAt) {
        return false;
    }

    return amount &amp;lt;= record.limit;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can then execute the action.&lt;/p&gt;

&lt;p&gt;This pattern is called &lt;strong&gt;capability-oriented disclosure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of revealing the entire internal state, you reveal only whether a particular operation is authorized.&lt;/p&gt;

&lt;p&gt;That is a much better primitive for privacy-sensitive financial applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  22. What Happens When the User Is Rejected?
&lt;/h1&gt;

&lt;p&gt;Do not assume that returning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;score = 694
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is harmless.&lt;/p&gt;

&lt;p&gt;A user can repeatedly modify their behavior and probe the model.&lt;/p&gt;

&lt;p&gt;If every request reveals a numerical score, they can potentially infer the decision boundary.&lt;/p&gt;

&lt;p&gt;A better interface is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eligible = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;possibly combined with a coarse reason category:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSUFFICIENT_COLLATERAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TEMPORARILY_INELIGIBLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even these should be designed carefully.&lt;/p&gt;

&lt;p&gt;The goal is to minimize the information returned by the underwriting oracle.&lt;/p&gt;




&lt;h1&gt;
  
  
  23. Replay Protection
&lt;/h1&gt;

&lt;p&gt;ROFL may process the same borrower more than once.&lt;/p&gt;

&lt;p&gt;Transactions can be retried.&lt;/p&gt;

&lt;p&gt;RPC responses can be delayed.&lt;/p&gt;

&lt;p&gt;Workers can restart.&lt;/p&gt;

&lt;p&gt;Therefore, the credit contract needs a monotonic nonce.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    nonce &amp;gt; records[borrower].nonce,
    "stale decision"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now an old underwriting result cannot overwrite a newer one.&lt;/p&gt;

&lt;p&gt;A better structure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct CreditDecision {
    uint256 limit;
    uint64 expiresAt;
    uint64 nonce;
    bytes32 modelHash;
    bytes32 policyHash;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and require:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new nonce &amp;gt; stored nonce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns the authorization state into a monotonic state machine.&lt;/p&gt;




&lt;h1&gt;
  
  
  24. Failure Handling
&lt;/h1&gt;

&lt;p&gt;Now imagine the Base RPC provider goes offline.&lt;/p&gt;

&lt;p&gt;Should credit applications immediately fail?&lt;/p&gt;

&lt;p&gt;Not necessarily.&lt;/p&gt;

&lt;p&gt;The ROFL application should distinguish between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NO DATA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOW RISK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are completely different states.&lt;/p&gt;

&lt;p&gt;A safe policy is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC unavailable
    ↓
no new underwriting decision
    ↓
existing authorization remains valid
until expiry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC unavailable
    ↓
grant maximum credit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an example of &lt;strong&gt;fail-closed financial automation&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  25. The Full Lifecycle
&lt;/h1&gt;

&lt;p&gt;The final system now behaves like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram

    participant User
    participant Base
    participant ROFL
    participant Sapphire
    participant CreditApp

    User-&amp;gt;&amp;gt;Base: Deposit collateral

    ROFL-&amp;gt;&amp;gt;Base: Read collateral/activity

    Base--&amp;gt;&amp;gt;ROFL: Public state

    ROFL-&amp;gt;&amp;gt;ROFL: Extract features

    ROFL-&amp;gt;&amp;gt;ROFL: Run private model

    ROFL-&amp;gt;&amp;gt;ROFL: Evaluate private policy

    ROFL-&amp;gt;&amp;gt;Sapphire: Submit authorization

    Sapphire-&amp;gt;&amp;gt;Sapphire: Verify ROFL origin

    Sapphire-&amp;gt;&amp;gt;Sapphire: Store confidential decision

    CreditApp-&amp;gt;&amp;gt;Sapphire: Can user draw X?

    Sapphire--&amp;gt;&amp;gt;CreditApp: true / false

    CreditApp-&amp;gt;&amp;gt;User: Execute permitted action&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Notice how little information crosses the boundary.&lt;/p&gt;

&lt;p&gt;Base provides evidence.&lt;/p&gt;

&lt;p&gt;ROFL produces a decision.&lt;/p&gt;

&lt;p&gt;Sapphire enforces authorization.&lt;/p&gt;

&lt;p&gt;The application receives only what it needs.&lt;/p&gt;




&lt;h1&gt;
  
  
  26. Testing the System
&lt;/h1&gt;

&lt;p&gt;For the Solidity layer, Foundry is a good fit.&lt;/p&gt;

&lt;p&gt;A basic test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testCreditExpires() public {
    vm.warp(block.timestamp + 2 hours);

    vm.expectRevert("expired");

    credit.getCreditLimit(borrower);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test stale updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsStaleDecision() public {
    credit.updateCredit(
        borrower,
        800,
        40000,
        uint64(block.timestamp + 1 days),
        10
    );

    vm.expectRevert("stale decision");

    credit.updateCredit(
        borrower,
        900,
        50000,
        uint64(block.timestamp + 1 days),
        9
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test authorization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testOnlyROFLCanUpdate() public {
    vm.prank(attacker);

    vm.expectRevert("not authorized");

    credit.updateCredit(
        borrower,
        900,
        50000,
        uint64(block.timestamp + 1 days)
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests should exist before the ROFL worker is connected.&lt;/p&gt;




&lt;h1&gt;
  
  
  27. Testing the Risk Engine Independently
&lt;/h1&gt;

&lt;p&gt;Do not make the TEE the only place where the model can be tested.&lt;/p&gt;

&lt;p&gt;The scoring function should have deterministic unit tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credit scoring&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rewards repayment history&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateScore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;walletAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;collateral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;utilization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;liquidations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;repayment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;volatility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want three separate testing layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model tests
     +
contract tests
     +
integration tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise a failure becomes difficult to localize.&lt;/p&gt;




&lt;h1&gt;
  
  
  28. What If the ROFL Application Is Compromised?
&lt;/h1&gt;

&lt;p&gt;This is where threat modeling becomes more interesting.&lt;/p&gt;

&lt;p&gt;You should not assume:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"ROFL means the application can never be wrong."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, design defense in depth.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ROFL
  ↓
credit limit
  ↓
Sapphire policy guard
  ↓
maximum protocol-wide limit
  ↓
application-level collateral check
  ↓
execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose ROFL accidentally returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$900,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Sapphire contract could enforce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    limit &amp;lt;= protocolMaximum,
    "limit exceeds protocol cap"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us two independent layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;off-chain underwriting
+
on-chain safety bounds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much stronger than putting every security assumption into one component.&lt;/p&gt;




&lt;h1&gt;
  
  
  29. Private Policy, Public Bounds
&lt;/h1&gt;

&lt;p&gt;This leads to an especially useful pattern.&lt;/p&gt;

&lt;p&gt;The detailed policy can remain confidential:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private model
private thresholds
private weights
private features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while broad safety bounds remain public:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maximum credit:
$50,000

maximum authorization:
24 hours

maximum utilization:
80%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now users can understand the outer safety envelope without learning the exact underwriting algorithm.&lt;/p&gt;

&lt;p&gt;This is often the right compromise between privacy and auditability.&lt;/p&gt;




&lt;h1&gt;
  
  
  30. What Can Be Publicly Audited?
&lt;/h1&gt;

&lt;p&gt;A confidential application still needs auditability.&lt;/p&gt;

&lt;p&gt;We can publish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract addresses
ROFL application identifier
model hash
policy hash
deployment version
authorization expiry
protocol-wide limits
upgrade events
revocation events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't need to publish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;every borrower score
private features
exact risk weights
individual underwriting inputs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a useful distinction:&lt;/p&gt;

&lt;h3&gt;
  
  
  Public integrity
&lt;/h3&gt;

&lt;p&gt;People can verify:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which version is running?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Private computation
&lt;/h3&gt;

&lt;p&gt;People cannot necessarily see:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What was the user's exact internal score?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Public safety envelope
&lt;/h3&gt;

&lt;p&gt;People can still verify:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the maximum authority the system can grant?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much more nuanced security model than simply calling the application "private."&lt;/p&gt;




&lt;h1&gt;
  
  
  31. Adding an Audit Manifest
&lt;/h1&gt;

&lt;p&gt;I would also put a machine-readable manifest in the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"private-credit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"credit-v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"policy-v3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maximumCreditUsd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maximumAuthorizationSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"execution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Oasis Sapphire"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Oasis ROFL"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manifest doesn't expose the policy.&lt;/p&gt;

&lt;p&gt;It identifies it.&lt;/p&gt;

&lt;p&gt;That gives you reproducibility without sacrificing confidential parameters.&lt;/p&gt;




&lt;h1&gt;
  
  
  32. Why This Is More Interesting Than a Private Database
&lt;/h1&gt;

&lt;p&gt;A private database can hide information.&lt;/p&gt;

&lt;p&gt;But it does not necessarily establish a decentralized authorization path.&lt;/p&gt;

&lt;p&gt;Our design instead creates a chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public evidence
      ↓
confidential computation
      ↓
confidential authorization
      ↓
bounded execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the interesting primitive.&lt;/p&gt;

&lt;p&gt;The blockchain remains useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;settlement&lt;/li&gt;
&lt;li&gt;collateral&lt;/li&gt;
&lt;li&gt;ownership&lt;/li&gt;
&lt;li&gt;authorization&lt;/li&gt;
&lt;li&gt;audit trails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while confidential infrastructure handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private feature extraction&lt;/li&gt;
&lt;li&gt;sensitive models&lt;/li&gt;
&lt;li&gt;proprietary policy&lt;/li&gt;
&lt;li&gt;individual risk state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly the kind of workload for which combining Sapphire and ROFL becomes compelling.&lt;/p&gt;




&lt;h1&gt;
  
  
  33. Where This Architecture Could Be Used
&lt;/h1&gt;

&lt;p&gt;Although we built a credit engine, the same architecture can support other systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private insurance underwriting
&lt;/h3&gt;

&lt;p&gt;Public collateral and claims data can feed a confidential underwriting engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Institutional DeFi
&lt;/h3&gt;

&lt;p&gt;An institution can receive a private risk classification without publishing its entire portfolio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merchant financing
&lt;/h3&gt;

&lt;p&gt;A merchant's transaction history can influence a credit line without exposing the complete scoring model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private derivatives
&lt;/h3&gt;

&lt;p&gt;Risk limits can remain confidential while settlement remains on-chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  DAO treasury controls
&lt;/h3&gt;

&lt;p&gt;A confidential policy engine could evaluate risk before authorizing certain treasury actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-chain credit
&lt;/h3&gt;

&lt;p&gt;ROFL can collect evidence from multiple chains and aggregate it before producing a Sapphire authorization.&lt;/p&gt;

&lt;p&gt;The last one is particularly interesting.&lt;/p&gt;

&lt;p&gt;The public blockchains become data sources.&lt;/p&gt;

&lt;p&gt;Sapphire becomes the confidential policy layer.&lt;/p&gt;

&lt;p&gt;ROFL becomes the computation and connectivity layer.&lt;/p&gt;




&lt;h1&gt;
  
  
  34. Extending It to Multiple Chains
&lt;/h1&gt;

&lt;p&gt;Suppose the borrower has assets on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethereum
Base
Arbitrum
Optimism
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ROFL can collect the relevant state from each network.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR

    ETH[Ethereum]
    BASE[Base]
    ARB[Arbitrum]
    OP[Optimism]

    ROFL[ROFL Risk Engine]

    ETH --&amp;gt; ROFL
    BASE --&amp;gt; ROFL
    ARB --&amp;gt; ROFL
    OP --&amp;gt; ROFL

    ROFL --&amp;gt; S[Sapphire]

    S --&amp;gt; CREDIT[Private Credit Controller]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Now the user does not need to manually consolidate the information.&lt;/p&gt;

&lt;p&gt;The risk engine sees the portfolio as a whole.&lt;/p&gt;

&lt;p&gt;Again, this doesn't make those chains private.&lt;/p&gt;

&lt;p&gt;It means the &lt;strong&gt;aggregation and derived risk state&lt;/strong&gt; can remain confidential.&lt;/p&gt;




&lt;h1&gt;
  
  
  35. The Important Trust Assumption
&lt;/h1&gt;

&lt;p&gt;There is no magic here.&lt;/p&gt;

&lt;p&gt;A responsible implementation must clearly document its assumptions.&lt;/p&gt;

&lt;p&gt;Our confidentiality depends on the security properties of the trusted execution environment and the Oasis infrastructure supporting ROFL/Sapphire.&lt;/p&gt;

&lt;p&gt;The application also has to ensure that its deployed binaries, configuration, key handling, authorization logic, and update process are correct.&lt;/p&gt;

&lt;p&gt;ROFL's current architecture explicitly uses TEEs, remote attestation, application registration, and Oasis consensus as parts of this trust model.&lt;/p&gt;

&lt;p&gt;Therefore, the correct claim is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Nobody can ever know this information."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better claim is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The protocol is designed so that sensitive computation and state are processed inside the confidential execution environment, subject to the documented TEE and protocol trust assumptions."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the statement I would put in the README.&lt;/p&gt;




&lt;h1&gt;
  
  
  36. Production Hardening Checklist
&lt;/h1&gt;

&lt;p&gt;Before calling this production-ready, I would add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Sapphire-specific access-control primitives&lt;/li&gt;
&lt;li&gt;[ ] ROFL application authentication&lt;/li&gt;
&lt;li&gt;[ ] nonce/replay protection&lt;/li&gt;
&lt;li&gt;[ ] authorization expiry&lt;/li&gt;
&lt;li&gt;[ ] model versioning&lt;/li&gt;
&lt;li&gt;[ ] policy versioning&lt;/li&gt;
&lt;li&gt;[ ] policy hashes&lt;/li&gt;
&lt;li&gt;[ ] maximum protocol-wide credit limits&lt;/li&gt;
&lt;li&gt;[ ] stale-data detection&lt;/li&gt;
&lt;li&gt;[ ] RPC failover&lt;/li&gt;
&lt;li&gt;[ ] oracle freshness checks&lt;/li&gt;
&lt;li&gt;[ ] deterministic model tests&lt;/li&gt;
&lt;li&gt;[ ] Foundry contract tests&lt;/li&gt;
&lt;li&gt;[ ] integration tests&lt;/li&gt;
&lt;li&gt;[ ] deployment manifest&lt;/li&gt;
&lt;li&gt;[ ] upgrade controls&lt;/li&gt;
&lt;li&gt;[ ] emergency revocation&lt;/li&gt;
&lt;li&gt;[ ] monitoring&lt;/li&gt;
&lt;li&gt;[ ] attestation verification&lt;/li&gt;
&lt;li&gt;[ ] key rotation procedure&lt;/li&gt;
&lt;li&gt;[ ] incident-response procedure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important lesson is that confidentiality does not eliminate ordinary smart-contract security.&lt;/p&gt;

&lt;p&gt;You still need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;access control
+
replay protection
+
bounds
+
expiry
+
monitoring
+
upgrade discipline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Privacy is another security property, not a replacement for the others.&lt;/p&gt;




&lt;h1&gt;
  
  
  37. The Bigger Design Pattern
&lt;/h1&gt;

&lt;p&gt;The most useful takeaway from this project isn't the credit score.&lt;/p&gt;

&lt;p&gt;It's the architecture.&lt;/p&gt;

&lt;p&gt;We can think of a modern decentralized application as three different layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌───────────────────────┐
                 │      PUBLIC WORLD     │
                 │                       │
                 │ Ethereum / Base / L2s │
                 │ settlement / assets   │
                 └───────────┬───────────┘
                             │
                             ▼
                 ┌───────────────────────┐
                 │   CONFIDENT COMPUTE   │
                 │                       │
                 │       ROFL TEE        │
                 │ private algorithms    │
                 │ external data         │
                 │ sensitive processing  │
                 └───────────┬───────────┘
                             │
                             ▼
                 ┌───────────────────────┐
                 │ CONFIDENT ON-CHAIN    │
                 │                       │
                 │       Sapphire       │
                 │ private state        │
                 │ authorization         │
                 │ policy enforcement    │
                 └───────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mistake is trying to force all three jobs into the same environment.&lt;/p&gt;

&lt;p&gt;Public blockchains are excellent at settlement and transparent state.&lt;/p&gt;

&lt;p&gt;Confidential runtimes are useful when state should not be globally observable.&lt;/p&gt;

&lt;p&gt;TEE-backed off-chain computation is useful when workloads are too expensive, complex, or externally connected to execute directly on-chain.&lt;/p&gt;

&lt;p&gt;ROFL and Sapphire allow those pieces to be composed.&lt;/p&gt;




&lt;h1&gt;
  
  
  38. Final Thoughts
&lt;/h1&gt;

&lt;p&gt;The most interesting privacy applications aren't necessarily applications where everything is hidden.&lt;/p&gt;

&lt;p&gt;They are applications where &lt;strong&gt;the confidentiality boundary is deliberate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For our credit engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Collateral:
PUBLIC

Transactions:
PUBLIC

Settlement:
PUBLIC

Risk features:
CONFIDENTIAL

Underwriting model:
CONFIDENTIAL

Policy parameters:
CONFIDENTIAL

Credit decision:
CONFIDENTIAL

Safety limits:
PUBLIC

Model identity:
PUBLIC

Authorization status:
MINIMALLY DISCLOSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a much more useful way to think about confidential blockchain applications.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can we put this entire application on a private chain?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which information actually needs confidentiality?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Where should that information be processed?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And finally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What is the smallest result that needs to cross the confidentiality boundary?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this architecture, Base provides the public evidence layer.&lt;/p&gt;

&lt;p&gt;ROFL performs the expensive and sensitive underwriting computation.&lt;/p&gt;

&lt;p&gt;Sapphire provides confidential state and policy enforcement.&lt;/p&gt;

&lt;p&gt;The resulting system doesn't try to make public blockchains disappear.&lt;/p&gt;

&lt;p&gt;It makes them more useful by adding a confidential computation layer where transparency would otherwise become a liability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository Blueprint
&lt;/h2&gt;

&lt;p&gt;The complete project can follow this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private-credit/
├── contracts/
│   ├── PrivateCredit.sol
│   ├── CreditController.sol
│   ├── PolicyRegistry.sol
│   └── interfaces/
│       └── IPrivateCredit.sol
│
├── rofl/
│   ├── src/
│   │   ├── index.ts
│   │   ├── base.ts
│   │   ├── features.ts
│   │   ├── scoring.ts
│   │   └── sapphire.ts
│   ├── Dockerfile
│   └── package.json
│
├── scripts/
│   ├── deploy.ts
│   ├── seed.ts
│   └── request-underwriting.ts
│
├── test/
│   ├── PrivateCredit.t.sol
│   ├── CreditController.t.sol
│   └── integration.t.sol
│
├── frontend/
│
├── manifest/
│   └── protocol.json
│
├── foundry.toml
├── hardhat.config.ts
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official Oasis documentation provides the current Sapphire development documentation, ROFL workflow, and examples for integrating ROFL applications with confidential Sapphire contracts.&lt;/p&gt;

&lt;p&gt;For developers wanting to experiment with the pattern, the official ROFL documentation also includes a trustless price-oracle example that demonstrates a ROFL application communicating with a confidential Sapphire contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Oasis Sapphire&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/sapphire/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Develop on Sapphire&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/sapphire/develop/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oasis ROFL&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/rofl/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/rofl/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ROFL Workflow and Architecture&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/rofl/workflow/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/rofl/workflow/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Encrypted Events&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/sapphire/develop/encrypted-events/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/sapphire/develop/encrypted-events/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oasis Build Documentation&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The official documentation is the best place to verify the current APIs and deployment workflow because the Oasis tooling continues to evolve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The core idea is simple:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't make the blockchain private.&lt;/p&gt;

&lt;p&gt;Make the parts that shouldn't be public confidential and make the boundary between public evidence and private computation explicit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where confidential Web3 infrastructure becomes much more interesting than simply hiding a smart contract's storage.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cryptocurrency</category>
      <category>blockchain</category>
      <category>confidentiality</category>
    </item>
    <item>
      <title>Building a Confidential Onchain Payroll &amp; Contractor Escrow with Oasis Sapphire + ROFL</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 07:01:37 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-onchain-payroll-contractor-escrow-with-oasis-sapphire-rofl-1d0l</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-onchain-payroll-contractor-escrow-with-oasis-sapphire-rofl-1d0l</guid>
      <description>&lt;p&gt;Traditional blockchain payroll has an uncomfortable property.&lt;/p&gt;

&lt;p&gt;The payment itself may be decentralized and programmable, but the information surrounding that payment is usually public.&lt;/p&gt;

&lt;p&gt;If a company pays a contractor 4 ETH every month, an observer can potentially see the payment amount, timing, destination address, and relationship between recurring transactions. If the company has multiple employees or contractors, these transactions can reveal a surprisingly detailed picture of its internal operations.&lt;/p&gt;

&lt;p&gt;That creates a problem for organizations that want the advantages of programmable settlement without turning their payroll system into a public business-intelligence feed.&lt;/p&gt;

&lt;p&gt;Consider a simple payroll transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Company Treasury
      |
      | 4 ETH
      v
0x91...A42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a public EVM chain, there is no inherent distinction between:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"The company paid an employee."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"The company transferred 4 ETH to this address."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The blockchain can verify the second statement.&lt;/p&gt;

&lt;p&gt;It does not understand the first.&lt;/p&gt;

&lt;p&gt;This article explores a different architecture using Oasis Sapphire and Oasis ROFL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    PUBLIC
              ┌─────────────────┐
              │ Company Treasury│
              │ Funding         │
              └────────┬────────┘
                       │
                       │ funds
                       ▼
              ┌─────────────────┐
              │ Sapphire        │
              │ Escrow          │
              │                 │
              │ PRIVATE         │
              │ salaries        │
              │ schedules       │
              │ recipients      │
              │ policies        │
              └────────┬────────┘
                       ▲
                       │ attested
                       │ payroll update
              ┌────────┴────────┐
              │ ROFL            │
              │                 │
              │ payroll adapter │
              │ validation      │
              │ computation     │
              └────────┬────────┘
                       ▲
                       │
              ┌────────┴────────┐
              │ Payroll system  │
              │ / ERP / HRIS    │
              └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea is simple:&lt;/p&gt;

&lt;p&gt;Put settlement rules onchain without putting the entire payroll database onchain.&lt;/p&gt;

&lt;p&gt;Oasis Sapphire is an EVM-compatible confidential runtime that supports confidential contract state and encrypted transactions.&lt;/p&gt;

&lt;p&gt;ROFL extends that model to applications running in trusted execution environments, allowing offchain workloads to interact with Sapphire while maintaining an attested application identity.&lt;/p&gt;

&lt;p&gt;This combination gives us a useful architecture for a class of applications that doesn't get much attention in Web3:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;private business processes with programmable settlement.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;Our example system is a confidential contractor payroll protocol.&lt;/p&gt;

&lt;p&gt;A company wants to hire 50 independent contractors.&lt;/p&gt;

&lt;p&gt;Each contractor has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a wallet&lt;/li&gt;
&lt;li&gt;a compensation rate&lt;/li&gt;
&lt;li&gt;a payment schedule&lt;/li&gt;
&lt;li&gt;an engagement start/end date&lt;/li&gt;
&lt;li&gt;an optional performance bonus&lt;/li&gt;
&lt;li&gt;a private internal identifier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The company wants the actual settlement to be programmable.&lt;/p&gt;

&lt;p&gt;But it does not want the public blockchain to reveal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alice → $14,000/month
Bob   → $8,500/month
Carol → $31,000/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, the company funds an onchain escrow.&lt;/p&gt;

&lt;p&gt;The confidential contract maintains the payroll state.&lt;/p&gt;

&lt;p&gt;A ROFL application receives an authorized payroll update from the organization's existing backend and submits the corresponding state transition to Sapphire.&lt;/p&gt;

&lt;p&gt;The final architecture looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft7smidyc9ildcmfvd2cl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft7smidyc9ildcmfvd2cl.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not intended to replace an enterprise HR system.&lt;/p&gt;

&lt;p&gt;The blockchain is being used for something much narrower:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;confidential, programmable settlement.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use a Database?
&lt;/h2&gt;

&lt;p&gt;A reasonable objection is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why put payroll information on a blockchain at all?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For many organizations, a database is completely sufficient.&lt;/p&gt;

&lt;p&gt;The interesting case is when multiple parties need guarantees around settlement.&lt;/p&gt;

&lt;p&gt;Imagine a company using an external payroll processor.&lt;/p&gt;

&lt;p&gt;The company might want the processor to calculate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contractor compensation
+
approved expenses
+
performance bonuses
-
withholding
=
final settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the company doesn't necessarily want the processor to have unrestricted authority over the funds.&lt;/p&gt;

&lt;p&gt;Likewise, a contractor may want stronger guarantees that an approved payment cannot simply disappear because an internal database was modified.&lt;/p&gt;

&lt;p&gt;This produces a separation of responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payroll system
    |
    | calculates
    v
ROFL
    |
    | attested update
    v
Sapphire
    |
    | enforces settlement policy
    v
Funds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payroll backend determines what should happen.&lt;/p&gt;

&lt;p&gt;The confidential smart contract determines what is allowed to happen.&lt;/p&gt;

&lt;p&gt;That distinction is one of the most important architectural decisions in this design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Sapphire?
&lt;/h2&gt;

&lt;p&gt;Sapphire is particularly useful here because the application needs confidentiality at the smart-contract state level.&lt;/p&gt;

&lt;p&gt;A normal EVM contract would expose storage such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(address =&amp;gt; uint256) salary;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if the Solidity source doesn't provide a public getter, public blockchain state is generally observable through node/RPC interfaces and historical state.&lt;/p&gt;

&lt;p&gt;Sapphire provides confidential contract state and encrypted communication between users and contracts.&lt;/p&gt;

&lt;p&gt;That means we can maintain sensitive application state inside the confidential execution environment rather than treating the public blockchain as a payroll database.&lt;/p&gt;

&lt;p&gt;The contract can therefore maintain records such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contractor wallet&lt;/li&gt;
&lt;li&gt;salary&lt;/li&gt;
&lt;li&gt;payment interval&lt;/li&gt;
&lt;li&gt;start date&lt;/li&gt;
&lt;li&gt;end date&lt;/li&gt;
&lt;li&gt;bonus policy&lt;/li&gt;
&lt;li&gt;last settlement&lt;/li&gt;
&lt;li&gt;employment status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without automatically publishing those values as ordinary public application state.&lt;/p&gt;

&lt;p&gt;Repository Structure&lt;/p&gt;

&lt;p&gt;For the implementation, I'd structure the repository like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confidential-payroll/
│
├── contracts/
│   ├── ConfidentialPayroll.sol
│   ├── PayrollEscrow.sol
│   ├── PayrollTypes.sol
│   └── interfaces/
│       └── IPayrollAdapter.sol
│
├── rofl/
│   ├── src/
│   │   ├── main.ts
│   │   ├── payroll.ts
│   │   ├── attestation.ts
│   │   └── validation.ts
│   ├── Dockerfile
│   └── rofl.yaml
│
├── scripts/
│   ├── deploy.ts
│   ├── fund.ts
│   └── register-rofl.ts
│
├── test/
│   ├── Payroll.t.sol
│   ├── Authorization.t.sol
│   ├── Settlement.t.sol
│   └── FuzzPayroll.t.sol
│
├── frontend/
│   └── ...
│
├── docs/
│   ├── architecture.md
│   └── threat-model.md
│
├── foundry.toml
├── package.json
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is that the ROFL application is not treated as a second smart contract.&lt;/p&gt;

&lt;p&gt;It is an external confidential component with a specific authorization relationship to the Sapphire contract.&lt;/p&gt;

&lt;p&gt;That gives us a much cleaner trust boundary.&lt;/p&gt;

&lt;p&gt;The Core Data Model&lt;/p&gt;

&lt;p&gt;Let's start with the internal payroll record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Contractor {
    address wallet;
    uint64 startTime;
    uint64 endTime;
    uint128 monthlyRate;
    uint128 accrued;
    uint128 bonus;
    bool active;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production implementation would likely use a more sophisticated accounting model, but this is enough to demonstrate the architecture.&lt;/p&gt;

&lt;p&gt;The mapping is deliberately private:&lt;/p&gt;

&lt;p&gt;mapping(bytes32 =&amp;gt; Contractor) private contractors;&lt;/p&gt;

&lt;p&gt;Instead of using the employee's wallet as the primary identifier, we can derive a confidential internal identifier.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contractorId = keccak256(
    companyId ||
    internalEmployeeId ||
    deploymentSalt
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't meant to be a magical privacy mechanism.&lt;/p&gt;

&lt;p&gt;Hashing does not make low-entropy information confidential.&lt;/p&gt;

&lt;p&gt;If the possible employee IDs are predictable, someone can brute-force them.&lt;/p&gt;

&lt;p&gt;The purpose of the identifier is instead to separate the organization's internal record structure from its public wallet addresses.&lt;/p&gt;

&lt;p&gt;The actual confidentiality comes from Sapphire's confidential execution model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payroll Accrual
&lt;/h2&gt;

&lt;p&gt;The system needs to calculate how much a contractor has earned since their last settlement.&lt;/p&gt;

&lt;p&gt;A simplified formula is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;elapsed = currentTime - lastSettlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;earned =
    monthlyRate
    × elapsed
    ÷ billingPeriod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;monthlyRate = $10,000&lt;br&gt;
billingPeriod = 30 days&lt;br&gt;
elapsed = 15 days&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;earned = $5,000&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In a real implementation, you should avoid floating point arithmetic entirely.&lt;/p&gt;

&lt;p&gt;Use fixed-point integer arithmetic and explicitly define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rounding direction&lt;/li&gt;
&lt;li&gt;minimum settlement&lt;/li&gt;
&lt;li&gt;maximum accrual&lt;/li&gt;
&lt;li&gt;timestamp boundaries&lt;/li&gt;
&lt;li&gt;partial periods&lt;/li&gt;
&lt;li&gt;leap-month handling&lt;/li&gt;
&lt;li&gt;token decimal conversion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Financial contracts should never leave rounding behavior implicit.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Confidential Payroll Contract
&lt;/h2&gt;

&lt;p&gt;The high-level contract can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;


contract ConfidentialPayroll {
    struct Contractor {
        address wallet;
        uint64 startTime;
        uint64 endTime;
        uint128 ratePerPeriod;
        uint128 accrued;
        uint64 lastSettlement;
        bool active;
    }


    address public immutable treasury;
    address public immutable roflApp;


    mapping(bytes32 =&amp;gt; Contractor) private contractors;


    error Unauthorized();
    error Inactive();
    error InvalidSchedule();
    error NothingDue();


    constructor(
        address _treasury,
        address _roflApp
    ) {
        treasury = _treasury;
        roflApp = _roflApp;
    }


    modifier onlyROFL() {
        if (msg.sender != roflApp) {
            revert Unauthorized();
        }
        _;
    }


    function createContractor(
        bytes32 id,
        address wallet,
        uint64 startTime,
        uint64 endTime,
        uint128 rate
    ) external onlyROFL {


        if (endTime &amp;lt;= startTime) {
            revert InvalidSchedule();
        }


        contractors[id] = Contractor({
            wallet: wallet,
            startTime: startTime,
            endTime: endTime,
            ratePerPeriod: rate,
            accrued: 0,
            lastSettlement: startTime,
            active: true
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is already an important property here:&lt;/p&gt;

&lt;p&gt;The normal wallet cannot create arbitrary payroll records.&lt;/p&gt;

&lt;p&gt;Only the authorized payroll application can modify the internal state.&lt;/p&gt;

&lt;p&gt;In production, the onlyROFL mechanism should use Oasis's supported ROFL authentication/verification pattern rather than treating a normal externally owned account as a sufficient trust boundary. ROFL applications can authenticate to Sapphire contracts, and Sapphire can verify ROFL transaction origin.&lt;/p&gt;

&lt;p&gt;Settlement Should Be Separate From Accrual&lt;/p&gt;

&lt;p&gt;One mistake would be to combine everything into a single function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calculate salary
+
modify employee
+
authorize payment
+
transfer funds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates a large security surface.&lt;/p&gt;

&lt;p&gt;Instead, separate the lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REGISTER
   ↓
ACTIVE
   ↓
ACCRUING
   ↓
SETTLEMENT READY
   ↓
PAID
   ↓
NEXT PERIOD

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us much clearer invariants.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A contractor cannot be paid more than their accrued amount.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An inactive contractor cannot generate additional compensation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A settlement cannot be executed twice for the same accounting period.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those invariants are much easier to fuzz-test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Settlement Function
&lt;/h2&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function settle(bytes32 id)
    external
    onlyROFL
{
    Contractor storage c = contractors[id];


    if (!c.active) {
        revert Inactive();
    }


    uint256 due = _calculateDue(c);


    if (due == 0) {
        revert NothingDue();
    }


    c.lastSettlement = uint64(block.timestamp);


    _transfer(c.wallet, due);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important security property isn't the transfer itself.&lt;/p&gt;

&lt;p&gt;It is the accounting relationship around it.&lt;/p&gt;

&lt;p&gt;We want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new accrued balance
=
old accrued balance
+
newly earned
-
settled amount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to remain true after every state transition.&lt;/p&gt;

&lt;p&gt;That becomes the foundation of the test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Make the Payroll Adapter the Custodian
&lt;/h2&gt;

&lt;p&gt;This is probably the most important design decision in the entire system.&lt;/p&gt;

&lt;p&gt;A tempting architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payroll backend
       ↓
ROFL
       ↓
Treasury private key
       ↓
Contractor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives ROFL too much power.&lt;/p&gt;

&lt;p&gt;If the ROFL application is compromised, the attacker potentially gets direct control over funds.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payroll backend
       ↓
ROFL
       ↓
Sapphire policy
       ↓
Escrow
       ↓
Contractor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ROFL should be able to submit claims, not arbitrarily withdraw money.&lt;/p&gt;

&lt;p&gt;The confidential contract should enforce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claim &amp;lt;= accrued
claim &amp;lt;= configured limit
claim belongs to contractor
contractor is active
period hasn't already been settled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is classic least-privilege design.&lt;/p&gt;

&lt;p&gt;The confidential execution environment is not an excuse to give one component unlimited authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Funding the Escrow
&lt;/h2&gt;

&lt;p&gt;The company can fund the payroll contract separately.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Treasury
   |
   | 100,000 USDC
   v
Payroll Escrow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public blockchain can therefore show:&lt;/p&gt;

&lt;p&gt;Payroll escrow balance = 100,000 USDC&lt;/p&gt;

&lt;p&gt;while the internal distribution remains confidential.&lt;/p&gt;

&lt;p&gt;That is a useful compromise.&lt;/p&gt;

&lt;p&gt;The company may want the total reserve to be publicly auditable while keeping the individual salary distribution private.&lt;/p&gt;

&lt;p&gt;This is an important distinction:&lt;/p&gt;

&lt;p&gt;Confidentiality does not require making everything opaque.&lt;/p&gt;

&lt;p&gt;A well-designed confidential application should expose exactly the information that external parties need to verify its public commitments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public vs Private State
&lt;/h2&gt;

&lt;p&gt;I'd explicitly document this in the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUBLIC
────────────────────────────
Contract address
Contract version
Escrow funding
ROFL application identity
Protocol configuration
Settlement existence


PRIVATE
────────────────────────────
Contractor identity
Compensation rate
Accrued amount
Payment schedule
Internal employee ID
Bonus information
Employment metadata


SELECTIVELY DISCLOSED
────────────────────────────
Payment receipt
Settlement proof
Accounting period
Aggregated payroll statistics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much more useful than simply saying:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"The contract is private."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Privacy should be defined field-by-field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confidential Events
&lt;/h2&gt;

&lt;p&gt;Events require particular care.&lt;/p&gt;

&lt;p&gt;On ordinary EVM networks, event data is publicly visible.&lt;/p&gt;

&lt;p&gt;Sapphire supports encrypted event payloads while retaining public event topics for indexing and offchain triggers.&lt;/p&gt;

&lt;p&gt;That gives us an interesting pattern.&lt;/p&gt;

&lt;p&gt;Suppose we want a contractor to receive a payment notification containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;paymentId&lt;/li&gt;
&lt;li&gt;period&lt;/li&gt;
&lt;li&gt;amount&lt;/li&gt;
&lt;li&gt;timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We don't necessarily want all of those fields to become public plaintext.&lt;/p&gt;

&lt;p&gt;Instead, the event can expose a public topic while keeping the sensitive payload encrypted for an authorized consumer.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;Public:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PayrollSettlement(contractorIdHash, paymentId)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Encrypted:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;amount&lt;br&gt;
period&lt;br&gt;
internal payroll metadata&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This gives infrastructure systems something to index without turning the event stream into a public payroll ledger.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where ROFL Becomes Useful
&lt;/h2&gt;

&lt;p&gt;The real world rarely stores payroll data directly onchain.&lt;/p&gt;

&lt;p&gt;An organization might already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workday&lt;/li&gt;
&lt;li&gt;SAP&lt;/li&gt;
&lt;li&gt;QuickBooks&lt;/li&gt;
&lt;li&gt;Stripe&lt;/li&gt;
&lt;li&gt;custom HR system&lt;/li&gt;
&lt;li&gt;internal PostgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We don't want the smart contract trying to query these systems directly.&lt;/p&gt;

&lt;p&gt;Instead, ROFL can operate as the confidential adapter.&lt;/p&gt;

&lt;p&gt;ROFL applications run inside TEEs and can interact with external systems through authenticated connections while maintaining an attested application identity.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of workload that benefits from separating:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;external data ingestion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;from:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;onchain settlement policy.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Example ROFL Workflow
&lt;/h2&gt;

&lt;p&gt;Suppose the company's payroll system produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employee"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"employee-9182"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approvedAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8500000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USDC"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ROFL shouldn't blindly forward this.&lt;/p&gt;

&lt;p&gt;It should validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this payroll period valid?&lt;/li&gt;
&lt;li&gt;Is the employee registered?&lt;/li&gt;
&lt;li&gt;Is the amount within policy?&lt;/li&gt;
&lt;li&gt;Is this period already settled?&lt;/li&gt;
&lt;li&gt;Is the payroll file authentic?&lt;/li&gt;
&lt;li&gt;Is the source system authorized?&lt;/li&gt;
&lt;li&gt;Is the deployment version expected?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only after those checks should the ROFL application construct the Sapphire transaction.&lt;/p&gt;

&lt;p&gt;Pseudo-code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processPayroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PayrollRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;validateSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;employee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;payroll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inactive employee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;verifyPayrollSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid payroll source&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateSettlement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sapphire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitSettlement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;contractorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privateId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;period&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important thing is that ROFL validates; Sapphire enforces.&lt;/p&gt;

&lt;p&gt;Those are different responsibilities.&lt;/p&gt;

&lt;p&gt;Preventing Replay Attacks&lt;/p&gt;

&lt;p&gt;Payroll systems are particularly vulnerable to replay.&lt;/p&gt;

&lt;p&gt;Imagine a valid payroll message:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alice&lt;br&gt;
August&lt;br&gt;
$8,500&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An attacker somehow submits the same message twice.&lt;/p&gt;

&lt;p&gt;Without replay protection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;August payment
+
August payment
=
$17,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract needs a settlement nonce or period identifier.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(bytes32 =&amp;gt; mapping(uint64 =&amp;gt; bool)) private settled;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (settled[id][period]) {
    revert AlreadySettled();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;settled[id][period] = true;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The invariant becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;∀ contractor, period:
    settlement_count &amp;lt;= 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be tested extensively.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens if ROFL Goes Offline?
&lt;/h2&gt;

&lt;p&gt;This is another reason not to make ROFL the sole source of truth.&lt;/p&gt;

&lt;p&gt;Suppose the ROFL application crashes for six hours.&lt;/p&gt;

&lt;p&gt;The escrow shouldn't become corrupted.&lt;/p&gt;

&lt;p&gt;The correct behavior should simply be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No payroll update
        ↓
No state corruption
        ↓
Existing escrow remains intact
        ↓
ROFL recovers
        ↓
Pending payroll period processed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where idempotency becomes important.&lt;/p&gt;

&lt;p&gt;The payroll processor should be able to safely retry the same period without causing a double payment.&lt;/p&gt;

&lt;p&gt;A good implementation therefore treats payroll updates as state transitions rather than one-shot commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Corrections
&lt;/h2&gt;

&lt;p&gt;Real payroll isn't perfectly linear.&lt;/p&gt;

&lt;p&gt;Someone might receive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base salary
+
bonus
-
expense correction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;after the original payroll calculation.&lt;/p&gt;

&lt;p&gt;Instead of modifying historical payments, create a new adjustment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;August salary
        +
August bonus
        -
expense correction
        =
August final adjustment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces an append-only accounting model.&lt;/p&gt;

&lt;p&gt;The confidential state can remain private while the protocol still preserves an internal accounting history.&lt;/p&gt;

&lt;p&gt;That makes audits much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Important Invariants
&lt;/h2&gt;

&lt;p&gt;A production implementation should not stop at unit tests.&lt;/p&gt;

&lt;p&gt;I'd use Foundry fuzzing to test the accounting model.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function invariant_neverPayMoreThanAccrued()
    public
{
    assertLe(
        totalSettled,
        totalAccrued
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another invariant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function invariant_noPeriodCanSettleTwice()
    public
{
    for (...) {
        assertLe(
            settlementCount[period],
            1
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function invariant_inactiveContractorCannotAccrue()
    public
{
    if (!contractor.active) {
        assertEq(
            accruedSinceDeactivation,
            0
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact implementation will depend on the accounting model, but the philosophy is important:&lt;/p&gt;

&lt;p&gt;test properties, not examples.&lt;/p&gt;

&lt;p&gt;A test that says:&lt;/p&gt;

&lt;p&gt;Alice receives $8,500&lt;/p&gt;

&lt;p&gt;is useful.&lt;/p&gt;

&lt;p&gt;A property that says:&lt;/p&gt;

&lt;p&gt;no contractor can receive more than the amount authorized by the accounting state&lt;/p&gt;

&lt;p&gt;is much more powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Threat Model
&lt;/h2&gt;

&lt;p&gt;Let's explicitly define the attackers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attacker 1: Malicious contractor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They control their wallet and attempt to claim more than they earned.&lt;/p&gt;

&lt;p&gt;Defense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contract-enforced accrual&lt;/li&gt;
&lt;li&gt;period tracking&lt;/li&gt;
&lt;li&gt;maximum claim&lt;/li&gt;
&lt;li&gt;replay protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Attacker 2: Compromised payroll backend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The backend generates a fraudulent payroll record.&lt;/p&gt;

&lt;p&gt;Defense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ROFL validation&lt;/li&gt;
&lt;li&gt;authorized source&lt;/li&gt;
&lt;li&gt;contract-level policy limits&lt;/li&gt;
&lt;li&gt;maximum settlement bounds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Attacker 3: Compromised ROFL application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ROFL process attempts to submit arbitrary payments.&lt;/p&gt;

&lt;p&gt;Defense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;restricted contract interface&lt;/li&gt;
&lt;li&gt;bounded permissions&lt;/li&gt;
&lt;li&gt;per-contractor limits&lt;/li&gt;
&lt;li&gt;period uniqueness&lt;/li&gt;
&lt;li&gt;escrow accounting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Attacker 4: Replay attacker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They resubmit a valid payroll message.&lt;/p&gt;

&lt;p&gt;Defense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;period nonce&lt;/li&gt;
&lt;li&gt;settlement identifier&lt;/li&gt;
&lt;li&gt;idempotent state transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Attacker 5: Curious blockchain observer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They attempt to reconstruct salary information from public chain data.&lt;/p&gt;

&lt;p&gt;Defense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confidential Sapphire state&lt;/li&gt;
&lt;li&gt;encrypted transaction inputs&lt;/li&gt;
&lt;li&gt;encrypted event payloads where appropriate&lt;/li&gt;
&lt;li&gt;minimal public disclosure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Attacker 6: Compromised frontend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend tries to trick a user into authorizing a malicious action.&lt;/p&gt;

&lt;p&gt;Defense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;strict contract authorization&lt;/li&gt;
&lt;li&gt;typed transaction validation&lt;/li&gt;
&lt;li&gt;domain separation&lt;/li&gt;
&lt;li&gt;least-privilege interfaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important observation is that confidentiality only solves one part of the threat model.&lt;/p&gt;

&lt;p&gt;It does not make authorization, accounting, or business logic automatically secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About the Company Treasury?
&lt;/h2&gt;

&lt;p&gt;There is another architectural decision.&lt;/p&gt;

&lt;p&gt;Should the treasury itself live on Sapphire?&lt;/p&gt;

&lt;p&gt;There are several possibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fully confidential&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Treasury
   ↓
Sapphire
   ↓
Payroll
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides maximum privacy but reduces public transparency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public treasury + confidential payroll&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Public Treasury
       ↓
Sapphire Escrow
       ↓
Private Payroll
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is probably the more interesting model for many organizations.&lt;/p&gt;

&lt;p&gt;The public treasury can prove that the company has funded the payroll system.&lt;/p&gt;

&lt;p&gt;The private contract controls individual distributions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hybrid&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Public reserve
      +
Private employee allocations
      +
Public aggregate settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives organizations control over exactly which information becomes publicly auditable.&lt;/p&gt;

&lt;p&gt;That is the model I'd recommend exploring first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Different From a Traditional Payroll Smart Contract
&lt;/h2&gt;

&lt;p&gt;A normal payroll contract might answer:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Did address X receive Y tokens?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This architecture asks a more useful question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Can the organization prove that its payroll policy was enforced without exposing the organization's entire payroll database?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That distinction is where confidential blockchain infrastructure becomes genuinely useful.&lt;/p&gt;

&lt;p&gt;The blockchain becomes a policy enforcement layer, not a database.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Possible Production Deployment
&lt;/h2&gt;

&lt;p&gt;A production deployment might look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This architecture gives every component a narrow responsibility.&lt;/li&gt;
&lt;li&gt;The enterprise systems know the business.&lt;/li&gt;
&lt;li&gt;ROFL handles confidential external computation and integration.&lt;/li&gt;
&lt;li&gt;Sapphire enforces private state transitions.&lt;/li&gt;
&lt;li&gt;The escrow controls settlement.&lt;/li&gt;
&lt;li&gt;The public treasury provides the funding layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Operational Monitoring
&lt;/h2&gt;

&lt;p&gt;Confidentiality also changes how monitoring should work.&lt;/p&gt;

&lt;p&gt;A traditional blockchain monitoring system might inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;salary&lt;/li&gt;
&lt;li&gt;recipient&lt;/li&gt;
&lt;li&gt;amount&lt;/li&gt;
&lt;li&gt;timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and reconstruct the entire payroll history.&lt;/p&gt;

&lt;p&gt;That isn't desirable here.&lt;/p&gt;

&lt;p&gt;Instead, monitoring should focus on public commitments and system health.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Escrow funded&lt;/li&gt;
&lt;li&gt;ROFL application active&lt;/li&gt;
&lt;li&gt;Payroll epoch completed&lt;/li&gt;
&lt;li&gt;Settlement batch committed&lt;/li&gt;
&lt;li&gt;Contract version changed&lt;/li&gt;
&lt;li&gt;Emergency mode activated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sensitive details remain inside the confidential system.&lt;/p&gt;

&lt;p&gt;This creates a useful principle:&lt;/p&gt;

&lt;p&gt;Monitor the protocol's guarantees, not necessarily the protocol's private inputs.&lt;/p&gt;

&lt;p&gt;Sapphire's encrypted-event support is useful here because applications can retain public event topics for indexing while keeping event payloads confidential.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Sapphire Does Not Hide
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This distinction is important enough to state explicitly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A confidential contract does not mean that every piece of information surrounding a transaction automatically disappears from the public world.&lt;/p&gt;

&lt;p&gt;Depending on the application architecture, observers may still learn things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a transaction occurred&lt;/li&gt;
&lt;li&gt;a contract was called&lt;/li&gt;
&lt;li&gt;a public balance changed&lt;/li&gt;
&lt;li&gt;a public funding transaction happened&lt;/li&gt;
&lt;li&gt;a settlement-related event exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is therefore not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Make the blockchain invisible."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Keep specifically identified sensitive state and execution inputs confidential while exposing the minimum information necessary for interoperability and verification."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a much more realistic security model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ROFL Instead of Putting Everything in Sapphire?
&lt;/h2&gt;

&lt;p&gt;Because not every task belongs inside a smart contract.&lt;/p&gt;

&lt;p&gt;A smart contract is excellent at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;state transitions&lt;/li&gt;
&lt;li&gt;authorization&lt;/li&gt;
&lt;li&gt;accounting&lt;/li&gt;
&lt;li&gt;settlement&lt;/li&gt;
&lt;li&gt;deterministic rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not the ideal place to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parse enterprise payroll files&lt;/li&gt;
&lt;li&gt;call an external HR API&lt;/li&gt;
&lt;li&gt;process complex business data&lt;/li&gt;
&lt;li&gt;maintain authenticated external connections&lt;/li&gt;
&lt;li&gt;run arbitrary application logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ROFL is designed specifically for offchain computation with private data and verifiable results, and it integrates with Sapphire for authenticated smart-contract interactions.&lt;/p&gt;

&lt;p&gt;The combination is therefore more powerful than either component by itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Repository's Security Checklist
&lt;/h2&gt;

&lt;p&gt;Before considering the implementation production-ready, I'd want the repository to contain a checklist like:&lt;/p&gt;

&lt;p&gt;[ ] ROFL caller authentication&lt;br&gt;
[ ] Payroll source authentication&lt;br&gt;
[ ] Replay protection&lt;br&gt;
[ ] Period uniqueness&lt;br&gt;
[ ] Maximum payment limits&lt;br&gt;
[ ] Maximum contractor exposure&lt;br&gt;
[ ] Escrow solvency checks&lt;br&gt;
[ ] Integer overflow protection&lt;br&gt;
[ ] Explicit rounding rules&lt;br&gt;
[ ] Emergency pause&lt;br&gt;
[ ] Emergency expiry&lt;br&gt;
[ ] Recovery mechanism&lt;br&gt;
[ ] Contract upgrade policy&lt;br&gt;
[ ] ROFL deployment policy&lt;br&gt;
[ ] Key rotation procedure&lt;br&gt;
[ ] Confidential event policy&lt;br&gt;
[ ] Fuzz tests&lt;br&gt;
[ ] Invariant tests&lt;br&gt;
[ ] Failure recovery tests&lt;br&gt;
[ ] ROFL outage tests&lt;br&gt;
[ ] Duplicate payroll tests&lt;br&gt;
[ ] Malicious payroll input tests&lt;/p&gt;

&lt;p&gt;This is the part I'd consider more important than adding another frontend feature.&lt;/p&gt;

&lt;p&gt;A confidential financial application has a very large attack surface if the trust boundaries aren't explicitly documented.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where This Could Go Beyond Payroll
&lt;/h2&gt;

&lt;p&gt;Once the architecture exists, payroll is only one application.&lt;/p&gt;

&lt;p&gt;The same primitive could support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Private contractor marketplaces
        ↓
Confidential compensation


B2B service agreements
        ↓
Confidential recurring settlement


Private grants
        ↓
Milestone-based payouts


Research collaborations
        ↓
Confidential contributor compensation


DAO contributor programs
        ↓
Private compensation schedules


Enterprise procurement
        ↓
Confidential invoice settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The common primitive is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private business state
        +
programmable settlement
        +
restricted authorization
        +
verifiable execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That combination is difficult to achieve cleanly with a conventional public EVM.&lt;/p&gt;

&lt;h2&gt;
  
  
  The More Interesting Question
&lt;/h2&gt;

&lt;p&gt;The interesting question isn't really:&lt;/p&gt;

&lt;p&gt;"Can blockchain do payroll?"&lt;/p&gt;

&lt;p&gt;It obviously can.&lt;/p&gt;

&lt;p&gt;The harder question is:&lt;/p&gt;

&lt;p&gt;Can blockchain enforce a business process without forcing the business to publish its internal state?&lt;/p&gt;

&lt;p&gt;That's where Oasis's architecture becomes interesting.&lt;/p&gt;

&lt;p&gt;Sapphire provides confidential EVM execution, while ROFL provides a framework for confidential offchain workloads that can interact with the onchain environment with an attested identity.&lt;/p&gt;

&lt;p&gt;Together, they allow developers to draw a much sharper boundary between:&lt;/p&gt;

&lt;p&gt;What must be public&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;What must merely be verifiable&lt;/p&gt;

&lt;p&gt;Those aren't the same thing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A company may need to prove that its payroll escrow is funded.&lt;/li&gt;
&lt;li&gt;It doesn't necessarily need to publish every employee's salary.&lt;/li&gt;
&lt;li&gt;A contractor may need to prove that a payment was authorized.&lt;/li&gt;
&lt;li&gt;They don't necessarily need to expose the entire company's payroll ledger.&lt;/li&gt;
&lt;li&gt;An auditor may need evidence that a policy was followed.&lt;/li&gt;
&lt;li&gt;They don't necessarily need unrestricted access to every private record.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the fundamental design space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Architecture
&lt;/h2&gt;

&lt;p&gt;The complete system can be summarized as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUBLIC
                           │
                    ┌──────▼──────┐
                    │   Treasury  │
                    └──────┬──────┘
                           │
                       Funding
                           │
                           ▼
                 ┌───────────────────┐
                 │ Sapphire Escrow   │
                 │                   │
                 │ Confidential      │
                 │ settlement state  │
                 └─────────▲─────────┘
                           │
                    authenticated
                       ROFL call
                           │
                 ┌─────────┴─────────┐
                 │       ROFL        │
                 │                   │
                 │ Validation        │
                 │ Aggregation       │
                 │ External APIs     │
                 │ Attested compute  │
                 └─────────▲─────────┘
                           │
                           │
                 ┌─────────┴─────────┐
                 │ HR / Payroll / ERP│
                 └───────────────────┘


                           │
                           ▼


                 ┌───────────────────┐
                 │ Contractor Wallet │
                 └───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The architecture deliberately avoids making one component responsible for everything.&lt;/li&gt;
&lt;li&gt;The payroll system knows the business rules.&lt;/li&gt;
&lt;li&gt;ROFL handles the confidential external computation.&lt;/li&gt;
&lt;li&gt;Sapphire enforces the settlement policy.&lt;/li&gt;
&lt;li&gt;The escrow controls the money.&lt;/li&gt;
&lt;li&gt;And the public chain exposes only the information that actually needs to be public.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the more interesting promise of confidential blockchain infrastructure: not simply hiding transactions, but making privacy a property of the application's architecture.&lt;/p&gt;

&lt;p&gt;For developers coming from Ethereum, the nice part is that Sapphire remains EVM-compatible, so familiar Solidity, Foundry, Hardhat, and other EVM tooling can be used while adding confidentiality-specific primitives.&lt;/p&gt;

&lt;p&gt;The result isn't a private database with a token attached.&lt;/p&gt;

&lt;p&gt;It's a programmable business process where confidential state, external computation, and onchain settlement can coexist without requiring the entire workflow to become public.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Oasis Sapphire
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/sapphire/" rel="noopener noreferrer"&gt;Oasis Sapphire Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/sapphire/develop/" rel="noopener noreferrer"&gt;Sapphire Developer Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Oasis ROFL
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/rofl/" rel="noopener noreferrer"&gt;ROFL Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/rofl/workflow/" rel="noopener noreferrer"&gt;How to ROFLize an App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Encrypted Events
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/sapphire/develop/encrypted-events/" rel="noopener noreferrer"&gt;Sapphire Encrypted Events&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Oasis Build Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/" rel="noopener noreferrer"&gt;Build on Oasis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web3</category>
      <category>privacy</category>
      <category>blockchain</category>
      <category>cryptocurrency</category>
    </item>
    <item>
      <title>Building a Confidential On-Chain Treasury Policy Engine with Oasis Sapphire</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Thu, 13 Aug 2026 07:25:13 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-on-chain-treasury-policy-engine-with-oasis-sapphire-45jp</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-on-chain-treasury-policy-engine-with-oasis-sapphire-45jp</guid>
      <description>&lt;p&gt;Private rules. Public commitments. Bounded emergency powers.&lt;/p&gt;

&lt;p&gt;Treasury contracts usually have an uncomfortable design problem.&lt;/p&gt;

&lt;p&gt;The treasury needs enough transparency that anyone can verify what happened, but it doesn't necessarily want to publish every piece of information that determines why a particular action was allowed.&lt;/p&gt;

&lt;p&gt;Imagine a protocol treasury that manages $10 million across several assets.&lt;/p&gt;

&lt;p&gt;The governance community might want the following guarantees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no transaction can exceed a daily spending limit&lt;/li&gt;
&lt;li&gt;a single operator cannot drain the treasury&lt;/li&gt;
&lt;li&gt;emergency powers automatically expire&lt;/li&gt;
&lt;li&gt;large transfers require additional approval&lt;/li&gt;
&lt;li&gt;certain destinations are prohibited&lt;/li&gt;
&lt;li&gt;policy changes are delayed&lt;/li&gt;
&lt;li&gt;every executed action leaves an auditable commitment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there is another requirement that is rarely discussed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the policy itself can be sensitive.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Publishing every internal threshold, destination rule, operational limit, or pending treasury decision can reveal how the treasury is managed before the organization is ready to disclose it.&lt;/p&gt;

&lt;p&gt;A conventional Ethereum contract doesn't give you much room here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its storage is public.&lt;/li&gt;
&lt;li&gt;Its calldata is public.&lt;/li&gt;
&lt;li&gt;Its events are public.&lt;/li&gt;
&lt;li&gt;Its execution path is observable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where Oasis Sapphire becomes interesting.&lt;/p&gt;

&lt;p&gt;Sapphire is an EVM-compatible confidential execution environment. Contract state is encrypted, and Sapphire provides end-to-end encryption for transactions and calls through its client tooling.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll build something more interesting than a private vault:&lt;/p&gt;

&lt;p&gt;a confidential treasury policy engine that evaluates whether a proposed treasury action is allowed, while publishing a cryptographic commitment that allows the system to remain auditable without revealing the policy itself.&lt;/p&gt;

&lt;p&gt;The goal isn't to make the treasury invisible.&lt;/p&gt;

&lt;p&gt;The goal is to separate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;what happened&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;from&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the private policy that caused it to be allowed.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;Our example system has three components:&lt;/p&gt;

&lt;p&gt;The Policy Engine runs on Oasis Sapphire.&lt;/p&gt;

&lt;p&gt;It stores sensitive treasury rules such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maximum transfer&lt;/li&gt;
&lt;li&gt;daily budget&lt;/li&gt;
&lt;li&gt;per-destination limits&lt;/li&gt;
&lt;li&gt;allowed assets&lt;/li&gt;
&lt;li&gt;emergency expiry&lt;/li&gt;
&lt;li&gt;operator permissions&lt;/li&gt;
&lt;li&gt;risk tiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A user submits a proposed action.&lt;/p&gt;

&lt;p&gt;The policy engine evaluates it.&lt;/p&gt;

&lt;p&gt;If the action is allowed, the engine produces an execution authorization and a cryptographic commitment.&lt;/p&gt;

&lt;p&gt;The actual treasury operation can then happen on a public EVM chain.&lt;/p&gt;

&lt;p&gt;That gives us a hybrid architecture:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                CONFIDENTIAL
             Oasis Sapphire
          ┌────────────────────┐
          │                    │
          │  Private Policy    │
          │        ↓           │
          │  Policy Evaluation │
          │        ↓           │
          │  Authorization     │
          │        ↓           │
          │  Commitment        │
          │                    │
          └─────────┬──────────┘
                    │
                    │ authorization
                    ▼
                PUBLIC
             Ethereum / Base
          ┌────────────────────┐
          │                    │
          │ Treasury execution │
          │                    │
          │ Events             │
          │                    │
          │ Asset movement     │
          │                    │
          └────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is useful because not everything needs confidentiality.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The amount actually transferred can remain public.&lt;/li&gt;
&lt;li&gt;The destination can remain public.&lt;/li&gt;
&lt;li&gt;The treasury balance can remain public.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we're protecting is the policy machinery around those actions.&lt;/p&gt;

&lt;p&gt;Why Not Just Put Everything in a Multisig?&lt;/p&gt;

&lt;p&gt;A natural question is:&lt;/p&gt;

&lt;p&gt;Why not use Safe + a timelock + a multisig?&lt;/p&gt;

&lt;p&gt;For many treasuries, that's exactly the right answer.&lt;/p&gt;

&lt;p&gt;This tutorial isn't claiming otherwise.&lt;/p&gt;

&lt;p&gt;The problem appears when the treasury's operational policy becomes more complicated than:&lt;/p&gt;

&lt;p&gt;3 of 5 signers approve transaction&lt;/p&gt;

&lt;p&gt;Consider a treasury with rules like:&lt;/p&gt;

&lt;p&gt;USDC:&lt;br&gt;
    daily limit = X&lt;/p&gt;

&lt;p&gt;ETH:&lt;br&gt;
    daily limit = Y&lt;/p&gt;

&lt;p&gt;New recipient:&lt;br&gt;
    requires additional approval&lt;/p&gt;

&lt;p&gt;Known recipient:&lt;br&gt;
    lower approval requirement&lt;/p&gt;

&lt;p&gt;Emergency mode:&lt;br&gt;
    withdrawals capped&lt;br&gt;
    expires after N blocks&lt;/p&gt;

&lt;p&gt;Strategy contract:&lt;br&gt;
    allowed to rebalance&lt;br&gt;
    but not withdraw to arbitrary EOAs&lt;/p&gt;

&lt;p&gt;Treasury operator:&lt;br&gt;
    can execute&lt;br&gt;
    but cannot modify policy&lt;/p&gt;

&lt;p&gt;A traditional public contract can encode all of this.&lt;/p&gt;

&lt;p&gt;But now everyone can inspect it.&lt;/p&gt;

&lt;p&gt;That isn't automatically bad.&lt;/p&gt;

&lt;p&gt;However, there are situations where revealing the exact operational policy is undesirable.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;market-making organizations&lt;/li&gt;
&lt;li&gt;grant programs&lt;/li&gt;
&lt;li&gt;institutional treasury operations&lt;/li&gt;
&lt;li&gt;DAO operational budgets&lt;/li&gt;
&lt;li&gt;private investment vehicles&lt;/li&gt;
&lt;li&gt;infrastructure operators&lt;/li&gt;
&lt;li&gt;trading organizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting design question becomes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can the control logic be confidential while the result of the control logic remains auditable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what we'll explore.&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Our final architecture looks like this:&lt;/p&gt;

&lt;p&gt;There are four important boundaries here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary 1: Policy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The policy is private.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary 2: Authorization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An operator isn't automatically trusted simply because they can call the policy engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary 3: Execution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The public treasury only accepts valid authorization artifacts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary 4: Auditability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every accepted decision produces enough public information to reconstruct the fact that authorization occurred without necessarily exposing the private policy.&lt;/p&gt;
&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;We'll use Foundry for the Solidity side and TypeScript for integration tests.&lt;/p&gt;

&lt;p&gt;The repository structure will look like this:&lt;/p&gt;

&lt;p&gt;confidential-treasury-policy/&lt;br&gt;
├── src/&lt;br&gt;
│   ├── ConfidentialPolicy.sol&lt;br&gt;
│   ├── PolicyTypes.sol&lt;br&gt;
│   ├── CommitmentRegistry.sol&lt;br&gt;
│   ├── ExecutionGateway.sol&lt;br&gt;
│   ├── Treasury.sol&lt;br&gt;
│   └── interfaces/&lt;br&gt;
│       ├── IPolicyEngine.sol&lt;br&gt;
│       └── ITreasury.sol&lt;br&gt;
│&lt;br&gt;
├── script/&lt;br&gt;
│   ├── Deploy.s.sol&lt;br&gt;
│   ├── ConfigurePolicy.s.sol&lt;br&gt;
│   └── RegisterGateway.s.sol&lt;br&gt;
│&lt;br&gt;
├── test/&lt;br&gt;
│   ├── PolicyEngine.t.sol&lt;br&gt;
│   ├── Emergency.t.sol&lt;br&gt;
│   ├── Replay.t.sol&lt;br&gt;
│   ├── Authorization.t.sol&lt;br&gt;
│   └── Invariants.t.sol&lt;br&gt;
│&lt;br&gt;
├── integration/&lt;br&gt;
│   ├── sapphire.ts&lt;br&gt;
│   ├── treasury.ts&lt;br&gt;
│   └── execute.ts&lt;br&gt;
│&lt;br&gt;
├── lib/&lt;br&gt;
│   └── forge-std/&lt;br&gt;
│&lt;br&gt;
├── foundry.toml&lt;br&gt;
├── package.json&lt;br&gt;
└── README.md&lt;/p&gt;

&lt;p&gt;Start with:&lt;/p&gt;

&lt;p&gt;forge init confidential-treasury-policy&lt;br&gt;
cd confidential-treasury-policy&lt;/p&gt;

&lt;p&gt;For Sapphire development, Oasis provides Foundry and Hardhat integration because Sapphire is EVM-compatible.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Defining the Policy Model
&lt;/h2&gt;

&lt;p&gt;Before writing Solidity, define what a policy actually controls.&lt;/p&gt;

&lt;p&gt;We'll use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Policy {
    uint256 maxPerTransaction;
    uint256 dailyLimit;
    uint256 emergencyLimit;
    uint64 emergencyUntil;

    mapping(address =&amp;gt; bool) allowedAsset;
    mapping(address =&amp;gt; bool) allowedRecipient;

    bool emergencyMode;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Solidity mappings inside structs make copying and hashing awkward.&lt;/p&gt;

&lt;p&gt;So we'll store the components separately.&lt;/p&gt;

&lt;p&gt;Our first version needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maximum transaction amount&lt;/li&gt;
&lt;li&gt;daily budget&lt;/li&gt;
&lt;li&gt;emergency budget&lt;/li&gt;
&lt;li&gt;emergency expiry&lt;/li&gt;
&lt;li&gt;allowed assets&lt;/li&gt;
&lt;li&gt;allowed recipients&lt;/li&gt;
&lt;li&gt;policy version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important thing is that policy versioning is explicit.&lt;/p&gt;

&lt;p&gt;We'll never silently mutate a policy.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Policy v1
    ↓
Policy v2
    ↓
Policy v3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every authorization records the version that produced it.&lt;/p&gt;

&lt;p&gt;That becomes extremely useful during audits.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. PolicyTypes.sol
&lt;/h2&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

library PolicyTypes {
    enum Decision {
        DENIED,
        APPROVED
    }

    enum RiskLevel {
        NORMAL,
        ELEVATED,
        EMERGENCY
    }

    struct Request {
        address asset;
        address recipient;
        uint256 amount;
        uint256 nonce;
        bytes32 requestId;
    }

    struct DecisionRecord {
        Decision decision;
        RiskLevel risk;
        uint256 policyVersion;
        uint256 expiresAt;
        bytes32 commitment;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Request represents what someone wants to do.&lt;/p&gt;

&lt;p&gt;The DecisionRecord represents what the policy engine decided.&lt;/p&gt;

&lt;p&gt;Notice something important:&lt;/p&gt;

&lt;p&gt;The policy itself isn't included in DecisionRecord.&lt;/p&gt;

&lt;p&gt;That's intentional.&lt;/p&gt;

&lt;p&gt;We don't want every authorization to reveal the complete policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Confidential Policy Contract
&lt;/h2&gt;

&lt;p&gt;Now create:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;src/ConfidentialPolicy.sol&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The basic contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "./PolicyTypes.sol";

contract ConfidentialPolicy {
    using PolicyTypes for *;

    address public governance;
    address public executionGateway;

    uint256 public policyVersion;

    uint256 private maxPerTransaction;
    uint256 private dailyLimit;
    uint256 private emergencyLimit;

    uint64 private emergencyUntil;
    bool private emergencyMode;

    mapping(address =&amp;gt; bool) private allowedAssets;
    mapping(address =&amp;gt; bool) private allowedRecipients;

    mapping(bytes32 =&amp;gt; bool) private consumedRequests;

    constructor(address _governance) {
        governance = _governance;
        policyVersion = 1;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The sensitive fields are private.&lt;/li&gt;
&lt;li&gt;On a normal EVM, private does not mean confidential.&lt;/li&gt;
&lt;li&gt;Anyone can inspect Ethereum storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On Sapphire, however, contract state is encrypted and accessible only through the contract's confidential execution environment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This distinction is fundamental.&lt;/p&gt;

&lt;p&gt;Don't write:&lt;/p&gt;

&lt;p&gt;"private makes Solidity storage secret."&lt;/p&gt;

&lt;p&gt;It doesn't.&lt;/p&gt;

&lt;p&gt;The confidentiality comes from Sapphire's execution and encrypted state model.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Access Control
&lt;/h2&gt;

&lt;p&gt;We need three roles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;governance&lt;/li&gt;
&lt;li&gt;operator&lt;/li&gt;
&lt;li&gt;execution gateway&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Governance changes policy.&lt;/p&gt;

&lt;p&gt;Operators submit actions.&lt;/p&gt;

&lt;p&gt;The execution gateway consumes authorizations.&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;address public operator;

modifier onlyGovernance() {
    require(msg.sender == governance, "not governance");
    _;
}

modifier onlyOperator() {
    require(msg.sender == operator, "not operator");
    _;
}

modifier onlyGateway() {
    require(msg.sender == executionGateway, "not gateway");
    _;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setOperator(address newOperator)
    external
    onlyGovernance
{
    require(newOperator != address(0), "zero operator");
    operator = newOperator;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setExecutionGateway(address gateway)
    external
    onlyGovernance
{
    require(gateway != address(0), "zero gateway");
    executionGateway = gateway;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation matters.&lt;/p&gt;

&lt;p&gt;The person allowed to propose treasury actions shouldn't automatically be allowed to modify the policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Adding Assets and Recipients
&lt;/h2&gt;

&lt;p&gt;We'll use allowlists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setAssetAllowed(
    address asset,
    bool allowed
)
    external
    onlyGovernance
{
    allowedAssets[asset] = allowed;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setRecipientAllowed(
    address recipient,
    bool allowed
)
    external
    onlyGovernance
{
    allowedRecipients[recipient] = allowed;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we already have something interesting.&lt;/p&gt;

&lt;p&gt;Suppose the treasury works with five counterparties.&lt;/p&gt;

&lt;p&gt;On Ethereum, you could publish the complete list of addresses.&lt;/p&gt;

&lt;p&gt;On Sapphire, the policy engine can keep that operational relationship confidential.&lt;/p&gt;

&lt;p&gt;That doesn't necessarily mean the final transfer is private.&lt;/p&gt;

&lt;p&gt;It means the decision infrastructure doesn't have to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The Core Policy Function
&lt;/h2&gt;

&lt;p&gt;Now we get to the heart of the system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function evaluate(
    PolicyTypes.Request calldata request
)
    external
    onlyOperator
    returns (PolicyTypes.DecisionRecord memory)
{
    require(
        !consumedRequests[request.requestId],
        "request already consumed"
    );

    require(
        allowedAssets[request.asset],
        "asset not allowed"
    );

    require(
        allowedRecipients[request.recipient],
        "recipient not allowed"
    );

    uint256 limit = maxPerTransaction;

    if (emergencyMode) {
        require(
            block.timestamp &amp;lt; emergencyUntil,
            "emergency expired"
        );

        limit = emergencyLimit;
    }

    require(
        request.amount &amp;lt;= limit,
        "policy limit"
    );

    consumedRequests[request.requestId] = true;

    bytes32 commitment = keccak256(
        abi.encode(
            request.requestId,
            request.asset,
            request.recipient,
            request.amount,
            policyVersion
        )
    );

    return PolicyTypes.DecisionRecord({
        decision: PolicyTypes.Decision.APPROVED,
        risk: emergencyMode
            ? PolicyTypes.RiskLevel.EMERGENCY
            : PolicyTypes.RiskLevel.NORMAL,
        policyVersion: policyVersion,
        expiresAt: block.timestamp + 60,
        commitment: commitment
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are several important properties here.&lt;/p&gt;

&lt;p&gt;The authorization is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tied to one request&lt;/li&gt;
&lt;li&gt;tied to one policy version&lt;/li&gt;
&lt;li&gt;short-lived&lt;/li&gt;
&lt;li&gt;single-use&lt;/li&gt;
&lt;li&gt;bounded by the policy&lt;/li&gt;
&lt;li&gt;committed cryptographically&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Why Nonces Aren't Enough
&lt;/h2&gt;

&lt;p&gt;A common mistake in authorization systems is using only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 nonce;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That protects against simple replay.&lt;/p&gt;

&lt;p&gt;But a robust authorization should also bind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request ID&lt;/li&gt;
&lt;li&gt;asset&lt;/li&gt;
&lt;li&gt;recipient&lt;/li&gt;
&lt;li&gt;amount&lt;/li&gt;
&lt;li&gt;policy version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise an authorization for:&lt;/p&gt;

&lt;p&gt;USDC → Alice → 10,000&lt;/p&gt;

&lt;p&gt;could potentially be interpreted incorrectly by another component as:&lt;/p&gt;

&lt;p&gt;USDC → Bob → 10,000&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;USDC → Alice → 100,000&lt;/p&gt;

&lt;p&gt;The commitment should therefore be domain-specific.&lt;/p&gt;

&lt;p&gt;A stronger commitment looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bytes32 constant DOMAIN =
    keccak256("CONFIDENTIAL_TREASURY_POLICY_V1");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bytes32 commitment = keccak256(
    abi.encode(
        DOMAIN,
        address(this),
        block.chainid,
        policyVersion,
        request.requestId,
        request.asset,
        request.recipient,
        request.amount,
        request.nonce
    )
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now an authorization isn't just a hash of data.&lt;/p&gt;

&lt;p&gt;It's a hash of:&lt;/p&gt;

&lt;p&gt;what system + what chain + what policy + what request + what action.&lt;/p&gt;

&lt;p&gt;This dramatically reduces cross-context replay risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The Execution Gateway
&lt;/h2&gt;

&lt;p&gt;Now we need a bridge between confidential authorization and public execution.&lt;/p&gt;

&lt;p&gt;Create:&lt;/p&gt;

&lt;p&gt;ExecutionGateway.sol&lt;/p&gt;

&lt;p&gt;The gateway receives an authorization.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;The gateway is deliberately dumb.&lt;/p&gt;

&lt;p&gt;This is important.&lt;/p&gt;

&lt;p&gt;We don't want the public execution layer to recreate the entire private policy.&lt;/p&gt;

&lt;p&gt;The policy engine says:&lt;/p&gt;

&lt;p&gt;this action is authorized.&lt;/p&gt;

&lt;p&gt;The gateway says:&lt;/p&gt;

&lt;p&gt;this authorization has not expired, has not been consumed, and corresponds to this execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Authorization Expiry
&lt;/h2&gt;

&lt;p&gt;Never create a confidential authorization that lasts indefinitely.&lt;/p&gt;

&lt;p&gt;Our authorization has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 expiresAt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    block.timestamp &amp;lt;= authorization.expiresAt,
    "authorization expired"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Imagine an operator obtains authorization at 10:00.&lt;/p&gt;

&lt;p&gt;At 10:30, governance changes the policy.&lt;/p&gt;

&lt;p&gt;If the old authorization remains valid forever, the policy change doesn't actually revoke the previous decision.&lt;/p&gt;

&lt;p&gt;Short-lived authorizations reduce that window.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Emergency Mode
&lt;/h2&gt;

&lt;p&gt;Now let's implement something more interesting.&lt;/p&gt;

&lt;p&gt;The treasury needs emergency powers.&lt;/p&gt;

&lt;p&gt;But we don't want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emergency = true;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to mean:&lt;/p&gt;

&lt;p&gt;administrator can do anything.&lt;/p&gt;

&lt;p&gt;Instead, emergency mode changes a small number of parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function activateEmergency(
    uint64 duration
)
    external
    onlyGovernance
{
    require(duration &amp;lt;= 1 hours, "duration too long");

    emergencyMode = true;
    emergencyUntil =
        uint64(block.timestamp) + duration;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function emergencyStatus()
    external
    view
    returns (
        bool active,
        uint64 until
    )
{
    return (
        emergencyMode &amp;amp;&amp;amp;
        block.timestamp &amp;lt; emergencyUntil,
        emergencyUntil
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key property is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;emergency authority expires automatically.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is much stronger than documenting:&lt;/p&gt;

&lt;p&gt;emergency mode should only be used temporarily.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11. Emergency Powers Should Be Narrow

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose normal mode allows:&lt;/p&gt;

&lt;p&gt;maximum transfer = 100,000 USDC&lt;/p&gt;

&lt;p&gt;Emergency mode might allow:&lt;/p&gt;

&lt;p&gt;maximum transfer = 10,000 USDC&lt;/p&gt;

&lt;p&gt;That sounds backwards at first.&lt;/p&gt;

&lt;p&gt;But that's exactly the point.&lt;/p&gt;

&lt;p&gt;An emergency system should often reduce capability, not increase it.&lt;/p&gt;

&lt;p&gt;A safer mental model is:&lt;/p&gt;

&lt;p&gt;Normal operation&lt;br&gt;
       │&lt;br&gt;
       │ incident&lt;br&gt;
       ▼&lt;br&gt;
Restricted operation&lt;br&gt;
       │&lt;br&gt;
       │ expiry&lt;br&gt;
       ▼&lt;br&gt;
Normal operation&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;p&gt;Normal operation&lt;br&gt;
       │&lt;br&gt;
       ▼&lt;br&gt;
Admin can do anything&lt;br&gt;
       │&lt;br&gt;
       ▼&lt;br&gt;
Hopefully someone turns it off&lt;/p&gt;
&lt;h2&gt;
  
  
  12. The Policy Version
&lt;/h2&gt;

&lt;p&gt;Whenever governance changes a policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function bumpPolicyVersion()
    internal
{
    policyVersion++;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setMaxPerTransaction(
    uint256 value
)
    external
    onlyGovernance
{
    maxPerTransaction = value;
    policyVersion++;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a useful property:&lt;/p&gt;

&lt;p&gt;Every authorization can answer:&lt;/p&gt;

&lt;p&gt;Which policy version approved this?&lt;/p&gt;

&lt;p&gt;That gives auditors a historical dimension.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Public Commitments
&lt;/h2&gt;

&lt;p&gt;Now comes the most important architectural idea.&lt;/p&gt;

&lt;p&gt;We want to expose enough information for an auditor to know that a decision occurred.&lt;/p&gt;

&lt;p&gt;But we don't necessarily want to expose the entire policy.&lt;/p&gt;

&lt;p&gt;We can publish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request ID&lt;/li&gt;
&lt;li&gt;policy version&lt;/li&gt;
&lt;li&gt;commitment&lt;/li&gt;
&lt;li&gt;execution timestamp&lt;/li&gt;
&lt;li&gt;execution result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without publishing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full policy&lt;/li&gt;
&lt;li&gt;private allowlists&lt;/li&gt;
&lt;li&gt;internal limits&lt;/li&gt;
&lt;li&gt;operational metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The commitment is:&lt;/p&gt;

&lt;p&gt;H(&lt;br&gt;
    domain,&lt;br&gt;
    chain ID,&lt;br&gt;
    policy version,&lt;br&gt;
    request,&lt;br&gt;
    authorization&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;The public chain therefore sees a cryptographic fingerprint.&lt;/p&gt;

&lt;p&gt;Think of it as:&lt;/p&gt;

&lt;p&gt;The commitment doesn't reveal the input.&lt;/p&gt;

&lt;p&gt;But if the system later needs to disclose the policy, an auditor can verify that the disclosed data corresponds to the commitment.&lt;/p&gt;
&lt;h2&gt;
  
  
  14. Why Not Just Emit the Policy?
&lt;/h2&gt;

&lt;p&gt;Because events are public.&lt;/p&gt;

&lt;p&gt;This is one of the most important Sapphire-specific considerations.&lt;/p&gt;

&lt;p&gt;Sapphire contract state is encrypted, but Solidity events are not automatically private. Oasis explicitly documents that event payloads are public unless you use an encrypted-event design.&lt;/p&gt;

&lt;p&gt;So this is dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event PolicyUpdated(
    uint256 maxPerTransaction,
    uint256 dailyLimit
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if the variables themselves are confidential, that event leaks them.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event PolicyCommitted(
    uint256 indexed version,
    bytes32 indexed commitment
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the event is useful for indexing without exposing the policy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;15. Confidential Events

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you actually need to publish sensitive event payloads to an authorized consumer, Sapphire provides encrypted-event patterns.&lt;/p&gt;

&lt;p&gt;For example, Oasis documents patterns based on symmetric keys and ECDH-derived keys for encrypted events.&lt;/p&gt;

&lt;p&gt;The conceptual architecture becomes:&lt;/p&gt;

&lt;p&gt;This gives you two information channels:&lt;/p&gt;

&lt;p&gt;Public channel&lt;/p&gt;

&lt;p&gt;Safe metadata.&lt;/p&gt;

&lt;p&gt;Confidential channel&lt;/p&gt;

&lt;p&gt;Authorized operational information.&lt;/p&gt;

&lt;p&gt;That distinction is useful for enterprise systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  16. The Daily Spending Limit
&lt;/h2&gt;

&lt;p&gt;So far we've only implemented a per-transaction limit.&lt;/p&gt;

&lt;p&gt;Let's add a daily limit.&lt;/p&gt;

&lt;p&gt;The naïve implementation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 spentToday;
uint256 currentDay;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (block.timestamp / 1 days != currentDay) {
    currentDay = block.timestamp / 1 days;
    spentToday = 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    spentToday + request.amount &amp;lt;= dailyLimit,
    "daily limit"
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;spentToday += request.amount;&lt;/p&gt;

&lt;p&gt;This works, but introduces a subtle privacy consideration.&lt;/p&gt;

&lt;p&gt;If the execution cost or state access pattern depends strongly on private values, an observer may infer information.&lt;/p&gt;

&lt;p&gt;Oasis explicitly warns that confidential execution can still leak information through timing, gas usage, and storage access patterns.&lt;/p&gt;

&lt;p&gt;So confidential programming isn't:&lt;/p&gt;

&lt;p&gt;"Put variables behind private."&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;p&gt;"Design the entire observable execution surface."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;17. Constant-Shaped Execution

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (amount &amp;gt; secretLimit) {
    revert();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result itself is private.&lt;/p&gt;

&lt;p&gt;But the gas behavior may differ between branches.&lt;/p&gt;

&lt;p&gt;A sufficiently capable observer may be able to infer information.&lt;/p&gt;

&lt;p&gt;This is why confidential systems need a different mindset.&lt;/p&gt;

&lt;p&gt;Instead of thinking:&lt;/p&gt;

&lt;p&gt;state is secret&lt;/p&gt;

&lt;p&gt;think:&lt;/p&gt;

&lt;p&gt;state + execution + gas + storage access + events + timing&lt;/p&gt;

&lt;p&gt;all contribute to the observable surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  18. Building the Treasury
&lt;/h2&gt;

&lt;p&gt;Now create a simple treasury.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract Treasury {
    address public immutable policyGateway;

    constructor(address _policyGateway) {
        policyGateway = _policyGateway;
    }

    modifier onlyGateway() {
        require(
            msg.sender == policyGateway,
            "not gateway"
        );
        _;
    }

    function executeERC20(
        address token,
        address recipient,
        uint256 amount
    )
        external
        onlyGateway
    {
        IERC20(token).transfer(
            recipient,
            amount
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the treasury itself doesn't know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;daily limit&lt;/li&gt;
&lt;li&gt;recipient classification&lt;/li&gt;
&lt;li&gt;emergency rules&lt;/li&gt;
&lt;li&gt;operator permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It only knows:&lt;/p&gt;

&lt;p&gt;the policy gateway is authorized to execute.&lt;/p&gt;

&lt;p&gt;This separation makes the system easier to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  19. Why the Treasury Should Not Reimplement Policy
&lt;/h2&gt;

&lt;p&gt;Imagine this:&lt;/p&gt;

&lt;p&gt;Policy Engine:&lt;br&gt;
    max transfer = 100k&lt;/p&gt;

&lt;p&gt;Treasury:&lt;br&gt;
    max transfer = 250k&lt;/p&gt;

&lt;p&gt;Now we have two policy engines.&lt;/p&gt;

&lt;p&gt;That's dangerous.&lt;/p&gt;

&lt;p&gt;The policy engine should be the authority.&lt;/p&gt;

&lt;p&gt;The treasury should enforce the minimum mechanical conditions needed for safe execution.&lt;/p&gt;

&lt;p&gt;This is a classic principle:&lt;/p&gt;

&lt;p&gt;Don't duplicate security-critical policy across independent components unless you explicitly want defense in depth.&lt;/p&gt;

&lt;p&gt;If you do duplicate it, test that the policies are equivalent.&lt;/p&gt;
&lt;h2&gt;
  
  
  20. Replaying an Authorization
&lt;/h2&gt;

&lt;p&gt;Now imagine an attacker obtains:&lt;/p&gt;

&lt;p&gt;authorization:&lt;br&gt;
    recipient = attacker&lt;br&gt;
    amount = 100,000&lt;/p&gt;

&lt;p&gt;and tries to submit it twice.&lt;/p&gt;

&lt;p&gt;The gateway needs:&lt;/p&gt;

&lt;p&gt;mapping(bytes32 =&amp;gt; bool) usedCommitments;&lt;/p&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    !usedCommitments[authorization.commitment],
    "already used"
);

usedCommitments[
    authorization.commitment
] = true;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ordering matters.&lt;/p&gt;

&lt;p&gt;Do not mark it consumed after an external call.&lt;/p&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;p&gt;check&lt;br&gt;
↓&lt;br&gt;
mark consumed&lt;br&gt;
↓&lt;br&gt;
external execution&lt;/p&gt;

&lt;p&gt;rather than:&lt;/p&gt;

&lt;p&gt;check&lt;br&gt;
↓&lt;br&gt;
external execution&lt;br&gt;
↓&lt;br&gt;
mark consumed&lt;/p&gt;

&lt;p&gt;Otherwise a reentrant path may attempt to reuse it.&lt;/p&gt;
&lt;h2&gt;
  
  
  21. The Complete Execution Flow
&lt;/h2&gt;

&lt;p&gt;The complete system now looks like:&lt;/p&gt;

&lt;p&gt;This is our core protocol.&lt;/p&gt;
&lt;h2&gt;
  
  
  22. Adding an Action Type
&lt;/h2&gt;

&lt;p&gt;Don't make authorization only about transfers.&lt;/p&gt;

&lt;p&gt;Define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enum ActionType {
    TRANSFER,
    REBALANCE,
    GRANT,
    REVOKE,
    EMERGENCY
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ActionType actionType;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in the request.&lt;/p&gt;

&lt;p&gt;Now the commitment binds the exact operation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bytes32 commitment = keccak256(
    abi.encode(
        DOMAIN,
        action.actionType,
        action.asset,
        action.recipient,
        action.amount,
        policyVersion,
        action.nonce
    )
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents an authorization for one semantic operation from being interpreted as another.&lt;/p&gt;

&lt;h2&gt;
  
  
  23. Adding Role Separation
&lt;/h2&gt;

&lt;p&gt;Now we can make the architecture more realistic.&lt;/p&gt;

&lt;p&gt;Governance&lt;br&gt;
   │&lt;br&gt;
   ├── modifies policy&lt;br&gt;
   │&lt;br&gt;
   └── controls emergency mode&lt;/p&gt;

&lt;p&gt;Operator&lt;br&gt;
   │&lt;br&gt;
   └── proposes actions&lt;/p&gt;

&lt;p&gt;Policy Engine&lt;br&gt;
   │&lt;br&gt;
   └── evaluates actions&lt;/p&gt;

&lt;p&gt;Gateway&lt;br&gt;
   │&lt;br&gt;
   └── executes authorized actions&lt;/p&gt;

&lt;p&gt;Treasury&lt;br&gt;
   │&lt;br&gt;
   └── owns assets&lt;/p&gt;

&lt;p&gt;No single component needs every permission.&lt;/p&gt;

&lt;p&gt;That matters because the most dangerous failure mode in treasury architecture is often not a bug.&lt;/p&gt;

&lt;p&gt;It's excessive authority.&lt;/p&gt;
&lt;h2&gt;
  
  
  24. A Real Use Case
&lt;/h2&gt;

&lt;p&gt;Let's make this concrete.&lt;/p&gt;

&lt;p&gt;Imagine a protocol treasury with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$4M USDC&lt;/li&gt;
&lt;li&gt;$2M ETH&lt;/li&gt;
&lt;li&gt;$1M governance token&lt;/li&gt;
&lt;li&gt;$500k operational budget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The organization has three operating groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operations&lt;/li&gt;
&lt;li&gt;Liquidity&lt;/li&gt;
&lt;li&gt;Grants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The treasury wants to keep its operational rules private.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because publishing:&lt;/p&gt;

&lt;p&gt;Liquidity strategy:&lt;br&gt;
    rebalance above 4%&lt;br&gt;
    maximum venue exposure = 22%&lt;br&gt;
    preferred counterparties = [...]&lt;/p&gt;

&lt;p&gt;can reveal operational strategy.&lt;/p&gt;

&lt;p&gt;Instead, governance can configure the policy privately.&lt;/p&gt;

&lt;p&gt;The public chain sees:&lt;/p&gt;

&lt;p&gt;Treasury:&lt;br&gt;
    0x...&lt;/p&gt;

&lt;p&gt;Authorization:&lt;br&gt;
    commitment 0xabc...&lt;br&gt;
    policy version 17&lt;br&gt;
    executed at block ...&lt;/p&gt;

&lt;p&gt;The actual treasury transfer remains observable.&lt;/p&gt;

&lt;p&gt;But the private policy that approved it doesn't have to become public.&lt;/p&gt;
&lt;h2&gt;
  
  
  25. Adding Policy Commitments
&lt;/h2&gt;

&lt;p&gt;We can make policy updates themselves auditable.&lt;/p&gt;

&lt;p&gt;Instead of publicly exposing:&lt;/p&gt;

&lt;p&gt;dailyLimit = 500,000&lt;br&gt;
maxPerTx = 100,000&lt;/p&gt;

&lt;p&gt;we calculate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bytes32 policyCommitment = keccak256(
    abi.encode(
        policyVersion,
        maxPerTransaction,
        dailyLimit,
        emergencyLimit,
        emergencyUntil
    )
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then emit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event PolicyCommitted(
    uint256 indexed version,
    bytes32 indexed commitment
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the public chain has:&lt;/p&gt;

&lt;p&gt;version 17&lt;br&gt;
commitment 0x...&lt;/p&gt;

&lt;p&gt;An auditor with access to the policy can verify:&lt;/p&gt;

&lt;p&gt;hash(disclosed_policy) == commitment&lt;/p&gt;

&lt;p&gt;This is a powerful pattern.&lt;/p&gt;

&lt;p&gt;The chain doesn't need to know the policy.&lt;/p&gt;

&lt;p&gt;It only needs to know which policy commitment was active.&lt;/p&gt;
&lt;h2&gt;
  
  
  26. Why Hashing Isn't Magic
&lt;/h2&gt;

&lt;p&gt;There is an important limitation.&lt;/p&gt;

&lt;p&gt;A commitment does not automatically make something trustworthy.&lt;/p&gt;

&lt;p&gt;Suppose the policy contains:&lt;/p&gt;

&lt;p&gt;recipient = 0x123&lt;/p&gt;

&lt;p&gt;and someone claims the commitment represents:&lt;/p&gt;

&lt;p&gt;recipient = 0x456&lt;/p&gt;

&lt;p&gt;Without knowing the original policy, you can't infer the truth.&lt;/p&gt;

&lt;p&gt;The commitment provides:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;integrity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;availability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;honesty&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's why governance, attestation, access control, and operational procedures still matter.&lt;/p&gt;

&lt;p&gt;Confidentiality isn't a substitute for governance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;27. Policy Disclosure

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose the organization wants to reveal a policy after an incident.&lt;/p&gt;

&lt;p&gt;They can publish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPerTransaction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dailyLimit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"500000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"emergencyLimit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"emergencyUntil"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1780000000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;policyCommitment = keccak256(canonical(policy))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Anyone can recompute it.&lt;/p&gt;

&lt;p&gt;That turns confidentiality into selective disclosure.&lt;/p&gt;

&lt;p&gt;This is particularly useful for audits.&lt;/p&gt;

&lt;h2&gt;
  
  
  28. Canonical Encoding Matters
&lt;/h2&gt;

&lt;p&gt;Don't hash arbitrary JSON.&lt;/p&gt;

&lt;p&gt;These two objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may represent the same logical policy but have different byte representations.&lt;/p&gt;

&lt;p&gt;For cryptographic commitments, define a canonical encoding.&lt;/p&gt;

&lt;p&gt;For Solidity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keccak256(
    abi.encode(
        policyVersion,
        maxPerTransaction,
        dailyLimit,
        emergencyLimit,
        emergencyUntil
    )
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the ordering fixed.&lt;/p&gt;

&lt;p&gt;Document it.&lt;/p&gt;

&lt;p&gt;Never change it silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  29. Testing the Policy Engine
&lt;/h2&gt;

&lt;p&gt;This is where the tutorial becomes more than a demo.&lt;/p&gt;

&lt;p&gt;We need tests for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authorization&lt;/li&gt;
&lt;li&gt;replay&lt;/li&gt;
&lt;li&gt;expiry&lt;/li&gt;
&lt;li&gt;policy versioning&lt;/li&gt;
&lt;li&gt;emergency expiry&lt;/li&gt;
&lt;li&gt;asset allowlist&lt;/li&gt;
&lt;li&gt;recipient allowlist&lt;/li&gt;
&lt;li&gt;amount limits&lt;/li&gt;
&lt;li&gt;cross-domain replay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A basic test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsUnauthorizedRecipient()
    public
{
    PolicyTypes.Request memory request =
        PolicyTypes.Request({
            asset: USDC,
            recipient: attacker,
            amount: 1_000e6,
            nonce: 1,
            requestId: keccak256("request-1")
        });

    vm.expectRevert("recipient not allowed");

    policy.evaluate(request);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  30. Replay Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testCannotReuseRequest()
    public
{
    PolicyTypes.Request memory request =
        validRequest();

    policy.evaluate(request);

    vm.expectRevert("request already consumed");

    policy.evaluate(request);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replay testing should not be an afterthought.&lt;/p&gt;

&lt;p&gt;Authorization systems fail surprisingly often because developers test:&lt;/p&gt;

&lt;p&gt;valid authorization&lt;/p&gt;

&lt;p&gt;but not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same authorization&lt;/li&gt;
&lt;li&gt;different transaction&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  31. Expiry Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testAuthorizationExpires()
    public
{
    PolicyTypes.Request memory request =
        validRequest();

    PolicyTypes.DecisionRecord memory decision =
        policy.evaluate(request);

    vm.warp(decision.expiresAt + 1);

    vm.expectRevert("authorization expired");

    gateway.execute(
        request,
        decision
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact mechanics depend on how the public gateway receives and verifies the authorization, but the invariant is simple:&lt;/p&gt;

&lt;p&gt;An authorization must never become more powerful with age.&lt;/p&gt;

&lt;h2&gt;
  
  
  32. Emergency Expiry Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testEmergencyAutomaticallyExpires()
    public
{
    governanceActivateEmergency(30 minutes);

    vm.warp(
        block.timestamp + 31 minutes
    );

    assertFalse(
        policy.emergencyStatus()
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we're not testing:&lt;/p&gt;

&lt;p&gt;someone called disableEmergency()&lt;/p&gt;

&lt;p&gt;We're testing:&lt;/p&gt;

&lt;p&gt;time itself disables emergency mode&lt;/p&gt;

&lt;p&gt;That's stronger.&lt;/p&gt;

&lt;h2&gt;
  
  
  33. Property-Based Thinking
&lt;/h2&gt;

&lt;p&gt;Now let's move beyond individual tests.&lt;/p&gt;

&lt;p&gt;A useful invariant is:&lt;/p&gt;

&lt;p&gt;No authorization can approve more than the current policy allows.&lt;/p&gt;

&lt;p&gt;Another:&lt;/p&gt;

&lt;p&gt;Every authorization is unique.&lt;/p&gt;

&lt;p&gt;Another:&lt;/p&gt;

&lt;p&gt;An expired authorization can never execute.&lt;/p&gt;

&lt;p&gt;Another:&lt;/p&gt;

&lt;p&gt;An emergency authorization cannot exceed the emergency limit.&lt;/p&gt;

&lt;p&gt;You can express these as invariant tests.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;∀ authorization A:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A.amount &amp;lt;= policyLimit(A.policyVersion)

A.expiresAt &amp;gt; now

consumed(A) == false
    before execution

consumed(A) == true
    after successful execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is the kind of testing that makes the project interesting for experienced Solidity developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  34. Testing Confidentiality Is Different
&lt;/h2&gt;

&lt;p&gt;Normal Solidity testing asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did the function return the correct result?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Confidential Solidity needs additional questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did an event leak private information?&lt;/li&gt;
&lt;li&gt;Does gas usage reveal a secret branch?&lt;/li&gt;
&lt;li&gt;Does storage access reveal which user was selected?&lt;/li&gt;
&lt;li&gt;Does a public getter expose confidential state?&lt;/li&gt;
&lt;li&gt;Does an error message reveal policy information?&lt;/li&gt;
&lt;li&gt;Does an unauthorized eth_call reveal anything?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oasis specifically documents that unsigned eth_call requests have msg.sender == address(0) on Sapphire, which is an important difference from assumptions developers may carry over from transparent EVMs.&lt;/p&gt;

&lt;p&gt;So authentication needs to be designed deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  35. View Functions Need Special Attention
&lt;/h2&gt;

&lt;p&gt;Suppose we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getPolicy()
    external
    view
    returns (uint256)
{
    return maxPerTransaction;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations.&lt;/p&gt;

&lt;p&gt;You just defeated the point of the confidential policy.&lt;/p&gt;

&lt;p&gt;The state may be encrypted internally, but your getter explicitly reveals it.&lt;/p&gt;

&lt;p&gt;A confidential contract should instead expose only what the caller is authorized to see.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function policyVersion()
    external
    view
    returns (uint256)
{
    return policyVersion;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And potentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function policyCommitment()
    external
    view
    returns (bytes32)
{
    return currentPolicyCommitment;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  36. The Public / Private Interface
&lt;/h2&gt;

&lt;p&gt;A good way to design the system is to write two API surfaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public API&lt;/strong&gt;&lt;br&gt;
policyVersion()&lt;br&gt;
policyCommitment()&lt;br&gt;
executionCommitment()&lt;br&gt;
&lt;strong&gt;Confidential API&lt;/strong&gt;&lt;br&gt;
evaluate()&lt;br&gt;
getPrivatePolicy()&lt;br&gt;
setPrivatePolicy()&lt;br&gt;
getInternalRiskState()&lt;/p&gt;

&lt;p&gt;The public interface answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What can everyone know?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The confidential interface answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should authorized participants know?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction is useful well beyond treasuries.&lt;/p&gt;
&lt;h2&gt;
  
  
  37. Client-Side Sapphire Integration
&lt;/h2&gt;

&lt;p&gt;For TypeScript applications, Oasis provides packages for Sapphire-aware clients. For ethers v6, the official integration is @oasisprotocol/sapphire-ethers-v6, which wraps providers and signers so calls and transactions can be encrypted appropriately.&lt;/p&gt;

&lt;p&gt;Install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ethers
npm &lt;span class="nb"&gt;install&lt;/span&gt; @oasisprotocol/sapphire-ethers-v6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Wallet&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;wrapEthersProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;wrapEthersSigner&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@oasisprotocol/sapphire-ethers-v6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SAPPHIRE_RPC&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;wrapEthersProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the signer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawSigner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rawProvider&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;wrapEthersSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawSigner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is important because ordinary EVM tooling isn't automatically enough for confidential transaction handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  38. Requesting Authorization
&lt;/h2&gt;

&lt;p&gt;Our TypeScript application can now construct a request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;USDC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseUnits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;keccak256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;toUtf8Bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The returned authorization is passed to the execution gateway.&lt;/p&gt;

&lt;p&gt;The public execution layer never needs the complete policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  39. Network Configuration
&lt;/h2&gt;

&lt;p&gt;For development, Oasis provides Sapphire Localnet and Testnet. The current official documentation lists:&lt;/p&gt;

&lt;p&gt;Sapphire Mainnet: 23294&lt;br&gt;
Sapphire Testnet: 23295&lt;br&gt;
Sapphire Localnet: 23293&lt;/p&gt;

&lt;p&gt;and documents the corresponding RPC configuration.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sapphire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23294&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rpcUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SAPPHIRE_RPC&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For local development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localSapphire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23293&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rpcUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8545&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not put real production secrets or production state on Sapphire Testnet. Oasis explicitly warns that Testnet state can be wiped and that confidentiality there should not be treated as guaranteed.&lt;/p&gt;

&lt;h2&gt;
  
  
  40. Running Sapphire Localnet
&lt;/h2&gt;

&lt;p&gt;For fast development, Oasis provides a local Sapphire environment through Docker.&lt;/p&gt;

&lt;p&gt;A typical workflow is:&lt;/p&gt;

&lt;p&gt;docker run \&lt;br&gt;
  -it \&lt;br&gt;
  -p 8544-8548:8544-8548 \&lt;br&gt;
  ghcr.io/oasisprotocol/sapphire-localnet&lt;/p&gt;

&lt;p&gt;Then point your test client at:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:8545" rel="noopener noreferrer"&gt;http://localhost:8545&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The advantage is that you can repeatedly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deploy&lt;/li&gt;
&lt;li&gt;test&lt;/li&gt;
&lt;li&gt;destroy&lt;/li&gt;
&lt;li&gt;restart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without depending on a public testnet.&lt;/p&gt;
&lt;h2&gt;
  
  
  41. Deployment
&lt;/h2&gt;

&lt;p&gt;Your foundry.toml can contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[profile.default]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src"&lt;/span&gt;
&lt;span class="py"&gt;script&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"script"&lt;/span&gt;
&lt;span class="py"&gt;test&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;
&lt;span class="py"&gt;out&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"out"&lt;/span&gt;
&lt;span class="py"&gt;libs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="py"&gt;solc_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8.24"&lt;/span&gt;
&lt;span class="py"&gt;optimizer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;optimizer_runs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;forge build&lt;/p&gt;

&lt;p&gt;Run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;forge test -vvv&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once the local tests pass, deploy to Sapphire Testnet.&lt;/p&gt;

&lt;p&gt;The exact RPC and account configuration should come from the current Oasis network documentation rather than hardcoding old endpoints into the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  42. Contract Verification
&lt;/h2&gt;

&lt;p&gt;A production-oriented repository shouldn't stop at "deployment successful".&lt;/p&gt;

&lt;p&gt;Verify the contract source and metadata.&lt;/p&gt;

&lt;p&gt;Oasis documents Sourcify-based verification for Sapphire deployments, including selecting Sapphire Mainnet or Sapphire Testnet and providing the Foundry/Hardhat build information.&lt;/p&gt;

&lt;p&gt;That matters particularly for confidential systems.&lt;/p&gt;

&lt;p&gt;People need to be able to inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what code is running&lt;/li&gt;
&lt;li&gt;what compiler version was used&lt;/li&gt;
&lt;li&gt;what dependencies were included&lt;/li&gt;
&lt;li&gt;what deployment corresponds to which policy version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confidentiality should never become an excuse for opaque engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  43. Threat Model
&lt;/h2&gt;

&lt;p&gt;Now let's attack our own system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Threat 1&lt;/strong&gt;: Compromised operator&lt;/p&gt;

&lt;p&gt;The operator can request actions.&lt;/p&gt;

&lt;p&gt;But the policy engine limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;asset&lt;/li&gt;
&lt;li&gt;recipient&lt;/li&gt;
&lt;li&gt;amount&lt;/li&gt;
&lt;li&gt;frequency&lt;/li&gt;
&lt;li&gt;policy state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the operator should not automatically be able to drain the treasury.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Threat 2&lt;/strong&gt;: Replay&lt;/p&gt;

&lt;p&gt;Mitigated by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requestId&lt;/li&gt;
&lt;li&gt;nonce&lt;/li&gt;
&lt;li&gt;commitment&lt;/li&gt;
&lt;li&gt;consumedRequests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Threat 3&lt;/strong&gt;: Stale authorization&lt;/p&gt;

&lt;p&gt;Mitigated by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expiresAt&lt;/li&gt;
&lt;li&gt;policyVersion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Threat 4&lt;/strong&gt;: Emergency abuse&lt;/p&gt;

&lt;p&gt;Mitigated by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maximum duration&lt;/li&gt;
&lt;li&gt;restricted limits&lt;/li&gt;
&lt;li&gt;automatic expiry&lt;/li&gt;
&lt;li&gt;separate governance authority&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Threat 5&lt;/strong&gt;: Event leakage&lt;/p&gt;

&lt;p&gt;Mitigated by:&lt;/p&gt;

&lt;p&gt;commitments instead of private values&lt;/p&gt;

&lt;p&gt;and encrypted events where confidential event payloads are genuinely required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Threat 6&lt;/strong&gt;: Getter leakage&lt;/p&gt;

&lt;p&gt;Mitigated by:&lt;/p&gt;

&lt;p&gt;minimal public view API&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Threat 7&lt;/strong&gt;: Policy inference&lt;/p&gt;

&lt;p&gt;This is harder.&lt;/p&gt;

&lt;p&gt;Potential leakage can come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gas usage&lt;/li&gt;
&lt;li&gt;execution timing&lt;/li&gt;
&lt;li&gt;storage access patterns&lt;/li&gt;
&lt;li&gt;error behavior&lt;/li&gt;
&lt;li&gt;frequency of policy decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sapphire's documentation explicitly discusses these side-channel considerations.&lt;/p&gt;

&lt;p&gt;This is why a serious confidential application needs a threat model beyond Solidity reentrancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  44. What Sapphire Does Not Solve
&lt;/h2&gt;

&lt;p&gt;This is worth stating clearly.&lt;/p&gt;

&lt;p&gt;Sapphire does not magically solve:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bad business logic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn't solve:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;compromised governance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn't solve:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;malicious operators&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn't solve:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;incorrect authorization semantics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn't make an unsafe contract safe.&lt;/p&gt;

&lt;p&gt;Instead, it changes the confidentiality properties of the execution environment.&lt;/p&gt;

&lt;p&gt;The correct mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethereum EVM
    +
confidential execution
    +
encrypted state
    +
encrypted client communication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;/p&gt;

&lt;p&gt;Ethereum but everything is magically private.&lt;/p&gt;

&lt;p&gt;Oasis explicitly documents the differences developers need to account for when moving from Ethereum to Sapphire.&lt;/p&gt;

&lt;h2&gt;
  
  
  45. Why Keep the Treasury Public?
&lt;/h2&gt;

&lt;p&gt;You might ask:&lt;/p&gt;

&lt;p&gt;If the policy is private, why not keep the whole treasury on Sapphire?&lt;/p&gt;

&lt;p&gt;You could, depending on the application.&lt;/p&gt;

&lt;p&gt;But a hybrid architecture has an attractive property:&lt;/p&gt;

&lt;p&gt;private decision&lt;br&gt;
+&lt;br&gt;
public settlement&lt;/p&gt;

&lt;p&gt;The treasury can remain compatible with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;public explorers&lt;/li&gt;
&lt;li&gt;existing ERC-20 infrastructure&lt;/li&gt;
&lt;li&gt;accounting systems&lt;/li&gt;
&lt;li&gt;public reporting&lt;/li&gt;
&lt;li&gt;external auditors&lt;/li&gt;
&lt;li&gt;existing DeFi protocols&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The private component becomes the decision layer, rather than replacing the entire financial stack.&lt;/p&gt;
&lt;h2&gt;
  
  
  46. The Hybrid Architecture
&lt;/h2&gt;

&lt;p&gt;The resulting design is:&lt;/p&gt;

&lt;p&gt;This is the core pattern of the tutorial.&lt;/p&gt;
&lt;h2&gt;
  
  
  47. Policy as a Product
&lt;/h2&gt;

&lt;p&gt;Once this works, the policy engine can become more general.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TreasuryPolicy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;we can build:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PolicyEngine&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;with rules such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allow(asset, recipient, amount)&lt;/li&gt;
&lt;li&gt;allow(strategy, market, exposure)&lt;/li&gt;
&lt;li&gt;allow(operator, action)&lt;/li&gt;
&lt;li&gt;allow(grant, recipient, budget)&lt;/li&gt;
&lt;li&gt;allow(payment, vendor, amount)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you're not building a treasury contract.&lt;/p&gt;

&lt;p&gt;You're building a confidential authorization layer.&lt;/p&gt;

&lt;p&gt;That's a much more general primitive.&lt;/p&gt;
&lt;h2&gt;
  
  
  48. Extending the Policy Model
&lt;/h2&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Rule {
    bytes32 ruleId;
    uint256 limit;
    uint64 validFrom;
    uint64 validUntil;
    bool enabled;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mapping(bytes32 =&amp;gt; Rule) private rules;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A request can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rule ID&lt;/li&gt;
&lt;li&gt;action type&lt;/li&gt;
&lt;li&gt;asset&lt;/li&gt;
&lt;li&gt;recipient&lt;/li&gt;
&lt;li&gt;amount&lt;/li&gt;
&lt;li&gt;context&lt;/li&gt;
&lt;li&gt;nonce&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The policy engine then evaluates:&lt;/p&gt;

&lt;p&gt;request&lt;br&gt;
+&lt;br&gt;
private rules&lt;br&gt;
+&lt;/p&gt;
&lt;h1&gt;
  
  
  private state
&lt;/h1&gt;

&lt;p&gt;authorization&lt;/p&gt;

&lt;p&gt;This resembles a traditional enterprise policy engine.&lt;/p&gt;

&lt;p&gt;Except the enforcement point is onchain.&lt;/p&gt;
&lt;h2&gt;
  
  
  49. Multi-Stage Authorization
&lt;/h2&gt;

&lt;p&gt;We can go further.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;operator → policy → execution&lt;/p&gt;

&lt;p&gt;use:&lt;/p&gt;

&lt;p&gt;operator&lt;br&gt;
    ↓&lt;br&gt;
risk policy&lt;br&gt;
    ↓&lt;br&gt;
budget policy&lt;br&gt;
    ↓&lt;br&gt;
emergency policy&lt;br&gt;
    ↓&lt;br&gt;
execution policy&lt;br&gt;
    ↓&lt;br&gt;
authorization&lt;/p&gt;

&lt;p&gt;Each layer can have a separate policy version.&lt;/p&gt;

&lt;p&gt;That gives auditors a much richer model.&lt;/p&gt;
&lt;h2&gt;
  
  
  50. Confidential Policies for Organizations
&lt;/h2&gt;

&lt;p&gt;This pattern becomes especially interesting for organizations that don't want to publish operational rules.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Market maker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inventory thresholds&lt;/li&gt;
&lt;li&gt;venue preferences&lt;/li&gt;
&lt;li&gt;rebalance bands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Public:&lt;/p&gt;

&lt;p&gt;actual settlements&lt;br&gt;
Grant organization&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;evaluation thresholds&lt;/li&gt;
&lt;li&gt;reviewer assignments&lt;/li&gt;
&lt;li&gt;internal limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Public&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approved grants&lt;/li&gt;
&lt;li&gt;Infrastructure provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Private&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal spending policies&lt;/li&gt;
&lt;li&gt;operator capabilities&lt;/li&gt;
&lt;li&gt;failover thresholds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Public&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;settlement events&lt;/li&gt;
&lt;li&gt;DAO treasury&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Private&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;operational policy&lt;/li&gt;
&lt;li&gt;counterparty classifications&lt;/li&gt;
&lt;li&gt;emergency thresholds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Public&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;final treasury movements&lt;/p&gt;

&lt;p&gt;The same architecture applies to all four.&lt;/p&gt;
&lt;h2&gt;
  
  
  51. What About Governance?
&lt;/h2&gt;

&lt;p&gt;Governance itself can remain public.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Proposal #182&lt;br&gt;
    ↓&lt;br&gt;
Policy commitment changed&lt;br&gt;
    ↓&lt;br&gt;
Version 18 activated&lt;/p&gt;

&lt;p&gt;The actual contents of the operational policy can remain confidential until disclosure is appropriate.&lt;/p&gt;

&lt;p&gt;This creates a useful separation:&lt;/p&gt;

&lt;p&gt;Governance transparency&lt;br&gt;
        ≠&lt;br&gt;
Operational transparency&lt;/p&gt;

&lt;p&gt;A DAO can disclose:&lt;/p&gt;

&lt;p&gt;Governance approved policy version 18.&lt;/p&gt;

&lt;p&gt;without necessarily disclosing:&lt;/p&gt;

&lt;p&gt;every operational threshold contained in version 18.&lt;/p&gt;
&lt;h2&gt;
  
  
  52. The Audit Model
&lt;/h2&gt;

&lt;p&gt;An auditor can be granted access to the confidential policy.&lt;/p&gt;

&lt;p&gt;They receive:&lt;/p&gt;

&lt;p&gt;Policy v18&lt;/p&gt;

&lt;p&gt;Then calculate:&lt;/p&gt;

&lt;p&gt;commitment(policy v18)&lt;/p&gt;

&lt;p&gt;and compare it to:&lt;/p&gt;

&lt;p&gt;onchain commitment(v18)&lt;/p&gt;

&lt;p&gt;If they match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Policy disclosed
        ↓
Hash
        ↓
Matches historical commitment
        ↓
Policy integrity established
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a powerful property.&lt;/p&gt;

&lt;p&gt;The system doesn't require everyone to know the policy.&lt;/p&gt;

&lt;p&gt;It only requires that the organization cannot later pretend that a different policy was active.&lt;/p&gt;

&lt;h2&gt;
  
  
  53. Why This Is Different From a Normal Multisig
&lt;/h2&gt;

&lt;p&gt;A multisig answers:&lt;/p&gt;

&lt;p&gt;Did enough authorized humans sign this transaction?&lt;/p&gt;

&lt;p&gt;Our policy engine answers:&lt;/p&gt;

&lt;p&gt;Did this action satisfy a private, versioned, machine-enforced policy?&lt;/p&gt;

&lt;p&gt;Those are different problems.&lt;/p&gt;

&lt;p&gt;You can combine them.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Private Policy
      ↓
Authorization
      ↓
3-of-5 Multisig
      ↓
Public Treasury
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the multisig provides human governance while the confidential policy engine provides operational constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  54. A Stronger Architecture
&lt;/h2&gt;

&lt;p&gt;The production version could therefore look like:&lt;/p&gt;

&lt;p&gt;Now compromise of one operator isn't enough.&lt;/p&gt;

&lt;p&gt;Compromise of one signer isn't necessarily enough.&lt;/p&gt;

&lt;p&gt;And compromise of the public gateway doesn't reveal the confidential policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  55. Operational Monitoring
&lt;/h2&gt;

&lt;p&gt;A production system should monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;policy version changes&lt;/li&gt;
&lt;li&gt;emergency activation&lt;/li&gt;
&lt;li&gt;emergency expiry&lt;/li&gt;
&lt;li&gt;authorization volume&lt;/li&gt;
&lt;li&gt;failed authorization attempts&lt;/li&gt;
&lt;li&gt;repeated requests&lt;/li&gt;
&lt;li&gt;gateway failures&lt;/li&gt;
&lt;li&gt;unexpected recipients&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But again, don't accidentally publish sensitive information.&lt;/p&gt;

&lt;p&gt;A monitoring event might say:&lt;/p&gt;

&lt;p&gt;PolicyVersionChanged(18)&lt;/p&gt;

&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PolicyChanged(
    maxTransfer = 123456,
    internalRecipient = ...
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitoring needs to respect the same confidentiality boundary as the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  56. A Useful Rule
&lt;/h2&gt;

&lt;p&gt;Here's the rule I'd use when designing a Sapphire application:&lt;/p&gt;

&lt;p&gt;If a value is confidential, assume every output derived from that value may also be confidential.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;events&lt;/li&gt;
&lt;li&gt;errors&lt;/li&gt;
&lt;li&gt;gas&lt;/li&gt;
&lt;li&gt;timing&lt;/li&gt;
&lt;li&gt;return values&lt;/li&gt;
&lt;li&gt;storage access&lt;/li&gt;
&lt;li&gt;authorization frequency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the biggest differences between writing ordinary Solidity and writing confidential Solidity.&lt;/p&gt;

&lt;h2&gt;
  
  
  57. Production Checklist
&lt;/h2&gt;

&lt;p&gt;Before deploying a system like this, I'd run through:&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Contract security&lt;br&gt;
*&lt;/em&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reentrancy protection&lt;/li&gt;
&lt;li&gt; Replay protection&lt;/li&gt;
&lt;li&gt; Authorization expiry&lt;/li&gt;
&lt;li&gt; Domain separation&lt;/li&gt;
&lt;li&gt; Policy versioning&lt;/li&gt;
&lt;li&gt; Explicit role separation&lt;/li&gt;
&lt;li&gt; Emergency expiry&lt;/li&gt;
&lt;li&gt; Maximum emergency duration&lt;/li&gt;
&lt;li&gt; No arbitrary delegatecall&lt;/li&gt;
&lt;li&gt; No unrestricted asset approval&lt;/li&gt;
&lt;li&gt; Safe ERC-20 handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Confidentiality&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; No private data in normal events&lt;/li&gt;
&lt;li&gt; No sensitive public getters&lt;/li&gt;
&lt;li&gt; No sensitive revert strings&lt;/li&gt;
&lt;li&gt; Constant-shaped branches where necessary&lt;/li&gt;
&lt;li&gt; Storage access patterns reviewed&lt;/li&gt;
&lt;li&gt; Gas leakage considered&lt;/li&gt;
&lt;li&gt; Client-side encryption configured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Governance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Policy changes are versioned&lt;/li&gt;
&lt;li&gt; Policy commitments are recorded&lt;/li&gt;
&lt;li&gt; Emergency powers are bounded&lt;/li&gt;
&lt;li&gt; Policy administrator is separate from operator&lt;/li&gt;
&lt;li&gt; Public disclosure process exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Sapphire-aware client&lt;/li&gt;
&lt;li&gt; Key management&lt;/li&gt;
&lt;li&gt; RPC redundancy&lt;/li&gt;
&lt;li&gt; Contract verification&lt;/li&gt;
&lt;li&gt; Deployment reproducibility&lt;/li&gt;
&lt;li&gt; Monitoring&lt;/li&gt;
&lt;li&gt; Incident response&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  58. The Most Important Design Principle
&lt;/h2&gt;

&lt;p&gt;The biggest lesson from this architecture isn't actually about Sapphire.&lt;/p&gt;

&lt;p&gt;It's about separating confidentiality from authority.&lt;/p&gt;

&lt;p&gt;A private policy doesn't automatically make an authority trustworthy.&lt;/p&gt;

&lt;p&gt;A public commitment doesn't automatically make an organization honest.&lt;/p&gt;

&lt;p&gt;A multisig doesn't automatically make a policy correct.&lt;/p&gt;

&lt;p&gt;Each mechanism solves a different problem.&lt;/p&gt;

&lt;p&gt;Think of the system as four independent properties:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             SECURITY MODEL

    ┌─────────────────────────┐
    │                         │
    │       AUTHORITY         │
    │   Who is allowed?       │
    │                         │
    └────────────┬────────────┘
                 │
    ┌────────────▼────────────┐
    │                         │
    │        POLICY           │
    │   What is permitted?    │
    │                         │
    └────────────┬────────────┘
                 │
    ┌────────────▼────────────┐
    │                         │
    │     CONFIDENTIALITY     │
    │   Who can know it?      │
    │                         │
    └────────────┬────────────┘
                 │
    ┌────────────▼────────────┐
    │                         │
    │       AUDITABILITY      │
    │   What can be proven?   │
    │                         │
    └─────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Oasis Sapphire is particularly useful for the third property.&lt;/p&gt;

&lt;p&gt;But a serious protocol needs all four.&lt;/p&gt;
&lt;h2&gt;
  
  
  59. Where This Pattern Gets Interesting
&lt;/h2&gt;

&lt;p&gt;Once you have:&lt;/p&gt;

&lt;p&gt;private policy&lt;br&gt;
+&lt;br&gt;
public commitment&lt;br&gt;
+&lt;br&gt;
bounded authorization&lt;br&gt;
+&lt;br&gt;
public settlement&lt;/p&gt;

&lt;p&gt;you can start building much more sophisticated systems.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidential treasury routing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The routing policy is private, but final settlement is public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private grant allocation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Eligibility rules remain private while successful grants become publicly auditable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Institutional transaction policy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Employees can initiate transactions without learning the complete treasury policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private market operations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Strategy constraints remain confidential while settlement remains verifiable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DAO emergency controls&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Emergency thresholds remain private while emergency activation and expiry are publicly observable.&lt;/p&gt;

&lt;p&gt;The common architecture is always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRIVATE DECISION
       ↓
CRYPTOGRAPHIC COMMITMENT
       ↓
PUBLIC EXECUTION
       ↓
PUBLIC AUDIT TRAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  60. Final Architecture
&lt;/h2&gt;

&lt;p&gt;At the end of the tutorial, we have built:&lt;/p&gt;

&lt;p&gt;The key property is that the public chain doesn't need to understand every private decision.&lt;/p&gt;

&lt;p&gt;It only needs to enforce the final authorization boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most blockchain systems treat transparency as a binary property:&lt;/p&gt;

&lt;p&gt;public chain = transparent.&lt;/p&gt;

&lt;p&gt;But that's not really how production systems work.&lt;/p&gt;

&lt;p&gt;There are multiple layers of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;assets&lt;/li&gt;
&lt;li&gt;transactions&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;policies&lt;/li&gt;
&lt;li&gt;strategies&lt;/li&gt;
&lt;li&gt;identities&lt;/li&gt;
&lt;li&gt;relationships&lt;/li&gt;
&lt;li&gt;internal state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not all of those need the same visibility.&lt;/p&gt;

&lt;p&gt;A treasury can be transparent about its assets while keeping operational policy confidential.&lt;/p&gt;

&lt;p&gt;A DAO can publicly record that a policy changed without publishing every internal parameter.&lt;/p&gt;

&lt;p&gt;An auditor can later verify that a disclosed policy corresponds to the policy commitment that existed when a transaction was authorized.&lt;/p&gt;

&lt;p&gt;And an emergency mechanism can be technically bounded instead of relying on documentation saying:&lt;/p&gt;

&lt;p&gt;"This function should only be used in emergencies."&lt;/p&gt;

&lt;p&gt;That's the architectural idea behind the system we built here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        PRIVATE
           │
           ▼
   Policy Evaluation
           │
           ▼
    Authorization
           │
           ▼
     Commitment
           │
           ▼
        PUBLIC
           │
           ▼
   Treasury Settlement
           │
           ▼
     Audit Trail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Oasis Sapphire is useful here because it provides an EVM-compatible environment where contract state and client communication can be confidential, while still allowing developers to use familiar Solidity and Ethereum tooling.&lt;/p&gt;

&lt;p&gt;But the important lesson isn't simply:&lt;/p&gt;

&lt;p&gt;"Put your treasury on Sapphire."&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;p&gt;Design confidentiality as a boundary around sensitive decision-making, while keeping the resulting financial actions independently auditable.&lt;/p&gt;

&lt;p&gt;That distinction makes confidential blockchain applications much more interesting than simply hiding balances.&lt;/p&gt;

&lt;p&gt;Repository Structure&lt;/p&gt;

&lt;p&gt;The complete project from this tutorial should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confidential-treasury-policy/
│
├── src/
│   ├── ConfidentialPolicy.sol
│   ├── PolicyTypes.sol
│   ├── CommitmentRegistry.sol
│   ├── ExecutionGateway.sol
│   ├── Treasury.sol
│   └── interfaces/
│       ├── IPolicyEngine.sol
│       └── ITreasury.sol
│
├── script/
│   ├── Deploy.s.sol
│   ├── ConfigurePolicy.s.sol
│   └── RegisterGateway.s.sol
│
├── test/
│   ├── PolicyEngine.t.sol
│   ├── Authorization.t.sol
│   ├── Emergency.t.sol
│   ├── Replay.t.sol
│   ├── Confidentiality.t.sol
│   └── Invariants.t.sol
│
├── integration/
│   ├── sapphire.ts
│   ├── policy.ts
│   └── execution.ts
│
├── foundry.toml
├── package.json
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A natural next step would be to extend the prototype with multisig approval, encrypted events, policy disclosure proofs, and a public execution gateway on Base, turning the tutorial into a complete end-to-end treasury system rather than a single Sapphire contract.&lt;/p&gt;

&lt;p&gt;Further reading&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/sapphire/develop/" rel="noopener noreferrer"&gt;Oasis Sapphire developer documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/sapphire/develop/concept/" rel="noopener noreferrer"&gt;Sapphire concepts and confidential state model&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/sapphire/develop/security/" rel="noopener noreferrer"&gt;Sapphire security considerations&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/sapphire/develop/encrypted-events/" rel="noopener noreferrer"&gt;Sapphire encrypted events&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/sapphire/network/" rel="noopener noreferrer"&gt;Sapphire network information&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.oasis.io/build/tools/foundry/" rel="noopener noreferrer"&gt;Sapphire Foundry tooling&lt;/a&gt;&lt;/p&gt;

</description>
      <category>web3</category>
      <category>privacy</category>
      <category>cryptocurrency</category>
      <category>oasisnetwork</category>
    </item>
    <item>
      <title>Building a Confidential OTC Settlement Protocol with Oasis Sapphire &amp; Base</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Mon, 20 Jul 2026 07:06:17 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-otc-settlement-protocol-with-oasis-sapphire-base-23ki</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-otc-settlement-protocol-with-oasis-sapphire-base-23ki</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Learn how to build a privacy-preserving OTC trading protocol where counterparties negotiate confidentially using Oasis Sapphire while settling trades transparently on Base.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Large cryptocurrency trades rarely happen on decentralized exchanges.&lt;/p&gt;

&lt;p&gt;When someone wants to swap a few hundred dollars worth of ETH for USDC, an automated market maker like Uniswap works exceptionally well. Liquidity is abundant, settlement is immediate, and the entire process can be completed in a single transaction.&lt;/p&gt;

&lt;p&gt;The picture changes completely once the trade size reaches institutional territory.&lt;/p&gt;

&lt;p&gt;Imagine a DAO treasury planning to exchange &lt;strong&gt;500,000 USDC&lt;/strong&gt; for ETH.&lt;/p&gt;

&lt;p&gt;Executing that transaction through a public liquidity pool immediately reveals valuable information to the entire market. Arbitrage bots begin simulating profitable routes before the transaction is finalized. Market makers widen spreads. Searchers compete to extract value from the pending order, while other traders infer the treasury's strategy simply by observing the transaction.&lt;/p&gt;

&lt;p&gt;The protocol itself is functioning exactly as designed.&lt;/p&gt;

&lt;p&gt;The problem is that transparency and negotiation don't always belong together.&lt;/p&gt;

&lt;p&gt;This is precisely why Over-the-Counter (OTC) trading exists.&lt;/p&gt;

&lt;p&gt;Rather than exposing an order to an entire market, two counterparties negotiate privately before agreeing on a final settlement price. Only after both parties reach an agreement is the trade executed.&lt;/p&gt;

&lt;p&gt;Traditional financial institutions have relied on this model for decades because confidentiality reduces information leakage and allows large transactions to occur without unnecessarily moving markets.&lt;/p&gt;

&lt;p&gt;Public blockchains introduce an interesting challenge.&lt;/p&gt;

&lt;p&gt;How do you preserve confidential negotiations while still benefiting from decentralized settlement?&lt;/p&gt;

&lt;p&gt;This is where Oasis Sapphire becomes particularly interesting.&lt;/p&gt;

&lt;p&gt;Instead of treating confidentiality as an application-layer concern, Sapphire provides confidential smart contract execution directly within an EVM-compatible environment. Sensitive contract state remains protected while developers continue writing familiar Solidity code.&lt;/p&gt;

&lt;p&gt;In this tutorial we'll build a simplified OTC trading protocol that combines confidential negotiation on Oasis Sapphire with transparent settlement on Base.&lt;/p&gt;

&lt;p&gt;Rather than building another decentralized exchange, we'll focus on the negotiation process itself.&lt;/p&gt;

&lt;p&gt;By the end of this guide you'll have built a protocol capable of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating confidential OTC trade offers&lt;/li&gt;
&lt;li&gt;Receiving private counterparty quotes&lt;/li&gt;
&lt;li&gt;Accepting offers without revealing negotiations publicly&lt;/li&gt;
&lt;li&gt;Escrowing assets before settlement&lt;/li&gt;
&lt;li&gt;Executing the final settlement on Base&lt;/li&gt;
&lt;li&gt;Cancelling expired orders safely&lt;/li&gt;
&lt;li&gt;Preventing replay attacks through signed messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although the protocol we'll build is intentionally simplified for educational purposes, its architecture mirrors patterns commonly found in production financial systems.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding the Problem
&lt;/h1&gt;

&lt;p&gt;Before writing a single line of Solidity, it's worth asking an important question.&lt;/p&gt;

&lt;p&gt;Why don't institutions simply use AMMs for everything?&lt;/p&gt;

&lt;p&gt;After all, decentralized exchanges already provide liquidity, transparent pricing, and permissionless access.&lt;/p&gt;

&lt;p&gt;The answer comes down to market impact.&lt;/p&gt;

&lt;p&gt;Suppose Alice wants to purchase &lt;strong&gt;400 ETH&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If that trade is submitted directly to a public liquidity pool, several things happen almost immediately.&lt;/p&gt;

&lt;p&gt;First, the pending transaction becomes visible to sophisticated searchers monitoring the network.&lt;/p&gt;

&lt;p&gt;Next, those searchers estimate the price impact by simulating the trade against current liquidity.&lt;/p&gt;

&lt;p&gt;If profitable opportunities exist, competing transactions are constructed before Alice's order has even been finalized.&lt;/p&gt;

&lt;p&gt;Even if nobody explicitly frontruns the transaction, the market itself now possesses information that previously existed only inside Alice's trading strategy.&lt;/p&gt;

&lt;p&gt;That information has value.&lt;/p&gt;

&lt;p&gt;The larger the order becomes, the more valuable it becomes.&lt;/p&gt;

&lt;p&gt;The sequence below illustrates what happens inside a typical public execution environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram

participant Alice
participant Ethereum
participant Searcher
participant LiquidityPool

Alice-&amp;gt;&amp;gt;Ethereum: Submit Swap

Ethereum--&amp;gt;&amp;gt;Searcher: Pending Transaction

Searcher-&amp;gt;&amp;gt;Searcher: Simulate Trade

Searcher-&amp;gt;&amp;gt;LiquidityPool: Competing Transactions

LiquidityPool--&amp;gt;&amp;gt;Alice: Final Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing malicious has occurred.&lt;/p&gt;

&lt;p&gt;Every participant simply acted upon publicly available information.&lt;/p&gt;

&lt;p&gt;For large trades, however, that transparency introduces unnecessary information leakage.&lt;/p&gt;

&lt;p&gt;OTC trading avoids this problem entirely by moving negotiation away from the public market.&lt;/p&gt;

&lt;p&gt;Only the final agreement becomes visible.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Oasis Sapphire?
&lt;/h1&gt;

&lt;p&gt;One obvious question is why we need another blockchain at all.&lt;/p&gt;

&lt;p&gt;Couldn't we simply negotiate off-chain using encrypted messages?&lt;/p&gt;

&lt;p&gt;In some situations, yes.&lt;/p&gt;

&lt;p&gt;However, purely off-chain negotiation introduces several new trust assumptions.&lt;/p&gt;

&lt;p&gt;Who stores the offers?&lt;/p&gt;

&lt;p&gt;Who guarantees neither party modified the terms?&lt;/p&gt;

&lt;p&gt;How do we prove a particular offer existed if negotiations break down?&lt;/p&gt;

&lt;p&gt;How do multiple market makers compete fairly?&lt;/p&gt;

&lt;p&gt;A confidential smart contract solves these coordination problems.&lt;/p&gt;

&lt;p&gt;Instead of trusting a centralized OTC desk, participants submit encrypted offers into a shared protocol.&lt;/p&gt;

&lt;p&gt;The blockchain enforces protocol rules while keeping sensitive trading information confidential.&lt;/p&gt;

&lt;p&gt;From the outside, observers know an offer exists.&lt;/p&gt;

&lt;p&gt;They do &lt;strong&gt;not&lt;/strong&gt; learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which asset is being traded,&lt;/li&gt;
&lt;li&gt;the quoted price,&lt;/li&gt;
&lt;li&gt;the requested amount,&lt;/li&gt;
&lt;li&gt;acceptable settlement conditions,&lt;/li&gt;
&lt;li&gt;or which counterparty accepted the offer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the final settlement transaction becomes public.&lt;/p&gt;

&lt;p&gt;That distinction dramatically reduces information leakage while preserving decentralized execution.&lt;/p&gt;




&lt;h1&gt;
  
  
  System Overview
&lt;/h1&gt;

&lt;p&gt;Our protocol consists of five independent components.&lt;/p&gt;

&lt;p&gt;Each has a single responsibility.&lt;/p&gt;

&lt;p&gt;Separating these concerns keeps the contracts significantly easier to audit and makes future upgrades less risky.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR

User["Trader"]

Frontend["Next.js Frontend"]

SDK["TypeScript SDK"]

Registry["OTC Registry"]

Escrow["Escrow Contract"]

Coordinator["Settlement Coordinator"]

Base["Base Network"]

User --&amp;gt; Frontend

Frontend --&amp;gt; SDK

SDK --&amp;gt; Registry

Registry --&amp;gt; Coordinator

Coordinator --&amp;gt; Escrow

Escrow --&amp;gt; Base
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's briefly examine the role of each component.&lt;/p&gt;

&lt;h3&gt;
  
  
  OTC Registry
&lt;/h3&gt;

&lt;p&gt;The registry acts as the confidential marketplace.&lt;/p&gt;

&lt;p&gt;Every new offer enters the protocol through this contract.&lt;/p&gt;

&lt;p&gt;Rather than immediately transferring assets, users first describe the trade they would like to perform.&lt;/p&gt;

&lt;p&gt;Those details remain confidential until the offer is accepted.&lt;/p&gt;




&lt;h3&gt;
  
  
  Escrow Contract
&lt;/h3&gt;

&lt;p&gt;Once two counterparties agree on a trade, both assets must be secured before settlement begins.&lt;/p&gt;

&lt;p&gt;Instead of relying on trust between participants, the escrow contract temporarily locks both sides of the trade.&lt;/p&gt;

&lt;p&gt;Neither participant can withdraw funds unilaterally after acceptance.&lt;/p&gt;

&lt;p&gt;Only successful settlement or cancellation releases escrowed assets.&lt;/p&gt;




&lt;h3&gt;
  
  
  Settlement Coordinator
&lt;/h3&gt;

&lt;p&gt;The final contract coordinates settlement on Base.&lt;/p&gt;

&lt;p&gt;Once escrow conditions are satisfied, settlement becomes deterministic.&lt;/p&gt;

&lt;p&gt;This separation keeps negotiation logic isolated from execution logic, significantly simplifying auditing.&lt;/p&gt;




&lt;h1&gt;
  
  
  Project Architecture
&lt;/h1&gt;

&lt;p&gt;We'll build the project as a monorepo.&lt;/p&gt;

&lt;p&gt;Although our example remains relatively small, organizing the repository this way closely resembles production Solidity projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confidential-otc-protocol/

├── contracts/
│
│   OTCRegistry.sol
│
│   Escrow.sol
│
│   SettlementCoordinator.sol
│
│   libraries/
│
│      SignatureVerifier.sol
│
│      OfferHasher.sol
│
│
├── sdk/
│
│   otcClient.ts
│
│   signer.ts
│
│   quotes.ts
│
├── backend/
│
│   api.ts
│
│   relayer.ts
│
│
├── frontend/
│
│   app/
│
├── script/
│
├── test/
│
└── docs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throughout this tutorial we'll implement each directory individually before integrating everything into a complete protocol.&lt;/p&gt;

&lt;p&gt;By the end, you'll have a repository structured much more like a production codebase than a simple Solidity example.&lt;/p&gt;




&lt;h1&gt;
  
  
  Protocol Lifecycle
&lt;/h1&gt;

&lt;p&gt;Before diving into the implementation, it's useful to visualize the complete lifecycle of an OTC trade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stateDiagram-v2

[*] --&amp;gt; Draft

Draft --&amp;gt; Submitted

Submitted --&amp;gt; Quoted

Quoted --&amp;gt; Accepted

Accepted --&amp;gt; Escrowed

Escrowed --&amp;gt; Settled

Submitted --&amp;gt; Cancelled

Quoted --&amp;gt; Cancelled

Accepted --&amp;gt; Expired
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that settlement is only one stage of the protocol.&lt;/p&gt;

&lt;p&gt;Most of the protocol's complexity actually happens &lt;strong&gt;before&lt;/strong&gt; assets move.&lt;/p&gt;

&lt;p&gt;Negotiation, verification, escrow, and acceptance each introduce their own state transitions.&lt;/p&gt;

&lt;p&gt;Designing these transitions carefully is one of the easiest ways to eliminate entire classes of smart contract bugs before they ever appear.&lt;/p&gt;

&lt;p&gt;In the next section we'll begin implementing the protocol's core contract: &lt;strong&gt;OTCRegistry.sol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rather than jumping directly into business logic, we'll first design the underlying data model and explain why each field exists before writing any functions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Setting Up the Project
&lt;/h1&gt;

&lt;p&gt;Before writing any Solidity, let's create the project structure.&lt;/p&gt;

&lt;p&gt;We'll use &lt;strong&gt;Foundry&lt;/strong&gt; because it has become the de facto standard for professional Solidity development. It provides fast compilation, excellent testing utilities, built-in fuzzing, and straightforward deployment scripts.&lt;/p&gt;

&lt;p&gt;If you haven't already installed Foundry, you can do so with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://foundry.paradigm.xyz | bash
foundryup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;forge init confidential-otc-protocol

&lt;span class="nb"&gt;cd &lt;/span&gt;confidential-otc-protocol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll also install OpenZeppelin contracts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;forge &lt;span class="nb"&gt;install &lt;/span&gt;OpenZeppelin/openzeppelin-contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our project now looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confidential-otc-protocol/

├── lib/
│
├── script/
│
├── src/
│
├── test/
│
├── foundry.toml
│
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throughout the tutorial we'll gradually replace the default template with our protocol implementation.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Foundry?
&lt;/h1&gt;

&lt;p&gt;Hardhat remains an excellent framework, but Foundry offers several advantages for protocol development.&lt;/p&gt;

&lt;p&gt;First, tests execute considerably faster because they're written directly in Solidity.&lt;/p&gt;

&lt;p&gt;Second, fuzz testing is built into the framework instead of requiring external plugins.&lt;/p&gt;

&lt;p&gt;Finally, cheatcodes make it straightforward to simulate different users, timestamps, balances, and blockchain conditions.&lt;/p&gt;

&lt;p&gt;Since we're building financial infrastructure, confidence in our testing environment matters just as much as confidence in the contracts themselves.&lt;/p&gt;




&lt;h1&gt;
  
  
  Designing the Registry
&lt;/h1&gt;

&lt;p&gt;Most blockchain tutorials begin with functions.&lt;/p&gt;

&lt;p&gt;Production protocols begin with data.&lt;/p&gt;

&lt;p&gt;The quality of a smart contract is largely determined by how well its state is modeled.&lt;/p&gt;

&lt;p&gt;Changing storage layouts after deployment is difficult, especially once external integrations exist.&lt;/p&gt;

&lt;p&gt;Before writing a single function, we should understand exactly what information our registry needs to remember.&lt;/p&gt;

&lt;p&gt;An OTC offer answers a simple question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What is one party willing to trade?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds simple, but the protocol needs considerably more information than just two token addresses.&lt;/p&gt;

&lt;p&gt;Our registry must remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who created the offer,&lt;/li&gt;
&lt;li&gt;which asset they're selling,&lt;/li&gt;
&lt;li&gt;which asset they expect,&lt;/li&gt;
&lt;li&gt;the amount offered,&lt;/li&gt;
&lt;li&gt;the minimum amount they'll accept,&lt;/li&gt;
&lt;li&gt;when the offer expires,&lt;/li&gt;
&lt;li&gt;whether it's still active,&lt;/li&gt;
&lt;li&gt;and whether somebody has already accepted it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Representing these pieces of information explicitly makes later contract logic much easier to understand.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating the Offer Model
&lt;/h1&gt;

&lt;p&gt;Inside &lt;code&gt;src/&lt;/code&gt;, create a file named &lt;code&gt;OTCRegistry.sol&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We'll start with the data model before implementing any business logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract OTCRegistry {

    enum OfferStatus {
        Open,
        Accepted,
        Cancelled,
        Settled,
        Expired
    }

    struct Offer {

        address maker;

        address tokenOffered;

        address tokenRequested;

        uint256 amountOffered;

        uint256 minimumAmountRequested;

        uint256 expiry;

        uint256 nonce;

        OfferStatus status;

    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although this contract contains only a few lines of Solidity, there's already quite a bit happening.&lt;/p&gt;

&lt;p&gt;Instead of storing offers as loosely connected mappings, we've grouped related information into a single structure.&lt;/p&gt;

&lt;p&gt;This makes the protocol easier to reason about because every offer becomes a self-contained object.&lt;/p&gt;

&lt;p&gt;Let's examine each field.&lt;/p&gt;




&lt;h2&gt;
  
  
  Maker
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;address maker;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;maker&lt;/code&gt; is the wallet that created the offer.&lt;/p&gt;

&lt;p&gt;This address becomes important throughout the protocol.&lt;/p&gt;

&lt;p&gt;Only the maker can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cancel an open offer,&lt;/li&gt;
&lt;li&gt;update certain parameters (before acceptance),&lt;/li&gt;
&lt;li&gt;or withdraw escrow if settlement fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than checking ownership through complex access-control mechanisms, we'll simply compare &lt;code&gt;msg.sender&lt;/code&gt; against this stored address.&lt;/p&gt;




&lt;h2&gt;
  
  
  Offered Token
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;address tokenOffered;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This represents the asset currently owned by the maker.&lt;/p&gt;

&lt;p&gt;For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USDC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registry itself won't hold these tokens.&lt;/p&gt;

&lt;p&gt;Instead, it records what the maker intends to escrow later in the protocol.&lt;/p&gt;

&lt;p&gt;Keeping negotiation separate from asset custody makes the protocol significantly easier to audit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requested Token
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;address tokenRequested;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the asset the maker expects in return.&lt;/p&gt;

&lt;p&gt;Together,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenOffered

↓

tokenRequested
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;define the trading pair.&lt;/p&gt;

&lt;p&gt;Unlike AMMs, we're not computing prices algorithmically.&lt;/p&gt;

&lt;p&gt;The maker decides exactly what exchange they're willing to accept.&lt;/p&gt;




&lt;h2&gt;
  
  
  Offered Amount
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 amountOffered;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This field specifies how many units of the offered token the maker wishes to trade.&lt;/p&gt;

&lt;p&gt;For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;500,000 USDC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The protocol intentionally avoids floating-point arithmetic.&lt;/p&gt;

&lt;p&gt;Every amount is stored using the token's smallest denomination.&lt;/p&gt;

&lt;p&gt;For ERC-20 tokens, that typically means accounting for decimals within the frontend or SDK.&lt;/p&gt;




&lt;h2&gt;
  
  
  Minimum Requested Amount
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 minimumAmountRequested;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This field acts as protection against unfavorable execution.&lt;/p&gt;

&lt;p&gt;Suppose Alice wants to exchange&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;500,000 USDC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for at least&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;165 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a counterparty proposes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;160 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the protocol should reject the trade automatically.&lt;/p&gt;

&lt;p&gt;Encoding these constraints directly into the offer ensures every accepted trade satisfies the maker's original conditions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Expiry
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 expiry;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No financial offer should remain valid forever.&lt;/p&gt;

&lt;p&gt;Market conditions change.&lt;/p&gt;

&lt;p&gt;Token prices fluctuate.&lt;/p&gt;

&lt;p&gt;Liquidity disappears.&lt;/p&gt;

&lt;p&gt;Rather than relying on users to manually cancel stale offers, we'll associate every offer with an expiration timestamp.&lt;/p&gt;

&lt;p&gt;Any interaction after that timestamp becomes invalid.&lt;/p&gt;

&lt;p&gt;This single field eliminates an entire class of replay attacks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nonce
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 nonce;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One subtle problem appears once signatures enter the picture.&lt;/p&gt;

&lt;p&gt;Suppose Alice signs an offer today.&lt;/p&gt;

&lt;p&gt;What's stopping somebody from submitting the exact same signed payload next month?&lt;/p&gt;

&lt;p&gt;The answer is nonces.&lt;/p&gt;

&lt;p&gt;Each new offer consumes a unique value.&lt;/p&gt;

&lt;p&gt;Once that value has been used, the protocol refuses to accept another offer carrying the same nonce.&lt;/p&gt;

&lt;p&gt;We'll implement this protection shortly.&lt;/p&gt;




&lt;h1&gt;
  
  
  Organizing Storage
&lt;/h1&gt;

&lt;p&gt;With the data model complete, we can decide how offers will actually be stored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(uint256 =&amp;gt; Offer) private offers;

uint256 public offerCounter;

mapping(address =&amp;gt; uint256) public nonces;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each mapping serves a different purpose.&lt;/p&gt;

&lt;p&gt;The first stores every offer ever created.&lt;/p&gt;

&lt;p&gt;The second generates unique identifiers.&lt;/p&gt;

&lt;p&gt;Finally, the third keeps track of each user's latest nonce.&lt;/p&gt;

&lt;p&gt;Separating these concerns keeps lookups straightforward while avoiding nested mappings that quickly become difficult to maintain.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Sequential IDs?
&lt;/h1&gt;

&lt;p&gt;Earlier we discussed deterministic hashes.&lt;/p&gt;

&lt;p&gt;Why switch to numeric identifiers?&lt;/p&gt;

&lt;p&gt;Because this protocol has different requirements.&lt;/p&gt;

&lt;p&gt;Unlike signed intents that may exist off-chain before submission, OTC offers originate inside the protocol itself.&lt;/p&gt;

&lt;p&gt;Sequential identifiers offer several practical advantages.&lt;/p&gt;

&lt;p&gt;They make indexing simpler.&lt;/p&gt;

&lt;p&gt;They're cheaper to generate than hashes.&lt;/p&gt;

&lt;p&gt;And they're considerably easier to reference from frontends and block explorers.&lt;/p&gt;

&lt;p&gt;Offer #421 is much easier for developers to work with than&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0xb7e13b8ecb5e...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, we'll still use cryptographic hashes later for signatures.&lt;/p&gt;

&lt;p&gt;But externally, sequential identifiers make the protocol much friendlier to use.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating Offers
&lt;/h1&gt;

&lt;p&gt;With storage in place, we can finally implement our first state-changing function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function createOffer(

    address tokenOffered,

    address tokenRequested,

    uint256 amountOffered,

    uint256 minimumAmountRequested,

    uint256 expiry

) external returns (uint256 offerId) {

    require(expiry &amp;gt; block.timestamp, "Offer already expired");

    offerId = ++offerCounter;

    offers[offerId] = Offer({

        maker: msg.sender,

        tokenOffered: tokenOffered,

        tokenRequested: tokenRequested,

        amountOffered: amountOffered,

        minimumAmountRequested: minimumAmountRequested,

        expiry: expiry,

        nonce: nonces[msg.sender]++,

        status: OfferStatus.Open

    });

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although concise, this function already demonstrates several useful design principles.&lt;/p&gt;

&lt;p&gt;First, validation occurs before any state modification.&lt;/p&gt;

&lt;p&gt;Second, every newly created offer immediately receives a unique identifier.&lt;/p&gt;

&lt;p&gt;Finally, we increment the maker's nonce at creation time, ensuring every offer remains uniquely identifiable even if all other parameters are identical.&lt;/p&gt;

&lt;p&gt;In the next section we'll improve this function by replacing &lt;code&gt;require&lt;/code&gt; strings with &lt;strong&gt;custom errors&lt;/strong&gt;, adding &lt;strong&gt;events&lt;/strong&gt; for off-chain indexing, and integrating &lt;strong&gt;EIP-712 signatures&lt;/strong&gt; so offers can be created through signed messages rather than direct transactions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding Production-Grade Solidity Patterns
&lt;/h1&gt;

&lt;p&gt;The first version of &lt;code&gt;OTCRegistry.sol&lt;/code&gt; works, but it still looks closer to a prototype than production infrastructure.&lt;/p&gt;

&lt;p&gt;Before moving deeper into confidential execution and cross-chain settlement, we need to improve the contract foundations.&lt;/p&gt;

&lt;p&gt;Financial protocols are rarely compromised because the developer forgot how to write a function.&lt;/p&gt;

&lt;p&gt;They fail because small design decisions accumulate.&lt;/p&gt;

&lt;p&gt;Gas inefficiencies become expensive at scale.&lt;/p&gt;

&lt;p&gt;Missing events break indexing systems.&lt;/p&gt;

&lt;p&gt;Weak signature handling creates replay vulnerabilities.&lt;/p&gt;

&lt;p&gt;Poorly defined state transitions make auditing significantly harder.&lt;/p&gt;

&lt;p&gt;The next iteration of the registry focuses on those details.&lt;/p&gt;




&lt;h1&gt;
  
  
  Replacing require Strings with Custom Errors
&lt;/h1&gt;

&lt;p&gt;The first improvement is replacing traditional revert messages.&lt;/p&gt;

&lt;p&gt;A common Solidity pattern looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    expiry &amp;gt; block.timestamp,
    "Offer already expired"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but it is unnecessarily expensive.&lt;/p&gt;

&lt;p&gt;Every revert string must be stored inside the contract bytecode.&lt;/p&gt;

&lt;p&gt;For frequently triggered errors, this increases deployment size and runtime gas costs.&lt;/p&gt;

&lt;p&gt;Modern Solidity provides custom errors.&lt;/p&gt;

&lt;p&gt;Instead of storing text, we define an error selector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error OfferExpired();

error InvalidToken();

error InvalidAmount();

error Unauthorized();

error OfferNotOpen();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now validation becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (expiry &amp;lt;= block.timestamp) {
    revert OfferExpired();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference may seem small.&lt;/p&gt;

&lt;p&gt;For a single transaction, the savings are minimal.&lt;/p&gt;

&lt;p&gt;For a protocol processing thousands of interactions, these optimizations compound.&lt;/p&gt;

&lt;p&gt;More importantly, custom errors create structured failure conditions.&lt;/p&gt;

&lt;p&gt;Frontend applications can identify the exact failure type instead of parsing human-readable strings.&lt;/p&gt;




&lt;h1&gt;
  
  
  Updating the Registry Contract
&lt;/h1&gt;

&lt;p&gt;Our contract now begins with clearer protocol-level failures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract OTCRegistry {

    error OfferExpired();
    error InvalidAmount();
    error Unauthorized();
    error OfferNotOpen();

    enum OfferStatus {
        Open,
        Accepted,
        Cancelled,
        Settled,
        Expired
    }

    struct Offer {

        address maker;

        address tokenOffered;

        address tokenRequested;

        uint256 amountOffered;

        uint256 minimumAmountRequested;

        uint256 expiry;

        uint256 nonce;

        OfferStatus status;

    }

    mapping(uint256 =&amp;gt; Offer) private offers;

    uint256 public offerCounter;

    mapping(address =&amp;gt; uint256) public nonces;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The storage layout remains unchanged.&lt;/p&gt;

&lt;p&gt;This is intentional.&lt;/p&gt;

&lt;p&gt;Changing storage unnecessarily creates migration complexity.&lt;/p&gt;

&lt;p&gt;A well-designed contract evolves by adding capabilities around stable foundations.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Events Matter
&lt;/h1&gt;

&lt;p&gt;Smart contracts do not push information to applications.&lt;/p&gt;

&lt;p&gt;They simply modify state.&lt;/p&gt;

&lt;p&gt;If a frontend wants to display active OTC offers, it needs an efficient way to discover changes.&lt;/p&gt;

&lt;p&gt;Reading every offer ID and querying storage directly does not scale.&lt;/p&gt;

&lt;p&gt;Events solve this problem.&lt;/p&gt;

&lt;p&gt;When emitted, events become part of transaction logs that external systems can index.&lt;/p&gt;

&lt;p&gt;Popular indexing systems such as The Graph and custom backend services rely heavily on this pattern.&lt;/p&gt;

&lt;p&gt;For our registry, we need events for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;offer creation,&lt;/li&gt;
&lt;li&gt;offer acceptance,&lt;/li&gt;
&lt;li&gt;cancellation,&lt;/li&gt;
&lt;li&gt;settlement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's add the first one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event OfferCreated(
    uint256 indexed offerId,
    address indexed maker,
    address tokenOffered,
    address tokenRequested,
    uint256 amountOffered,
    uint256 minimumAmountRequested,
    uint256 expiry
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;indexed&lt;/code&gt; keyword is important.&lt;/p&gt;

&lt;p&gt;Indexed fields become searchable topics.&lt;/p&gt;

&lt;p&gt;For example, a frontend can efficiently query:&lt;/p&gt;

&lt;p&gt;"Show me every offer created by this address."&lt;/p&gt;

&lt;p&gt;without scanning the entire blockchain.&lt;/p&gt;




&lt;h1&gt;
  
  
  Emitting Offer Creation Events
&lt;/h1&gt;

&lt;p&gt;Now we update our creation function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function createOffer(

    address tokenOffered,

    address tokenRequested,

    uint256 amountOffered,

    uint256 minimumAmountRequested,

    uint256 expiry

) external returns (uint256 offerId) {


    if (expiry &amp;lt;= block.timestamp) {
        revert OfferExpired();
    }


    if (
        amountOffered == 0 ||
        minimumAmountRequested == 0
    ) {
        revert InvalidAmount();
    }


    offerId = ++offerCounter;


    offers[offerId] = Offer({

        maker: msg.sender,

        tokenOffered: tokenOffered,

        tokenRequested: tokenRequested,

        amountOffered: amountOffered,

        minimumAmountRequested: minimumAmountRequested,

        expiry: expiry,

        nonce: nonces[msg.sender]++,

        status: OfferStatus.Open

    });


    emit OfferCreated(
        offerId,
        msg.sender,
        tokenOffered,
        tokenRequested,
        amountOffered,
        minimumAmountRequested,
        expiry
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function now has three clear responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate the offer.&lt;/li&gt;
&lt;li&gt;Store protocol state.&lt;/li&gt;
&lt;li&gt;Notify external systems.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping those responsibilities separated makes later auditing much easier.&lt;/p&gt;




&lt;h1&gt;
  
  
  Introducing EIP-712 Typed Data
&lt;/h1&gt;

&lt;p&gt;Direct transactions are not always ideal for OTC workflows.&lt;/p&gt;

&lt;p&gt;Institutional traders frequently negotiate away from the blockchain.&lt;/p&gt;

&lt;p&gt;A market maker might send a quote through a private channel.&lt;/p&gt;

&lt;p&gt;A treasury manager might approve a trade through an internal signing system.&lt;/p&gt;

&lt;p&gt;Broadcasting every negotiation step on-chain defeats the purpose of confidentiality.&lt;/p&gt;

&lt;p&gt;Instead, users should be able to sign trade intents.&lt;/p&gt;

&lt;p&gt;The blockchain only verifies the final agreement.&lt;/p&gt;

&lt;p&gt;This is where EIP-712 becomes important.&lt;/p&gt;

&lt;p&gt;EIP-712 defines a standard format for signing structured data.&lt;/p&gt;

&lt;p&gt;Instead of signing an unreadable blob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x72af91bc...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;users sign something understandable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sell:

500000 USDC

Receive:

165 ETH

Expiry:

July 20 2026
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wallets can display these fields clearly before approval.&lt;/p&gt;




&lt;h1&gt;
  
  
  Designing the Signed Offer
&lt;/h1&gt;

&lt;p&gt;Our signed message needs a deterministic structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct OfferMessage {

    address maker;

    address tokenOffered;

    address tokenRequested;

    uint256 amountOffered;

    uint256 minimumAmountRequested;

    uint256 expiry;

    uint256 nonce;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every field matters.&lt;/p&gt;

&lt;p&gt;Removing a field creates ambiguity.&lt;/p&gt;

&lt;p&gt;For example, if &lt;code&gt;expiry&lt;/code&gt; is not included, an attacker could reuse a valid signature after market conditions changed.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;nonce&lt;/code&gt; is missing, replay attacks become possible.&lt;/p&gt;

&lt;p&gt;Cryptographic signatures are only as strong as the data they protect.&lt;/p&gt;




&lt;h1&gt;
  
  
  Adding OpenZeppelin EIP712
&lt;/h1&gt;

&lt;p&gt;Rather than implementing hashing manually, we'll use OpenZeppelin's audited implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
    EIP712
} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";


import {
    ECDSA
} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our contract now inherits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract OTCRegistry is EIP712 {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constructor initializes the signing domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor()
    EIP712(
        "Confidential OTC Protocol",
        "1"
    )
{}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The domain separates signatures from other applications.&lt;/p&gt;

&lt;p&gt;A signature created for our OTC protocol cannot accidentally be reused inside another application with different rules.&lt;/p&gt;




&lt;h1&gt;
  
  
  Hashing the Offer
&lt;/h1&gt;

&lt;p&gt;EIP-712 requires two stages.&lt;/p&gt;

&lt;p&gt;First, we hash the structured message.&lt;/p&gt;

&lt;p&gt;Then we hash that result with the domain separator.&lt;/p&gt;

&lt;p&gt;The final digest is what gets signed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bytes32 private constant OFFER_TYPEHASH =
keccak256(
    "OfferMessage(address maker,address tokenOffered,address tokenRequested,uint256 amountOffered,uint256 minimumAmountRequested,uint256 expiry,uint256 nonce)"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type hash describes the exact structure.&lt;/p&gt;

&lt;p&gt;Any modification changes the resulting hash.&lt;/p&gt;

&lt;p&gt;This prevents attackers from altering individual fields without invalidating the signature.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Matters for Confidential OTC
&lt;/h1&gt;

&lt;p&gt;At this point, we have an important architectural improvement.&lt;/p&gt;

&lt;p&gt;The negotiation process no longer requires every participant to submit transactions.&lt;/p&gt;

&lt;p&gt;A trader can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an offer locally.&lt;/li&gt;
&lt;li&gt;Sign it.&lt;/li&gt;
&lt;li&gt;Share it privately.&lt;/li&gt;
&lt;li&gt;Receive counterparty acceptance.&lt;/li&gt;
&lt;li&gt;Submit only the final agreed trade.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This changes the privacy model dramatically.&lt;/p&gt;

&lt;p&gt;The blockchain no longer observes every negotiation step.&lt;/p&gt;

&lt;p&gt;It only verifies the final commitment.&lt;/p&gt;

&lt;p&gt;This pattern is common in modern trading infrastructure.&lt;/p&gt;

&lt;p&gt;The chain becomes a settlement layer rather than a communication layer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Connecting This Back to Oasis Sapphire
&lt;/h1&gt;

&lt;p&gt;This is where Oasis Sapphire becomes useful.&lt;/p&gt;

&lt;p&gt;Sapphire allows the negotiation layer itself to become confidential while remaining EVM-compatible.&lt;/p&gt;

&lt;p&gt;Instead of exposing signed offers and quotes publicly, sensitive state can remain inside confidential contract execution.&lt;/p&gt;

&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR

TraderA --&amp;gt; Sapphire

TraderB --&amp;gt; Sapphire

Sapphire --&amp;gt; SignedSettlement

SignedSettlement --&amp;gt; Base

Base --&amp;gt; FinalSettlement

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sapphire handles the private coordination.&lt;/p&gt;

&lt;p&gt;Base provides transparent final settlement.&lt;/p&gt;

&lt;p&gt;Neither chain is replacing the other.&lt;/p&gt;

&lt;p&gt;Each handles the part it is best suited for.&lt;/p&gt;




&lt;h1&gt;
  
  
  Next Section
&lt;/h1&gt;

&lt;p&gt;The registry now has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structured storage,&lt;/li&gt;
&lt;li&gt;custom errors,&lt;/li&gt;
&lt;li&gt;event indexing,&lt;/li&gt;
&lt;li&gt;typed signatures,&lt;/li&gt;
&lt;li&gt;replay protection foundations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next step is implementing the acceptance flow.&lt;/p&gt;

&lt;p&gt;We will build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;counterparty quote submission,&lt;/li&gt;
&lt;li&gt;signature verification,&lt;/li&gt;
&lt;li&gt;offer acceptance,&lt;/li&gt;
&lt;li&gt;escrow state transitions,&lt;/li&gt;
&lt;li&gt;settlement preparation,&lt;/li&gt;
&lt;li&gt;and the bridge between confidential negotiation and public Base settlement.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Building the Acceptance Flow
&lt;/h1&gt;

&lt;p&gt;The registry can now create offers and verify signed trade intents. However, an OTC protocol is not complete when an offer exists.&lt;/p&gt;

&lt;p&gt;An offer represents only one side of the agreement.&lt;/p&gt;

&lt;p&gt;The critical moment happens when another participant decides:&lt;/p&gt;

&lt;p&gt;"I accept these terms, and I am willing to proceed toward settlement."&lt;/p&gt;

&lt;p&gt;This transition is where many financial protocols become complicated.&lt;/p&gt;

&lt;p&gt;A naive implementation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function acceptOffer(uint256 offerId)
    external
{
    offers[offerId].status = OfferStatus.Accepted;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This appears simple, but it introduces several problems.&lt;/p&gt;

&lt;p&gt;Who is accepting?&lt;/p&gt;

&lt;p&gt;Can anyone accept?&lt;/p&gt;

&lt;p&gt;Was the offer already cancelled?&lt;/p&gt;

&lt;p&gt;Has the offer expired?&lt;/p&gt;

&lt;p&gt;Did the counterparty actually provide the requested asset?&lt;/p&gt;

&lt;p&gt;Can the same offer be accepted twice?&lt;/p&gt;

&lt;p&gt;In financial infrastructure, changing a single enum value is not the operation. The operation is a state transition with strict conditions.&lt;/p&gt;

&lt;p&gt;Our goal is to make every transition explicit.&lt;/p&gt;




&lt;h1&gt;
  
  
  Designing the Acceptance Lifecycle
&lt;/h1&gt;

&lt;p&gt;Before writing code, let's define what acceptance actually means.&lt;/p&gt;

&lt;p&gt;The lifecycle currently looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open
 |
 |
 v
Accepted
 |
 |
 v
Escrowed
 |
 |
 v
Settled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also failure paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open
 |
 +----&amp;gt; Cancelled
 |
 +----&amp;gt; Expired
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important observation is that acceptance is not settlement.&lt;/p&gt;

&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;A counterparty agreeing to trade does not mean assets have moved.&lt;/p&gt;

&lt;p&gt;The protocol must first verify:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The offer still exists.&lt;/li&gt;
&lt;li&gt;The offer is still open.&lt;/li&gt;
&lt;li&gt;The acceptance satisfies the original conditions.&lt;/li&gt;
&lt;li&gt;Both parties can proceed to escrow.&lt;/li&gt;
&lt;li&gt;Settlement can happen deterministically later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Separating these phases prevents a common class of bugs where negotiation state and asset movement become tightly coupled.&lt;/p&gt;




&lt;h1&gt;
  
  
  Reading Offers Safely
&lt;/h1&gt;

&lt;p&gt;Before adding acceptance logic, we need a way for external applications to inspect offers.&lt;/p&gt;

&lt;p&gt;Currently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(uint256 =&amp;gt; Offer) private offers;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The storage is private.&lt;/p&gt;

&lt;p&gt;This is intentional.&lt;/p&gt;

&lt;p&gt;In a confidential environment, exposing raw storage defeats the purpose of keeping negotiation details protected.&lt;/p&gt;

&lt;p&gt;However, applications still need controlled access.&lt;/p&gt;

&lt;p&gt;We can create a view function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getOffer(uint256 offerId)
    external
    view
    returns (Offer memory)
{
    return offers[offerId];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for a public chain.&lt;/p&gt;

&lt;p&gt;However, because our final architecture uses Oasis Sapphire for confidential negotiation, this function becomes an important design decision.&lt;/p&gt;

&lt;p&gt;On Sapphire, the contract can expose information selectively.&lt;/p&gt;

&lt;p&gt;The difference is subtle but important:&lt;/p&gt;

&lt;p&gt;A public Ethereum contract assumes:&lt;/p&gt;

&lt;p&gt;"Everything is visible unless encrypted externally."&lt;/p&gt;

&lt;p&gt;A confidential execution environment assumes:&lt;/p&gt;

&lt;p&gt;"The contract decides what information leaves the private state."&lt;/p&gt;

&lt;p&gt;The same Solidity pattern behaves differently depending on where it executes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Validating State Transitions
&lt;/h1&gt;

&lt;p&gt;Let's add a helper function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function _requireOpenOffer(
    Offer storage offer
)
    internal
    view
{
    if (offer.status != OfferStatus.Open) {
        revert OfferNotOpen();
    }

    if (block.timestamp &amp;gt; offer.expiry) {
        revert OfferExpired();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function centralizes the rules for an active offer.&lt;/p&gt;

&lt;p&gt;Why not repeat these checks everywhere?&lt;/p&gt;

&lt;p&gt;Because duplicated state validation creates drift.&lt;/p&gt;

&lt;p&gt;Imagine one function checks expiry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;block.timestamp &amp;gt;= expiry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while another checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;block.timestamp &amp;gt; expiry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one-character difference creates inconsistent behavior.&lt;/p&gt;

&lt;p&gt;Financial protocols should have one definition of validity.&lt;/p&gt;




&lt;h1&gt;
  
  
  Accepting an Offer
&lt;/h1&gt;

&lt;p&gt;Now we can implement the acceptance transition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function acceptOffer(
    uint256 offerId
)
    external
{
    Offer storage offer = offers[offerId];

    _requireOpenOffer(offer);

    offer.status = OfferStatus.Accepted;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function is intentionally simple.&lt;/p&gt;

&lt;p&gt;The complexity comes from the guarantees around it.&lt;/p&gt;

&lt;p&gt;Before changing state, the contract proves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the offer exists,&lt;/li&gt;
&lt;li&gt;the offer is active,&lt;/li&gt;
&lt;li&gt;the offer has not expired.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only then does the transition occur.&lt;/p&gt;




&lt;h1&gt;
  
  
  But Who Can Accept?
&lt;/h1&gt;

&lt;p&gt;This is the first major design decision.&lt;/p&gt;

&lt;p&gt;There are several possible models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Acceptance
&lt;/h2&gt;

&lt;p&gt;Anyone can accept an offer.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Alice creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sell:
500,000 USDC

Receive:
165 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any participant can accept.&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple,&lt;/li&gt;
&lt;li&gt;permissionless,&lt;/li&gt;
&lt;li&gt;similar to public exchange orders.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;poor fit for institutional OTC,&lt;/li&gt;
&lt;li&gt;no counterparty negotiation,&lt;/li&gt;
&lt;li&gt;creates competition around acceptance.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Whitelisted Counterparties
&lt;/h2&gt;

&lt;p&gt;The maker specifies who can accept.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maker:
DAO Treasury

Allowed Counterparty:
Market Maker X
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;closer to institutional OTC,&lt;/li&gt;
&lt;li&gt;predictable counterparties.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requires identity management.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Signed Acceptance
&lt;/h2&gt;

&lt;p&gt;The most flexible approach is requiring both sides to sign the agreement.&lt;/p&gt;

&lt;p&gt;The final transaction contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maker Signature

+

Counterparty Signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract verifies both signatures before allowing settlement.&lt;/p&gt;

&lt;p&gt;This is the approach we will move toward.&lt;/p&gt;




&lt;h1&gt;
  
  
  Adding Counterparty Information
&lt;/h1&gt;

&lt;p&gt;Our current Offer struct only knows the maker.&lt;/p&gt;

&lt;p&gt;For bilateral OTC trading, we need another participant.&lt;/p&gt;

&lt;p&gt;Update the structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Offer {

    address maker;

    address taker;

    address tokenOffered;

    address tokenRequested;

    uint256 amountOffered;

    uint256 minimumAmountRequested;

    uint256 expiry;

    uint256 nonce;

    OfferStatus status;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;address taker;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;represents the counterparty who accepted the trade.&lt;/p&gt;

&lt;p&gt;Before acceptance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maker = Alice

taker = address(0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After acceptance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maker = Alice

taker = Bob
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the protocol an explicit record of both parties.&lt;/p&gt;




&lt;h1&gt;
  
  
  Updating Acceptance
&lt;/h1&gt;

&lt;p&gt;Now acceptance records the counterparty.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function acceptOffer(
    uint256 offerId
)
    external
{
    Offer storage offer = offers[offerId];

    _requireOpenOffer(offer);

    offer.taker = msg.sender;

    offer.status = OfferStatus.Accepted;

    emit OfferAccepted(
        offerId,
        msg.sender
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The state transition is now clearer.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open

maker:
Alice

taker:
none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accepted

maker:
Alice

taker:
Bob
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract now knows exactly who negotiated the trade.&lt;/p&gt;




&lt;h1&gt;
  
  
  Adding Acceptance Events
&lt;/h1&gt;

&lt;p&gt;Just like offer creation, acceptance needs an event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event OfferAccepted(
    uint256 indexed offerId,
    address indexed taker
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows external systems to react.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;A backend service can listen for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OfferAccepted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and automatically begin escrow preparation.&lt;/p&gt;

&lt;p&gt;A frontend can update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status:
Accepted
Waiting for escrow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Events become the communication layer between blockchain state and applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Preventing Double Acceptance
&lt;/h1&gt;

&lt;p&gt;Consider this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction 1:
Bob accepts offer #10

Transaction 2:
Charlie accepts offer #10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only one should succeed.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;_requireOpenOffer&lt;/code&gt; check already prevents this.&lt;/p&gt;

&lt;p&gt;After Bob's transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;offer.status = OfferStatus.Accepted;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second transaction sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OfferStatus.Accepted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and reverts.&lt;/p&gt;

&lt;p&gt;This is why explicit state machines are powerful.&lt;/p&gt;

&lt;p&gt;The contract does not need complicated logic.&lt;/p&gt;

&lt;p&gt;The state itself prevents invalid actions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Matters for Confidential OTC
&lt;/h1&gt;

&lt;p&gt;At first glance, acceptance looks like a simple update.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;Acceptance is the point where private negotiation becomes a binding agreement.&lt;/p&gt;

&lt;p&gt;In traditional OTC markets, this moment happens after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;price discovery,&lt;/li&gt;
&lt;li&gt;counterparty verification,&lt;/li&gt;
&lt;li&gt;risk checks,&lt;/li&gt;
&lt;li&gt;legal confirmation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On-chain, the smart contract replaces much of that coordination.&lt;/p&gt;

&lt;p&gt;The contract becomes the neutral agreement layer.&lt;/p&gt;

&lt;p&gt;With Sapphire, this process can remain confidential:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trader A
   |
   |
Private Offer
   |
   v

Oasis Sapphire

   |
   |
Accepted Agreement
   |
   v

Base Settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public chain does not need to observe every negotiation step.&lt;/p&gt;

&lt;p&gt;It only needs proof that settlement conditions were satisfied.&lt;/p&gt;




&lt;h1&gt;
  
  
  Preparing for Escrow
&lt;/h1&gt;

&lt;p&gt;Acceptance alone is still not enough.&lt;/p&gt;

&lt;p&gt;A malicious counterparty could accept an offer and then disappear.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Alice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Locks:
500,000 USDC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accepts trade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then Bob never provides ETH.&lt;/p&gt;

&lt;p&gt;The protocol must solve this problem.&lt;/p&gt;

&lt;p&gt;The next stage introduces escrow.&lt;/p&gt;

&lt;p&gt;We will build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ERC-20 token transfers,&lt;/li&gt;
&lt;li&gt;secure custody,&lt;/li&gt;
&lt;li&gt;allowance handling,&lt;/li&gt;
&lt;li&gt;escrow state tracking,&lt;/li&gt;
&lt;li&gt;settlement authorization,&lt;/li&gt;
&lt;li&gt;withdrawal rules,&lt;/li&gt;
&lt;li&gt;failure recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the protocol starts becoming an actual OTC settlement system rather than a negotiation registry.&lt;/p&gt;

&lt;h1&gt;
  
  
  Building the Escrow Layer
&lt;/h1&gt;

&lt;p&gt;The acceptance flow gives us something important.&lt;/p&gt;

&lt;p&gt;We now have agreement.&lt;/p&gt;

&lt;p&gt;But agreement is not settlement.&lt;/p&gt;

&lt;p&gt;This distinction is fundamental in financial protocols.&lt;/p&gt;

&lt;p&gt;A traditional OTC desk does not send millions of dollars simply because two traders verbally agree on a price. There is usually a custody layer, a clearing process, or a settlement mechanism that guarantees both parties perform.&lt;/p&gt;

&lt;p&gt;Smart contracts need the same separation.&lt;/p&gt;

&lt;p&gt;The registry answers:&lt;/p&gt;

&lt;p&gt;"Who agreed to trade?"&lt;/p&gt;

&lt;p&gt;The escrow contract answers:&lt;/p&gt;

&lt;p&gt;"Are the assets actually secured?"&lt;/p&gt;

&lt;p&gt;Only after both questions have a valid answer should settlement begin.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Escrow Exists
&lt;/h1&gt;

&lt;p&gt;Consider a simple failure scenario.&lt;/p&gt;

&lt;p&gt;Alice creates an offer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sell:
500,000 USDC

Receive:
165 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bob accepts.&lt;/p&gt;

&lt;p&gt;The registry now contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status = Accepted;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But nothing forces Bob to provide ETH.&lt;/p&gt;

&lt;p&gt;The protocol has created an agreement without enforcement.&lt;/p&gt;

&lt;p&gt;This is the difference between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Negotiation Layer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Settlement Layer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The escrow contract connects them.&lt;/p&gt;




&lt;h1&gt;
  
  
  Separating Escrow From the Registry
&lt;/h1&gt;

&lt;p&gt;A common beginner design is putting everything into one contract.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract OTCProtocol {

    createOffer()

    acceptOffer()

    deposit()

    settle()

    cancel()

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks convenient.&lt;/p&gt;

&lt;p&gt;However, it creates several problems.&lt;/p&gt;

&lt;p&gt;The contract now handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;offer management,&lt;/li&gt;
&lt;li&gt;token custody,&lt;/li&gt;
&lt;li&gt;settlement,&lt;/li&gt;
&lt;li&gt;permissions,&lt;/li&gt;
&lt;li&gt;refunds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every additional responsibility increases audit complexity.&lt;/p&gt;

&lt;p&gt;Instead, we separate concerns.&lt;/p&gt;

&lt;p&gt;Our architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 OTCRegistry

              "What was agreed?"
                    |
                    |
                    v

                  Escrow

              "Are assets locked?"
                    |
                    |
                    v

        SettlementCoordinator

              "Execute final trade"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each contract has one job.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating Escrow.sol
&lt;/h1&gt;

&lt;p&gt;Inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/Escrow.sol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we create our custody contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {
    IERC20
} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";


contract Escrow {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first dependency is ERC-20 support.&lt;/p&gt;

&lt;p&gt;The escrow contract does not care whether the token is USDC, WETH, or another asset.&lt;/p&gt;

&lt;p&gt;It only needs the standard interface.&lt;/p&gt;




&lt;h1&gt;
  
  
  Modeling Escrow Positions
&lt;/h1&gt;

&lt;p&gt;An escrow position represents one side of a trade.&lt;/p&gt;

&lt;p&gt;We need to store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who deposited,&lt;/li&gt;
&lt;li&gt;which token,&lt;/li&gt;
&lt;li&gt;how much,&lt;/li&gt;
&lt;li&gt;which offer it belongs to,&lt;/li&gt;
&lt;li&gt;current state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's define it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract Escrow {

    enum EscrowStatus {
        Pending,
        Deposited,
        Released,
        Refunded
    }


    struct Deposit {

        address depositor;

        address token;

        uint256 amount;

        uint256 offerId;

        EscrowStatus status;

    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The structure is intentionally small.&lt;/p&gt;

&lt;p&gt;Escrow contracts should avoid unnecessary state.&lt;/p&gt;

&lt;p&gt;Every stored variable creates another invariant that must remain correct forever.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tracking Deposits
&lt;/h1&gt;

&lt;p&gt;Now we need storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(uint256 =&amp;gt; Deposit) public deposits;

uint256 public depositCounter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each deposit receives an identifier.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deposit #42

Offer:
#100

Depositor:
Alice

Token:
USDC

Amount:
500000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This identifier becomes useful during settlement.&lt;/p&gt;




&lt;h1&gt;
  
  
  Depositing Assets
&lt;/h1&gt;

&lt;p&gt;Now we can implement the first important function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function deposit(
    uint256 offerId,
    address token,
    uint256 amount
)
    external
    returns (uint256 depositId)
{

    require(
        amount &amp;gt; 0,
        "Invalid amount"
    );


    IERC20(token).transferFrom(
        msg.sender,
        address(this),
        amount
    );


    depositId = ++depositCounter;


    deposits[depositId] = Deposit({

        depositor: msg.sender,

        token: token,

        amount: amount,

        offerId: offerId,

        status: EscrowStatus.Deposited

    });

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User approves the escrow contract.&lt;/li&gt;
&lt;li&gt;Escrow pulls tokens using &lt;code&gt;transferFrom&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A deposit record is created.&lt;/li&gt;
&lt;li&gt;The protocol now knows assets are secured.&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  Why Approval Comes First
&lt;/h1&gt;

&lt;p&gt;ERC-20 tokens do not allow arbitrary contracts to move funds.&lt;/p&gt;

&lt;p&gt;The user must first approve spending.&lt;/p&gt;

&lt;p&gt;The sequence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Wallet

approve()

      |

      v

Escrow Contract

transferFrom()

      |

      v

Escrow Balance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is standard ERC-20 behavior.&lt;/p&gt;

&lt;p&gt;For production systems, frontends usually combine these steps into a single user experience.&lt;/p&gt;

&lt;p&gt;The user sees:&lt;/p&gt;

&lt;p&gt;"Deposit 500,000 USDC"&lt;/p&gt;

&lt;p&gt;rather than manually performing approval and transfer operations.&lt;/p&gt;




&lt;h1&gt;
  
  
  Safe Transfer Handling
&lt;/h1&gt;

&lt;p&gt;The previous example uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IERC20(token).transferFrom()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, production contracts should handle tokens that do not properly return boolean values.&lt;/p&gt;

&lt;p&gt;OpenZeppelin provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SafeERC20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's update the contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
    SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";


contract Escrow {

    using SafeERC20 for IERC20;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now deposits become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IERC20(token).safeTransferFrom(
    msg.sender,
    address(this),
    amount
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This protects against non-standard ERC-20 implementations.&lt;/p&gt;




&lt;h1&gt;
  
  
  Adding Events
&lt;/h1&gt;

&lt;p&gt;Like the registry, escrow needs events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event Deposited(
    uint256 indexed depositId,
    uint256 indexed offerId,
    address indexed depositor,
    address token,
    uint256 amount
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then after creating the deposit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emit Deposited(
    depositId,
    offerId,
    msg.sender,
    token,
    amount
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;External systems can now monitor escrow activity.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offer Accepted

        |

        v

Waiting for Deposits

        |

        v

Both Sides Funded

        |

        v

Settlement Ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Escrow Invariant
&lt;/h1&gt;

&lt;p&gt;The most important property of this contract is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Escrowed funds
=
Funds controlled by protocol rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users should never be able to bypass the settlement process.&lt;/p&gt;

&lt;p&gt;After depositing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alice
 |
 | 500,000 USDC
 v

Escrow Contract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alice cannot simply withdraw.&lt;/p&gt;

&lt;p&gt;Bob cannot withdraw.&lt;/p&gt;

&lt;p&gt;The only valid paths are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Successful Settlement

or

Authorized Refund
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core security property.&lt;/p&gt;




&lt;h1&gt;
  
  
  Connecting Escrow to Acceptance
&lt;/h1&gt;

&lt;p&gt;Right now the escrow contract does not know whether an offer was actually accepted.&lt;/p&gt;

&lt;p&gt;That is intentional.&lt;/p&gt;

&lt;p&gt;Remember our separation of responsibilities.&lt;/p&gt;

&lt;p&gt;The registry knows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offer #100

Maker:
Alice

Taker:
Bob

Status:
Accepted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The escrow knows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deposit #55

Offer:
#100

Amount:
500,000 USDC

Status:
Deposited
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settlement coordinator will connect these pieces.&lt;/p&gt;




&lt;h1&gt;
  
  
  Introducing the Settlement Coordinator
&lt;/h1&gt;

&lt;p&gt;The final architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Sapphire

              OTCRegistry

        Negotiation + Agreement


                    |

                    v


                  Escrow

          Asset Security Layer


                    |

                    v


        SettlementCoordinator

             Cross-chain Execution


                    |

                    v


                  Base

          Public Settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coordinator becomes the bridge between confidential negotiation and public execution.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Architecture Fits Sapphire + Base
&lt;/h1&gt;

&lt;p&gt;This separation lets each network do what it does best.&lt;/p&gt;

&lt;p&gt;Sapphire handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private offers,&lt;/li&gt;
&lt;li&gt;confidential quotes,&lt;/li&gt;
&lt;li&gt;negotiation state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Escrow handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;asset custody,&lt;/li&gt;
&lt;li&gt;enforcement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Base handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transparent settlement,&lt;/li&gt;
&lt;li&gt;final ownership changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The protocol does not attempt to make one chain solve every problem.&lt;/p&gt;

&lt;p&gt;Instead, it assigns responsibilities.&lt;/p&gt;




&lt;h1&gt;
  
  
  Next Section
&lt;/h1&gt;

&lt;p&gt;The escrow layer now provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ERC-20 custody,&lt;/li&gt;
&lt;li&gt;deposit tracking,&lt;/li&gt;
&lt;li&gt;safe token transfers,&lt;/li&gt;
&lt;li&gt;event indexing,&lt;/li&gt;
&lt;li&gt;clear ownership boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next part will implement the final settlement path.&lt;/p&gt;

&lt;p&gt;We will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SettlementCoordinator.sol&lt;/li&gt;
&lt;li&gt;cross-chain settlement design&lt;/li&gt;
&lt;li&gt;proving escrow conditions&lt;/li&gt;
&lt;li&gt;releasing funds safely&lt;/li&gt;
&lt;li&gt;Base integration patterns&lt;/li&gt;
&lt;li&gt;replay protection across chains&lt;/li&gt;
&lt;li&gt;handling failures and cancellations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the complete OTC protocol architecture comes together.&lt;/p&gt;

&lt;h1&gt;
  
  
  Building the Settlement Coordinator
&lt;/h1&gt;

&lt;p&gt;At this point, the protocol has three important capabilities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The registry can create and accept agreements.&lt;/li&gt;
&lt;li&gt;The escrow contract can lock assets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What remains is the most important operation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moving from a private agreement into final settlement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where the architecture becomes interesting.&lt;/p&gt;

&lt;p&gt;The protocol intentionally separates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Private negotiation

        |

        v

Verified agreement

        |

        v

Public settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The negotiation does not happen on Base.&lt;/p&gt;

&lt;p&gt;The final ownership transfer does.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Settlement Should Be Separate
&lt;/h1&gt;

&lt;p&gt;A common mistake is allowing the escrow contract to directly perform settlement.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function settle()
external
{
    transferTokens();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This couples custody and execution.&lt;/p&gt;

&lt;p&gt;The escrow contract now needs to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who receives funds,&lt;/li&gt;
&lt;li&gt;which chain settlement happens on,&lt;/li&gt;
&lt;li&gt;whether signatures are valid,&lt;/li&gt;
&lt;li&gt;whether external messages are trusted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That creates unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Instead, escrow only answers:&lt;/p&gt;

&lt;p&gt;"Are the required assets secured?"&lt;/p&gt;

&lt;p&gt;The settlement coordinator answers:&lt;/p&gt;

&lt;p&gt;"Should the trade execute?"&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating SettlementCoordinator.sol
&lt;/h1&gt;

&lt;p&gt;Inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/SettlementCoordinator.sol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;


contract SettlementCoordinator {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coordinator will eventually communicate with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OTCRegistry

        |

        |

Escrow

        |

        |

Base Settlement Layer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Defining Settlement State
&lt;/h1&gt;

&lt;p&gt;A settlement has its own lifecycle.&lt;/p&gt;

&lt;p&gt;It is different from an offer lifecycle.&lt;/p&gt;

&lt;p&gt;The offer lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open

 |

 v

Accepted

 |

 v

Escrowed

 |

 v

Settled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settlement lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created

 |

 v

Verified

 |

 v

Executed

 |

 v

Completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's model this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enum SettlementStatus {

    Pending,

    Ready,

    Executed,

    Failed

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents ambiguous states.&lt;/p&gt;




&lt;h1&gt;
  
  
  Settlement Records
&lt;/h1&gt;

&lt;p&gt;The coordinator needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which offer is being settled,&lt;/li&gt;
&lt;li&gt;which escrow positions are involved,&lt;/li&gt;
&lt;li&gt;who receives what,&lt;/li&gt;
&lt;li&gt;whether execution happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create the structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Settlement {

    uint256 offerId;

    uint256 makerDeposit;

    uint256 takerDeposit;

    address maker;

    address taker;

    SettlementStatus status;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A settlement is not an offer.&lt;/p&gt;

&lt;p&gt;An offer describes intention.&lt;/p&gt;

&lt;p&gt;A settlement describes execution.&lt;/p&gt;

&lt;p&gt;Keeping those separate avoids mixing negotiation state with financial state.&lt;/p&gt;




&lt;h1&gt;
  
  
  Storing Settlements
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(uint256 =&amp;gt; Settlement)
public settlements;


uint256 public settlementCounter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Settlement #7

Offer:
#421


Maker:
Alice


Taker:
Bob


Maker Deposit:
USDC


Taker Deposit:
ETH


Status:
Ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Creating a Settlement
&lt;/h1&gt;

&lt;p&gt;Once both sides have escrowed funds, we create a settlement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function createSettlement(
    uint256 offerId,
    uint256 makerDeposit,
    uint256 takerDeposit,
    address maker,
    address taker
)
external
returns(uint256 settlementId)
{

    settlementId = ++settlementCounter;


    settlements[settlementId] =
        Settlement({

            offerId: offerId,

            makerDeposit: makerDeposit,

            takerDeposit: takerDeposit,

            maker: maker,

            taker: taker,

            status: SettlementStatus.Pending

        });

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function does not move funds.&lt;/p&gt;

&lt;p&gt;That is intentional.&lt;/p&gt;

&lt;p&gt;Settlement creation is a coordination step.&lt;/p&gt;




&lt;h1&gt;
  
  
  Verifying Escrow Conditions
&lt;/h1&gt;

&lt;p&gt;Before execution, we need proof that both sides funded the trade.&lt;/p&gt;

&lt;p&gt;The coordinator should never trust user input.&lt;/p&gt;

&lt;p&gt;A malicious user could attempt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;makerDeposit = fakeDepositId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coordinator must verify directly with escrow.&lt;/p&gt;

&lt;p&gt;The pattern becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Escrow

"Is deposit X valid?"

        |

        v

SettlementCoordinator

"Proceed or reject"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Escrow Interface
&lt;/h1&gt;

&lt;p&gt;Instead of importing the entire escrow contract, define a small interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface IEscrow {

    function getDeposit(
        uint256 depositId
    )
    external
    view
    returns(
        address depositor,
        address token,
        uint256 amount,
        uint256 offerId,
        uint8 status
    );

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coordinator only knows what it needs.&lt;/p&gt;

&lt;p&gt;This reduces coupling.&lt;/p&gt;




&lt;h1&gt;
  
  
  Making Settlement Ready
&lt;/h1&gt;

&lt;p&gt;Now we can verify both deposits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function markReady(
    uint256 settlementId
)
external
{

    Settlement storage settlement =
        settlements[settlementId];


    require(
        settlement.status ==
        SettlementStatus.Pending,
        "Invalid state"
    );


    settlement.status =
        SettlementStatus.Ready;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, this function would perform full escrow validation.&lt;/p&gt;

&lt;p&gt;The simplified version demonstrates the state machine.&lt;/p&gt;




&lt;h1&gt;
  
  
  Executing Settlement
&lt;/h1&gt;

&lt;p&gt;Execution is where assets finally move.&lt;/p&gt;

&lt;p&gt;The coordinator instructs escrow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Release maker asset

        +

Release taker asset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important rule:&lt;/p&gt;

&lt;p&gt;Both sides execute atomically.&lt;/p&gt;

&lt;p&gt;Either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alice receives ETH

Bob receives USDC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nothing happens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Partial settlement is dangerous.&lt;/p&gt;




&lt;h1&gt;
  
  
  Atomic Settlement Pattern
&lt;/h1&gt;

&lt;p&gt;A bad implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;releaseAliceFunds();

releaseBobFunds();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the second operation fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alice receives funds

Bob receives nothing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production system requires transactional atomicity.&lt;/p&gt;

&lt;p&gt;The better pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function settle(
    uint256 settlementId
)
external
{

    Settlement storage settlement =
        settlements[settlementId];


    require(
        settlement.status ==
        SettlementStatus.Ready,
        "Not ready"
    );


    // execute both transfers


    settlement.status =
        SettlementStatus.Executed;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because Solidity transactions revert entirely on failure, the operation is atomic.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cross-Chain Settlement Design
&lt;/h1&gt;

&lt;p&gt;Now we reach the interesting architectural question.&lt;/p&gt;

&lt;p&gt;How does Sapphire communicate with Base?&lt;/p&gt;

&lt;p&gt;The answer is not:&lt;/p&gt;

&lt;p&gt;"Move the whole application to Base."&lt;/p&gt;

&lt;p&gt;The answer is:&lt;/p&gt;

&lt;p&gt;Create a settlement message.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sapphire

Private Trade Agreement

        |

        v

Settlement Message

        |

        v

Base

Public Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settlement message contains only what is necessary.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct SettlementMessage {

    bytes32 tradeId;

    address receiver;

    address token;

    uint256 amount;

    uint256 nonce;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what is missing.&lt;/p&gt;

&lt;p&gt;No negotiation history.&lt;/p&gt;

&lt;p&gt;No quotes.&lt;/p&gt;

&lt;p&gt;No private strategy.&lt;/p&gt;

&lt;p&gt;Only the information required for final execution.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Matters
&lt;/h1&gt;

&lt;p&gt;The privacy boundary becomes very clear.&lt;/p&gt;

&lt;p&gt;Before settlement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Confidential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After settlement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mirrors traditional finance.&lt;/p&gt;

&lt;p&gt;A hedge fund may privately negotiate a block trade.&lt;/p&gt;

&lt;p&gt;But once settlement occurs, ownership changes become visible.&lt;/p&gt;

&lt;p&gt;The blockchain is not replacing OTC markets.&lt;/p&gt;

&lt;p&gt;It is improving the settlement layer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Adding Replay Protection
&lt;/h1&gt;

&lt;p&gt;Cross-chain systems introduce another risk.&lt;/p&gt;

&lt;p&gt;A valid settlement message could potentially be submitted twice.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Message:

Pay Alice 500 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executed once.&lt;/p&gt;

&lt;p&gt;Then replayed.&lt;/p&gt;

&lt;p&gt;Executed again.&lt;/p&gt;

&lt;p&gt;This is why every settlement message needs uniqueness.&lt;/p&gt;

&lt;p&gt;The simplest mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(bytes32 =&amp;gt; bool)
processedMessages;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    !processedMessages[id],
    "Already executed"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;processedMessages[id] = true;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One small mapping prevents catastrophic duplication.&lt;/p&gt;




&lt;h1&gt;
  
  
  Current Architecture
&lt;/h1&gt;

&lt;p&gt;We now have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Oasis Sapphire


          Confidential Negotiation


                    |

                    v


              Settlement Message


                    |

                    v


                   Base


          Transparent Final Settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has a clear responsibility.&lt;/p&gt;




&lt;h1&gt;
  
  
  Integrating Oasis Sapphire Confidential Execution
&lt;/h1&gt;

&lt;p&gt;The settlement architecture is now complete.&lt;/p&gt;

&lt;p&gt;However, we have not yet fully answered the original question:&lt;/p&gt;

&lt;p&gt;How do we actually make OTC negotiation confidential?&lt;/p&gt;

&lt;p&gt;So far, we have built a conventional Solidity protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;offers,&lt;/li&gt;
&lt;li&gt;signatures,&lt;/li&gt;
&lt;li&gt;escrow,&lt;/li&gt;
&lt;li&gt;settlement coordination.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those components work on any EVM-compatible chain.&lt;/p&gt;

&lt;p&gt;The reason Oasis Sapphire is interesting is that it changes the privacy model of the application.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;"How do we encrypt everything before putting it on-chain?"&lt;/p&gt;

&lt;p&gt;we can ask:&lt;/p&gt;

&lt;p&gt;"Which parts of the application should execute privately?"&lt;/p&gt;

&lt;p&gt;That distinction is extremely important.&lt;/p&gt;




&lt;h1&gt;
  
  
  Public Blockchains and Information Leakage
&lt;/h1&gt;

&lt;p&gt;On a traditional EVM chain, contract state is transparent.&lt;/p&gt;

&lt;p&gt;If a contract stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Offer {

    address maker;

    address tokenOffered;

    uint256 amount;

    uint256 price;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then anyone can eventually inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who created the offer,&lt;/li&gt;
&lt;li&gt;what asset they want,&lt;/li&gt;
&lt;li&gt;how much they are trading,&lt;/li&gt;
&lt;li&gt;the conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if the frontend hides this information, the blockchain does not.&lt;/p&gt;

&lt;p&gt;For OTC trading, that is a problem.&lt;/p&gt;

&lt;p&gt;The negotiation itself is valuable information.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Sapphire Changes
&lt;/h1&gt;

&lt;p&gt;Oasis Sapphire provides confidential EVM execution.&lt;/p&gt;

&lt;p&gt;The developer experience remains familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma solidity ^0.8.24;

contract OTCRegistry {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but execution happens inside a confidential environment.&lt;/p&gt;

&lt;p&gt;Sensitive state can remain protected while the contract continues using Solidity patterns.&lt;/p&gt;

&lt;p&gt;The important architectural shift is:&lt;/p&gt;

&lt;p&gt;Before Sapphire:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User

 |
 |
Encrypted Data

 |
 |
Public Smart Contract

 |
 |
Everyone Observes State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Sapphire:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User

 |
 |
Confidential Contract Execution

 |
 |
Protected State

 |
 |
Only Authorized Outputs Revealed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Moving the Registry to Sapphire
&lt;/h1&gt;

&lt;p&gt;The OTC Registry is the natural candidate for Sapphire.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because the registry contains negotiation information.&lt;/p&gt;

&lt;p&gt;The escrow and final settlement layer do not need the same privacy guarantees.&lt;/p&gt;

&lt;p&gt;A better architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Oasis Sapphire


          OTCRegistry

     Private Offers + Quotes


                 |

                 |

          Settlement Proof


                 |

                 |

                  Base


          Final Asset Settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registry becomes the private negotiation engine.&lt;/p&gt;




&lt;h1&gt;
  
  
  Confidential Offer Storage
&lt;/h1&gt;

&lt;p&gt;Previously we stored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(uint256 =&amp;gt; Offer)
private offers;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a public chain, private storage is only private by convention.&lt;/p&gt;

&lt;p&gt;On Sapphire, confidential state is enforced by the execution environment.&lt;/p&gt;

&lt;p&gt;This means the contract can safely maintain information such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct PrivateOffer {

    address maker;

    address requestedCounterparty;

    uint256 amount;

    uint256 price;

    bytes encryptedTerms;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without exposing every field publicly.&lt;/p&gt;




&lt;h1&gt;
  
  
  Private Quotes
&lt;/h1&gt;

&lt;p&gt;Institutional OTC trading rarely happens with a single offer.&lt;/p&gt;

&lt;p&gt;Usually the process looks like:&lt;/p&gt;

&lt;p&gt;Trader:&lt;/p&gt;

&lt;p&gt;"I want to sell 10 million USDC."&lt;/p&gt;

&lt;p&gt;Market makers:&lt;/p&gt;

&lt;p&gt;"Here are three quotes."&lt;/p&gt;

&lt;p&gt;Trader:&lt;/p&gt;

&lt;p&gt;"I accept quote #2."&lt;/p&gt;

&lt;p&gt;A public blockchain would reveal every quote.&lt;/p&gt;

&lt;p&gt;That creates unnecessary information leakage.&lt;/p&gt;

&lt;p&gt;Sapphire allows this flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trader

 |
 |
Private Request

 |
 |
Sapphire Contract

 |
 |
Market Makers Submit Quotes

 |
 |
Trader Accepts Best Quote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the final agreement needs to leave the confidential environment.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Sapphire Does Not Hide
&lt;/h1&gt;

&lt;p&gt;It is important not to overstate confidentiality.&lt;/p&gt;

&lt;p&gt;Sapphire does not magically make everything invisible.&lt;/p&gt;

&lt;p&gt;The final settlement on Base is public.&lt;/p&gt;

&lt;p&gt;Observers can see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;settlement transactions,&lt;/li&gt;
&lt;li&gt;transferred assets,&lt;/li&gt;
&lt;li&gt;destination addresses,&lt;/li&gt;
&lt;li&gt;execution timing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The privacy boundary is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Negotiation

Private


Settlement

Public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly the same separation used in traditional OTC markets.&lt;/p&gt;




&lt;h1&gt;
  
  
  Confidential Signatures
&lt;/h1&gt;

&lt;p&gt;Our earlier EIP-712 flow becomes even more useful here.&lt;/p&gt;

&lt;p&gt;Instead of broadcasting every signed message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offer Created

Quote Received

Counter Offer

Revised Quote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the application can keep those interactions confidential.&lt;/p&gt;

&lt;p&gt;Only the final signed commitment becomes relevant.&lt;/p&gt;

&lt;p&gt;The protocol effectively changes from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every negotiation step is public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Only the final agreement is verifiable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Sapphire Design Principles
&lt;/h1&gt;

&lt;p&gt;When building confidential contracts, the same engineering principles still apply.&lt;/p&gt;

&lt;p&gt;Privacy does not replace good architecture.&lt;/p&gt;

&lt;p&gt;We still need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit state machines,&lt;/li&gt;
&lt;li&gt;replay protection,&lt;/li&gt;
&lt;li&gt;authorization checks,&lt;/li&gt;
&lt;li&gt;safe token handling,&lt;/li&gt;
&lt;li&gt;deterministic settlement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confidential execution protects information.&lt;/p&gt;

&lt;p&gt;It does not protect incorrect logic.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building the SDK and Production Workflow
&lt;/h1&gt;

&lt;p&gt;The smart contracts are complete.&lt;/p&gt;

&lt;p&gt;Now we need users to interact with them.&lt;/p&gt;

&lt;p&gt;A professional protocol rarely exposes raw contract calls directly.&lt;/p&gt;

&lt;p&gt;Instead, applications use an SDK layer.&lt;/p&gt;

&lt;p&gt;Our repository already contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sdk/

    otcClient.ts

    signer.ts

    quotes.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This layer handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wallet connections,&lt;/li&gt;
&lt;li&gt;typed messages,&lt;/li&gt;
&lt;li&gt;signatures,&lt;/li&gt;
&lt;li&gt;contract calls,&lt;/li&gt;
&lt;li&gt;settlement tracking.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Creating the SDK Client
&lt;/h1&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OTCClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;registryContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createOffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registryContract&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenOffered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenRequested&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiry&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend should not know Solidity details.&lt;/p&gt;

&lt;p&gt;It should think in business operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createOffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;acceptOffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;settle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Signing Offers
&lt;/h1&gt;

&lt;p&gt;The SDK also handles EIP-712.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signTypedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;offer&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sell:

500,000 USDC

Receive:

165 ETH

Expires:

Tomorrow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x83ab29ff....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Readable signatures reduce user mistakes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Frontend Flow
&lt;/h1&gt;

&lt;p&gt;A complete user journey looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trader Opens App

        |

Creates OTC Request

        |

Signs Private Intent

        |

Market Makers Submit Quotes

        |

Trader Accepts Quote

        |

Assets Escrowed

        |

Settlement Message Created

        |

Base Transaction Executed

        |

Trade Complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Testing the Protocol
&lt;/h1&gt;

&lt;p&gt;Financial protocols require more than happy-path testing.&lt;/p&gt;

&lt;p&gt;We should test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invalid signatures,&lt;/li&gt;
&lt;li&gt;expired offers,&lt;/li&gt;
&lt;li&gt;double acceptance,&lt;/li&gt;
&lt;li&gt;failed deposits,&lt;/li&gt;
&lt;li&gt;replayed settlement messages,&lt;/li&gt;
&lt;li&gt;unauthorized withdrawals.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Example Foundry Test
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testCannotAcceptExpiredOffer()
    public
{

    vm.warp(block.timestamp + 2 days);


    vm.expectRevert(
        OfferExpired.selector
    );


    registry.acceptOffer(
        offerId
    );

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not simply proving success.&lt;/p&gt;

&lt;p&gt;The goal is proving invalid behavior is impossible.&lt;/p&gt;




&lt;h1&gt;
  
  
  Fuzz Testing
&lt;/h1&gt;

&lt;p&gt;OTC protocols have many numeric inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;amounts,&lt;/li&gt;
&lt;li&gt;timestamps,&lt;/li&gt;
&lt;li&gt;nonces,&lt;/li&gt;
&lt;li&gt;token quantities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fuzz testing helps discover unexpected edge cases.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testAmounts(
    uint256 amount
)
public
{

    vm.assume(amount &amp;gt; 0);


    createOffer(amount);

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The framework automatically explores many values.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security Checklist
&lt;/h1&gt;

&lt;p&gt;Before deploying a protocol handling valuable assets:&lt;/p&gt;

&lt;h2&gt;
  
  
  Access Control
&lt;/h2&gt;

&lt;p&gt;Verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only makers cancel offers,&lt;/li&gt;
&lt;li&gt;only accepted counterparties settle,&lt;/li&gt;
&lt;li&gt;only authorized coordinators release escrow.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Replay Protection
&lt;/h2&gt;

&lt;p&gt;Verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;signatures cannot be reused,&lt;/li&gt;
&lt;li&gt;settlement messages cannot execute twice,&lt;/li&gt;
&lt;li&gt;nonces increment correctly.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  State Transitions
&lt;/h2&gt;

&lt;p&gt;Verify:&lt;/p&gt;

&lt;p&gt;Invalid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open → Settled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open
 |
Accepted
 |
Escrowed
 |
Settled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Token Safety
&lt;/h2&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SafeERC20,&lt;/li&gt;
&lt;li&gt;allowance checks,&lt;/li&gt;
&lt;li&gt;zero address validation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Failure Recovery
&lt;/h2&gt;

&lt;p&gt;Every financial protocol needs failure paths.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expired offers,&lt;/li&gt;
&lt;li&gt;abandoned settlements,&lt;/li&gt;
&lt;li&gt;cancelled negotiations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A protocol is not defined only by successful trades.&lt;/p&gt;

&lt;p&gt;It is defined by how safely it handles failure.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Architecture
&lt;/h1&gt;

&lt;p&gt;The completed system looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Trader A


                       |

                       |

              Oasis Sapphire


        Confidential OTC Registry

        - Private Offers

        - Private Quotes

        - Signed Agreements


                       |

                       |

              Settlement Message


                       |

                       |

                    Base


        Public Settlement Layer

        - Asset Transfer

        - Final Ownership


                       |

                       |

                    Trader B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Large trades require different infrastructure from ordinary swaps.&lt;/p&gt;

&lt;p&gt;Automated market makers are excellent for transparent liquidity.&lt;/p&gt;

&lt;p&gt;They are not designed for confidential institutional negotiation.&lt;/p&gt;

&lt;p&gt;An OTC protocol needs a different approach.&lt;/p&gt;

&lt;p&gt;The architecture we built separates the problem into three layers:&lt;/p&gt;

&lt;h2&gt;
  
  
  Confidential Negotiation
&lt;/h2&gt;

&lt;p&gt;Handled by Oasis Sapphire.&lt;/p&gt;

&lt;p&gt;This protects sensitive trading information while preserving smart contract coordination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asset Security
&lt;/h2&gt;

&lt;p&gt;Handled through escrow.&lt;/p&gt;

&lt;p&gt;This ensures both parties commit funds before execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public Settlement
&lt;/h2&gt;

&lt;p&gt;Handled by Base.&lt;/p&gt;

&lt;p&gt;This provides transparent final ownership transfer.&lt;/p&gt;

&lt;p&gt;The result is not a replacement for decentralized exchanges.&lt;/p&gt;

&lt;p&gt;It is a complementary system designed for a different class of trading.&lt;/p&gt;

&lt;p&gt;Large financial transactions require both privacy and trust minimization.&lt;/p&gt;

&lt;p&gt;Confidential execution provides the privacy.&lt;/p&gt;

&lt;p&gt;Smart contracts provide the enforcement.&lt;/p&gt;

&lt;p&gt;Public settlement provides the transparency.&lt;/p&gt;

&lt;p&gt;Together, they create a practical foundation for the next generation of decentralized OTC trading systems.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>privacy</category>
      <category>cryptocurrency</category>
      <category>confidentiality</category>
    </item>
    <item>
      <title>Building a Confidential Cross-Chain Identity Protocol with Oasis Sapphire, ENS &amp; Base</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Thu, 16 Jul 2026 07:15:44 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-cross-chain-identity-protocol-with-oasis-sapphire-ens-base-249o</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-cross-chain-identity-protocol-with-oasis-sapphire-ens-base-249o</guid>
      <description>&lt;p&gt;The biggest paradox in Web3 identity is that while we demand privacy in the real world, our on-chain infrastructure defaults to absolute transparency. If you attach a KYC credential, credit score, or real name to your ENS domain, it is immediately indexable by anyone.&lt;/p&gt;

&lt;p&gt;But what if you could store your sensitive Personally Identifiable Information (PII) inside a secure enclave, and only expose cryptographic proofs of that data to smart contracts on other chains?&lt;/p&gt;

&lt;p&gt;In this advanced tutorial, we are building a &lt;strong&gt;Confidential Cross-Chain Identity Layer&lt;/strong&gt;. We will use &lt;strong&gt;Oasis Sapphire&lt;/strong&gt; (the first confidential EVM) as our secure vault, &lt;strong&gt;Base&lt;/strong&gt; (Ethereum L2) as our execution environment, and the &lt;strong&gt;Oasis Privacy Layer (OPL) via Celer IM&lt;/strong&gt; to bridge the two. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Real-World Use Case: Compliant DeFi on Base
&lt;/h2&gt;

&lt;p&gt;Imagine a DeFi lending protocol on Base that requires users to be non-US citizens and over 18 to access leverage. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem:&lt;/strong&gt; Storing "Country" and "Date of Birth" directly on Base violates privacy and data protection laws (like GDPR).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Solution:&lt;/strong&gt; The user mints an ENS name (&lt;code&gt;alice.base.eth&lt;/code&gt;) on Base. They store their private KYC data in a smart contract on Oasis Sapphire. When the Base DeFi protocol queries the ENS resolver, the resolver triggers a cross-chain message via OPL to Sapphire. Sapphire computes the logic inside a Trusted Execution Environment (TEE), checking if &lt;code&gt;age &amp;gt; 18 &amp;amp;&amp;amp; country != US&lt;/code&gt;, and returns a simple &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; back to Base.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No PII is ever leaked, the public RPCs see nothing but ciphertext, and the DeFi protocol remains fully compliant.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture &amp;amp; Interaction Flow
&lt;/h2&gt;

&lt;p&gt;We will utilize &lt;strong&gt;EIP-3668 (CCIP-Read)&lt;/strong&gt; for off-chain data resolution and &lt;strong&gt;Celer Inter-Chain Messaging (IM)&lt;/strong&gt; for state synchronization between Base and Sapphire.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant User
    participant DeFi as Base DeFi Protocol
    participant Resolver as Base ENS Resolver
    participant Relayer as Celer IM / OPL
    participant Sapphire as Oasis Sapphire (TEE)

    User-&amp;gt;&amp;gt;Sapphire: 1. Encrypt &amp;amp; Store KYC (Age, Country)
    Note over Sapphire: State is encrypted end-to-end
    User-&amp;gt;&amp;gt;DeFi: 2. Request Undercollateralized Loan
    DeFi-&amp;gt;&amp;gt;Resolver: 3. Verify compliance for `alice.base.eth`
    Resolver-&amp;gt;&amp;gt;Relayer: 4. Dispatch Cross-Chain Request
    Relayer-&amp;gt;&amp;gt;Sapphire: 5. Execute query inside TEE
    Sapphire--&amp;gt;&amp;gt;Sapphire: 6. Decrypt -&amp;gt; Check logic -&amp;gt; Re-encrypt
    Sapphire-&amp;gt;&amp;gt;Relayer: 7. Emit boolean compliance proof
    Relayer-&amp;gt;&amp;gt;Resolver: 8. CCIP-Read Callback with Proof
    Resolver--&amp;gt;&amp;gt;DeFi: 9. Returns `true` (Compliant)
    DeFi--&amp;gt;&amp;gt;User: 10. Loan Approved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Repository Structure
&lt;/h2&gt;

&lt;p&gt;For this advanced stack, we will use a monorepo built with Hardhat/Foundry to manage multi-chain deployments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cross-chain-identity/
├── contracts/
│   ├── base/
│   │   ├── ENSIdentityResolver.sol     # Custom ENS Resolver on Base
│   │   └── IDeFiProtocol.sol           # Mock DeFi contract
│   ├── sapphire/
│   │   ├── ConfidentialVault.sol       # TEE-encrypted storage contract
│   │   └── VerificationEngine.sol      # Logic computation 
│   └── interfaces/
│       ├── ICelerMessageBus.sol        # Celer IM interface
│       └── IOasisPrivacyLayer.sol
├── scripts/
│   ├── deploy-base.js
│   ├── deploy-sapphire.js
│   └── bridge-setup.js
├── hardhat.config.js                   # Configured for Base &amp;amp; Sapphire networks
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: The Confidential Vault on Oasis Sapphire&lt;br&gt;
Oasis Sapphire is EVM-compatible but runs inside secure enclaves. State variables are encrypted by default. We will write ConfidentialVault.sol to store user data securely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SPDX-License-Identifier: MIT&lt;/span&gt;
&lt;span class="nx"&gt;pragma&lt;/span&gt; &lt;span class="nx"&gt;solidity&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="nx"&gt;ConfidentialVault&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This state is encrypted inside the Sapphire TEE&lt;/span&gt;
    &lt;span class="nx"&gt;struct&lt;/span&gt; &lt;span class="nx"&gt;IdentityRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;uint256&lt;/span&gt; &lt;span class="nx"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;bool&lt;/span&gt; &lt;span class="nx"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;IdentityRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;privateIdentities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Address of the Celer Message Bus on Sapphire&lt;/span&gt;
    &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;_messageBus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;messageBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_messageBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * @dev User updates their private data. Transactions are end-to-end encrypted.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uint256&lt;/span&gt; &lt;span class="nx"&gt;_dob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="nx"&gt;calldata&lt;/span&gt; &lt;span class="nx"&gt;_countryCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;external&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;privateIdentities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IdentityRecord&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_dob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * @dev Executed via cross-chain message from Base.
     * Computes compliance without revealing raw data.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyComplianceCrossChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;_baseCallbackContract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;external&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Only Relayer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;IdentityRecord&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;privateIdentities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// Confidential computation: Is over 18 and not US?&lt;/span&gt;
        &lt;span class="nx"&gt;bool&lt;/span&gt; &lt;span class="nx"&gt;isCompliant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;uint256&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;31556926&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Approx years&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;keccak256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;keccak256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;isCompliant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Construct payload to send back to Base&lt;/span&gt;
        &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isCompliant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Send via Celer IM (Oasis Privacy Layer) back to Base&lt;/span&gt;
        &lt;span class="c1"&gt;// (Pseudocode for Celer IM integration)&lt;/span&gt;
        &lt;span class="nc"&gt;ICelerMessageBus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;_baseCallbackContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;8453&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Base Mainnet Chain ID&lt;/span&gt;
            &lt;span class="nx"&gt;message&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Because Sapphire encrypts calldata, an observer on block explorers will only see gibberish bytes, completely protecting the user's DoB and Country.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: The Cross-Chain ENS Resolver on Base&lt;br&gt;
On Base, our custom ENS Resolver implements executeWithCCIPRead to pause execution, request data from Sapphire, and resume via a callback once Celer IM delivers the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SPDX-License-Identifier: MIT&lt;/span&gt;
&lt;span class="nx"&gt;pragma&lt;/span&gt; &lt;span class="nx"&gt;solidity&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="nx"&gt;ENSIdentityResolver&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;sapphireVaultAddress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;complianceCache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="nc"&gt;OffchainLookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="nx"&gt;callData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes4&lt;/span&gt; &lt;span class="nx"&gt;callbackFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="nx"&gt;extraData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;_messageBus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;_sapphireVaultAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;messageBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_messageBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;sapphireVaultAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_sapphireVaultAddress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * @dev Standard check called by DeFi protocols on Base.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isUserCompliant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;external&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="nf"&gt;returns &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// If not in cache, trigger CCIP-Read / Cross-chain flow&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;complianceCache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[](&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[https://our-relayer-gateway.io/verify/](https://our-relayer-gateway.io/verify/){sender}/{data}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nx"&gt;revert&lt;/span&gt; &lt;span class="nc"&gt;OffchainLookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodePacked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;celfCallback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;complianceCache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * @dev Callback executed by Celer Executor once Sapphire computes the result.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;_srcContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;uint64&lt;/span&gt; &lt;span class="nx"&gt;_srcChainId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="nx"&gt;calldata&lt;/span&gt; &lt;span class="nx"&gt;_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;_executor&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;external&lt;/span&gt; &lt;span class="nx"&gt;payable&lt;/span&gt; &lt;span class="nf"&gt;returns &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Only Celer Message Bus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_srcChainId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;23294&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Only Sapphire Mainnet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Sapphire Chain ID&lt;/span&gt;
        &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_srcContract&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;sapphireVaultAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid source contract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt; &lt;span class="nx"&gt;isCompliant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Cache the confidential state on Base&lt;/span&gt;
        &lt;span class="nx"&gt;complianceCache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isCompliant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Hardhat Deployment &amp;amp; Orchestration&lt;br&gt;
To deploy this, your hardhat.config.js needs RPC endpoints for both networks. Oasis Sapphire provides standard Web3 RPC endpoints, making the developer experience seamless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;solidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.8.19&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[https://mainnet.base.org](https://mainnet.base.org)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;sapphire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[https://sapphire.oasis.io](https://sapphire.oasis.io)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23294&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When integrating the frontend, use the @oasisprotocol/sapphire-paratime wrapper for Ethers.js. This automatically encrypts your transactions on the client side before sending them to the Sapphire network.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;sapphire&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@oasisprotocol/sapphire-paratime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Wrap your standard signer to enable end-to-end encryption&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Web3Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ethereum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sapphire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSigner&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Now call the vault; the calldata will be automatically encrypted!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vaultContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VAULT_ADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VAULT_ABI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vaultContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;946684800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: Building the CCIP-Read Off-Chain Gateway (Node.js)&lt;br&gt;
When the Base contract reverts with OffchainLookup, the user's wallet (or the dApp frontend) will automatically send an HTTP GET request to the URL specified in the revert. We need an Express server to catch this, trigger the Celer IM message to Sapphire, and manage the asynchronous state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js (Gateway)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/verify/:sender/:data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Decode the user address from the original call data&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultAbiCoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;address&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Trigger the cross-chain message on the Relayer/Sapphire side&lt;/span&gt;
        &lt;span class="c1"&gt;// In a production environment, this triggers a relayer bot to pay gas on Sapphire&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dispatchToSapphire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decodedUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. We return a pending state or an acknowledgment &lt;/span&gt;
        &lt;span class="c1"&gt;// Note: Because Celer IM is asynchronous, the Base contract will receive &lt;/span&gt;
        &lt;span class="c1"&gt;// the actual boolean value from the Celer Executor in a subsequent block.&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending_cross_chain_execution&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Celer IM dispatched to Oasis Sapphire. Await callback on Base.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CCIP-Read Gateway running on port 3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;: Gas Abstraction (Zero-Gas UX on Sapphire)&lt;br&gt;
A major UX hurdle in cross-chain architectures is forcing the user to hold the native gas token (ROSE) on Oasis Sapphire just to update their identity.&lt;/p&gt;

&lt;p&gt;To solve this, we can implement the Oasis Gas Station Network (GSN) or a custom Paymaster. Because Sapphire supports EIP-4337 (Account Abstraction) and standard meta-transactions, you can set up a Gas Relayer. The user signs an EIP-712 message containing their encrypted KYC data, and your backend submits it to Sapphire, paying the ROSE gas fee on their behalf. The user only ever needs ETH on Base.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6&lt;/strong&gt;: Security &amp;amp; Threat Model&lt;br&gt;
When dealing with cross-chain PII, the threat model extends beyond standard smart contract bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State Desyncs&lt;/strong&gt;: If a user updates their age on Sapphire but the Base cache isn't refreshed, the Base DeFi protocol might operate on stale data. Always implement a Time-to-Live (TTL) or an expiration timestamp on the complianceCache mapping on Base.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Replay Attacks&lt;/strong&gt;: Ensure the Celer IM callback (executeMessage) strictly verifies _srcChainId and _srcContract. If an attacker can spoof the message origin, they can arbitrarily set their complianceCache to true on Base.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TEE Side-Channels&lt;/strong&gt;: While Sapphire's Secure Enclaves (Intel SGX) protect data in use, developers must ensure they do not leak data via access patterns. Avoid writing logic that significantly changes gas consumption based on the value of a private variable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By combining ENS on Base with the Oasis Privacy Layer, we've built a robust architecture that solves Web3's biggest compliance hurdle. Smart contracts on L2s can now leverage confidential logic, request attestations, and maintain composability without doxxing their users.&lt;/p&gt;

&lt;p&gt;The future of Web3 identity isn't fully transparent, nor is it siloed in isolated private chains. The future is modular, cross-chain, and selectively private.&lt;/p&gt;

&lt;p&gt;If you found this advanced tutorial helpful, leave a comment below or check out the &lt;a href="https://docs.oasis.io/build/opl/" rel="noopener noreferrer"&gt;Oasis Privacy Layer Documentation&lt;/a&gt; to dive deeper into confidential smart contracts!&lt;/p&gt;

</description>
      <category>web3</category>
      <category>cryptocurrency</category>
      <category>privacy</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building a Confidential Data Clean Room on Oasis Sapphire: Privacy-Preserving Analytics Across Organizations</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Sat, 13 Jun 2026 17:27:40 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-data-clean-room-on-oasis-sapphire-privacy-preserving-analytics-across-3el3</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-data-clean-room-on-oasis-sapphire-privacy-preserving-analytics-across-3el3</guid>
      <description>&lt;p&gt;One of the biggest unsolved problems in data engineering is collaboration between organizations that do not trust each other enough to share raw data.&lt;/p&gt;

&lt;p&gt;Consider the following scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two crypto exchanges want to identify overlapping fraudulent wallets.&lt;/li&gt;
&lt;li&gt;Multiple banks want to detect coordinated money laundering activity.&lt;/li&gt;
&lt;li&gt;Healthcare providers want to compare patient cohorts for research.&lt;/li&gt;
&lt;li&gt;Marketing companies want to measure customer overlap.&lt;/li&gt;
&lt;li&gt;Cybersecurity firms want to identify shared threat actors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all of these cases, the organizations would benefit from collaboration.&lt;/p&gt;

&lt;p&gt;The problem is that they cannot expose their underlying datasets.&lt;/p&gt;

&lt;p&gt;Traditional solutions usually involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trusted third parties&lt;/li&gt;
&lt;li&gt;Secure legal agreements&lt;/li&gt;
&lt;li&gt;Data escrow providers&lt;/li&gt;
&lt;li&gt;Multi-party computation frameworks&lt;/li&gt;
&lt;li&gt;Internal clean room infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These solutions are expensive, operationally complex, and often difficult to audit.&lt;/p&gt;

&lt;p&gt;This is where Oasis Sapphire, once again (:p), becomes interesting.&lt;/p&gt;

&lt;p&gt;Instead of sharing raw data, organizations can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encrypt datasets locally&lt;/li&gt;
&lt;li&gt;Submit encrypted data to Sapphire&lt;/li&gt;
&lt;li&gt;Execute confidential computations&lt;/li&gt;
&lt;li&gt;Receive aggregated results&lt;/li&gt;
&lt;li&gt;Never expose the underlying records&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this tutorial we will build a complete confidential data clean room that allows multiple organizations to compare datasets without revealing them. I will also answer any comments/dms I'm going to get as soon as possible!&lt;/p&gt;

&lt;p&gt;We will cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Architecture design&lt;/li&gt;
&lt;li&gt;Confidential smart contracts&lt;/li&gt;
&lt;li&gt;Dataset encryption&lt;/li&gt;
&lt;li&gt;Secure submissions&lt;/li&gt;
&lt;li&gt;Set intersection algorithms&lt;/li&gt;
&lt;li&gt;Similarity metrics&lt;/li&gt;
&lt;li&gt;Differential privacy&lt;/li&gt;
&lt;li&gt;Attestation concepts&lt;/li&gt;
&lt;li&gt;Production deployment considerations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end, you will have a system capable of performing privacy-preserving analytics across organizations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Suppose Exchange A maintains a list of suspicious wallets:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;0x111...&lt;br&gt;
0x222...&lt;br&gt;
0x333...&lt;br&gt;
0x444...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exchange B maintains its own list:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;0x333...&lt;br&gt;
0x444...&lt;br&gt;
0x555...&lt;br&gt;
0x666...&lt;/p&gt;

&lt;p&gt;Both exchanges want to know:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many suspicious wallets do we have in common?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A traditional approach would require both exchanges to reveal their full datasets.&lt;/p&gt;

&lt;p&gt;This creates multiple risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer privacy violations&lt;/li&gt;
&lt;li&gt;Regulatory concerns&lt;/li&gt;
&lt;li&gt;Competitive intelligence leakage&lt;/li&gt;
&lt;li&gt;Data retention obligations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The clean room model allows the question to be answered without exposing the datasets themselves.&lt;/p&gt;

&lt;p&gt;The final output becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overlap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing else is revealed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High-Level Architecture
                Exchange A
                     |
                     |
            Encrypt Dataset
                     |
                     v

          +------------------+
          | Oasis Sapphire   |
          | Confidential     |
          | Data Clean Room  |
          +------------------+

                     ^
                     |
            Encrypt Dataset
                     |
                     |

                Exchange B

                     |
                     v

              Aggregated Result

               overlap = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical observation is that raw data never leaves the confidential execution boundary in plaintext.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project Structure
sapphire-clean-room/

├── contracts/
│   ├── CleanRoom.sol
│   ├── SimilarityEngine.sol
│
├── scripts/
│   ├── deploy.ts
│
├── worker/
│   ├── encryptDataset.ts
│   ├── submitDataset.ts
│
├── api/
│   ├── server.ts
│
├── datasets/
│   ├── exchangeA.csv
│   ├── exchangeB.csv
│
├── test/
│   ├── cleanRoom.test.ts
│
└── hardhat.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding Data Clean Rooms
&lt;/h2&gt;

&lt;p&gt;A data clean room is a controlled environment where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data providers submit information&lt;/li&gt;
&lt;li&gt;Computations occur&lt;/li&gt;
&lt;li&gt;Raw data remains hidden&lt;/li&gt;
&lt;li&gt;Only approved outputs leave the environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditionally, cloud providers offer clean room services.&lt;/p&gt;

&lt;p&gt;The challenge is trust.&lt;/p&gt;

&lt;p&gt;Organizations must trust:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Infrastructure operators&lt;/li&gt;
&lt;li&gt;Database administrators&lt;/li&gt;
&lt;li&gt;Cloud providers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sapphire introduces a different trust model.&lt;/p&gt;

&lt;p&gt;The computation occurs inside confidential execution environments backed by Trusted Execution Environments (TEEs).&lt;/p&gt;

&lt;p&gt;This means computation is isolated from the host system itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Dataset Registration Contract
&lt;/h3&gt;

&lt;p&gt;We begin by allowing organizations to register encrypted datasets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma solidity ^0.8.20;

contract CleanRoom {

    struct Dataset {
        bytes encryptedData;
        address owner;
        uint256 submittedAt;
        bool active;
    }

    uint256 public datasetCount;

    mapping(uint256 =&amp;gt; Dataset) public datasets;

    event DatasetSubmitted(
        uint256 indexed datasetId,
        address indexed owner
    );

    function submitDataset(
        bytes calldata encryptedData
    ) external {

        datasetCount++;

        datasets[datasetCount] = Dataset({
            encryptedData: encryptedData,
            owner: msg.sender,
            submittedAt: block.timestamp,
            active: true
        });

        emit DatasetSubmitted(
            datasetCount,
            msg.sender
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This contract stores encrypted payloads only.&lt;/li&gt;
&lt;li&gt;The blockchain never sees plaintext records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why Encryption Alone Is Not Enough&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common misconception is:&lt;/p&gt;

&lt;p&gt;"Just encrypt the data and store it on-chain."&lt;/p&gt;

&lt;p&gt;Encryption protects storage.&lt;/p&gt;

&lt;p&gt;It does not solve computation.&lt;/p&gt;

&lt;p&gt;Eventually somebody must decrypt and process the data.&lt;/p&gt;

&lt;p&gt;The real challenge is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we compute on sensitive information without exposing it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is where Sapphire enters the picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Confidential Set Intersection
&lt;/h3&gt;

&lt;p&gt;The most common clean room operation is set intersection.&lt;/p&gt;

&lt;p&gt;We want:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A ∩ B&lt;/strong&gt; without revealing A or B.&lt;/p&gt;

&lt;p&gt;Inside Sapphire we can safely decrypt datasets and perform the operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma solidity ^0.8.20;

contract SimilarityEngine {

    function computeIntersection(
        address[] memory a,
        address[] memory b
    )
        public
        pure
        returns (uint256)
    {
        uint256 overlap;

        for (uint256 i = 0; i &amp;lt; a.length; i++) {

            for (uint256 j = 0; j &amp;lt; b.length; j++) {

                if (a[i] == b[j]) {
                    overlap++;
                }
            }
        }

        return overlap;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation is intentionally simple. We will optimize it later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Naive Approach Does Not Scale&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dataset A = 100,000 entries&lt;br&gt;
Dataset B = 100,000 entries&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Complexity becomes:&lt;/p&gt;

&lt;p&gt;100,000 × 100,000 = 10 billion comparisons&lt;/p&gt;

&lt;p&gt;This is not practical. We need better data structures.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Hash-Based Intersection
&lt;/h3&gt;

&lt;p&gt;Instead of nested loops we build lookup tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function computeIntersectionFast(
    bytes32[] memory a,
    bytes32[] memory b
)
    public
    pure
    returns (uint256)
{
    uint256 overlap;

    for (uint256 i = 0; i &amp;lt; a.length; i++) {

        for (uint256 j = 0; j &amp;lt; b.length; j++) {

            if (a[i] == b[j]) {
                overlap++;
            }
        }
    }

    return overlap;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, off-chain preprocessing should convert datasets into hashed representations.&lt;/p&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller payloads&lt;/li&gt;
&lt;li&gt;Better privacy&lt;/li&gt;
&lt;li&gt;Faster matching&lt;/li&gt;
&lt;li&gt;Similarity Metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes overlap counts are insufficient.&lt;/p&gt;

&lt;p&gt;Organizations may want similarity scores.&lt;/p&gt;

&lt;p&gt;A common metric is Jaccard Similarity.&lt;/p&gt;

&lt;p&gt;The formula is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jaccard Similarity = Number of Shared Records / Total Unique Records​&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Interpretation:&lt;/p&gt;

&lt;p&gt;0.0 = completely different&lt;br&gt;
1.0 = identical datasets&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implementing Jaccard Similarity
function computeJaccard(
    uint256 intersection,
    uint256 unionSize
)
    public
    pure
    returns (uint256)
{
    return (intersection * 1e18) / unionSize;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returning scaled values avoids floating-point arithmetic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Local Dataset Encryption
&lt;/h3&gt;

&lt;p&gt;Before submission, organizations encrypt locally.&lt;/p&gt;

&lt;p&gt;Example Node.js utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;algorithm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aes-256-gcm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encryptDataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;iv&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAuthTag&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Encryption occurs before data leaves the organization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Submission Worker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submitDataset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;CONTRACT_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;ABI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;signer&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;encryptDataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;key&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitDataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUtf8Bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worker becomes the ingestion pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differential Privacy Layer
&lt;/h2&gt;

&lt;p&gt;Even aggregate results can leak information.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Dataset overlap: 1245&lt;/p&gt;

&lt;p&gt;Repeated queries could allow reconstruction attacks.&lt;/p&gt;

&lt;p&gt;To mitigate this, we can introduce controlled noise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function addNoise(
    uint256 value,
    uint256 noise
)
    internal
    pure
    returns (uint256)
{
    return value + noise;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;Actual overlap: 1245&lt;br&gt;
Reported overlap: 1242&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy improves while preserving utility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multi-Party Data Clean Rooms&lt;/p&gt;

&lt;p&gt;The real power appears when more than two organizations participate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Exchange A
Exchange B
Exchange C
Exchange D

        |
        v

   Sapphire Clean Room

        |
        v

  Aggregated Analysis

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared fraud detection&lt;/li&gt;
&lt;li&gt;Shared sanctions monitoring&lt;/li&gt;
&lt;li&gt;Shared AML screening&lt;/li&gt;
&lt;li&gt;Shared cybersecurity intelligence&lt;/li&gt;
&lt;li&gt;Production Security Considerations&lt;/li&gt;
&lt;li&gt;Dataset Poisoning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An attacker may submit fabricated data.&lt;/p&gt;

&lt;p&gt;Mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Staking requirements&lt;/li&gt;
&lt;li&gt;Dataset signatures&lt;/li&gt;
&lt;li&gt;Reputation systems&lt;/li&gt;
&lt;li&gt;Replay Attacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An attacker may repeatedly submit old datasets.&lt;/p&gt;

&lt;p&gt;Mitigations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(bytes32 =&amp;gt; bool)
public processedHashes;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reject duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query Abuse
&lt;/h2&gt;

&lt;p&gt;Attackers may repeatedly request computations.&lt;/p&gt;

&lt;p&gt;Mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Access control&lt;/li&gt;
&lt;li&gt;Usage quotas&lt;/li&gt;
&lt;li&gt;Membership Inference Attacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An attacker may attempt to infer whether a specific record exists.&lt;/p&gt;

&lt;p&gt;Mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Differential privacy&lt;/li&gt;
&lt;li&gt;Minimum cohort sizes&lt;/li&gt;
&lt;li&gt;Query restrictions&lt;/li&gt;
&lt;li&gt;Future Extensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture can evolve into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confidential AML Network&lt;/li&gt;
&lt;li&gt;Banks privately compare suspicious entities.&lt;/li&gt;
&lt;li&gt;Cross-Exchange Fraud Detection&lt;/li&gt;
&lt;li&gt;Exchanges identify shared attackers.&lt;/li&gt;
&lt;li&gt;Healthcare Research Network&lt;/li&gt;
&lt;li&gt;Hospitals compare patient cohorts without sharing records.&lt;/li&gt;
&lt;li&gt;Privacy-Preserving Advertising Analytics&lt;/li&gt;
&lt;li&gt;Companies measure audience overlap.&lt;/li&gt;
&lt;li&gt;Confidential Supply Chain Intelligence&lt;/li&gt;
&lt;li&gt;Manufacturers compare supplier data without exposing relationships.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Most blockchain applications focus on moving value.&lt;/p&gt;

&lt;p&gt;Far fewer explore moving information safely.&lt;/p&gt;

&lt;p&gt;Confidential data clean rooms represent a massive opportunity because they solve a real-world problem that organizations face every day:&lt;/p&gt;

&lt;p&gt;How can we collaborate on data without exposing the data itself?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Oasis Sapphire&lt;/strong&gt; provides a powerful foundation for this model by combining smart contracts with confidential execution.&lt;/p&gt;

&lt;p&gt;Instead of choosing between privacy and computation, we can finally have both.&lt;/p&gt;

&lt;p&gt;The result is a new class of applications where organizations can learn from each other without surrendering control of their most valuable asset: their data.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Confidential Event-Driven Indexer on Oasis Sapphire (Beyond The Graph Pattern)</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Thu, 11 Jun 2026 07:18:56 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-event-driven-indexer-on-oasis-sapphire-beyond-the-graph-pattern-3kac</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-event-driven-indexer-on-oasis-sapphire-beyond-the-graph-pattern-3kac</guid>
      <description>&lt;p&gt;Most blockchain indexing systems today assume a very simple model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;all blockchain data is public, and indexing is just a deterministic transformation of public events.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This assumption works well for analytics platforms like dashboards, DEX trackers, or portfolio tools.&lt;/p&gt;

&lt;p&gt;However, it breaks down in real-world scenarios where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;event data is sensitive (wallet behavior, internal flows, private classifications)&lt;/li&gt;
&lt;li&gt;indexing logic itself is proprietary (risk models, scoring systems, compliance logic)&lt;/li&gt;
&lt;li&gt;or you need selective disclosure (some users can see more data than others)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In traditional architectures, you are forced to choose between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;transparency (but no privacy)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;privacy (but no verifiability)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oasis Sapphire introduces a third option:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;execute indexing logic inside a confidential runtime (TEE-backed), while still using standard EVM interfaces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This tutorial builds a &lt;strong&gt;confidential event-driven indexer&lt;/strong&gt;, where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;raw blockchain events remain public&lt;/li&gt;
&lt;li&gt;classification logic is hidden&lt;/li&gt;
&lt;li&gt;only sanitized outputs are exposed externally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The Graph, but the mapping function is private and policy-controlled.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why This Is Architecturally Different
&lt;/h2&gt;

&lt;p&gt;To understand why this matters, let's compare the standard indexing pipeline:&lt;/p&gt;

&lt;h3&gt;
  
  
  Traditional Indexing (The Graph-style)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Smart Contract -&amp;gt; Events -&amp;gt; Indexer -&amp;gt; Public Database -&amp;gt; API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deterministic&lt;/li&gt;
&lt;li&gt;reproducible&lt;/li&gt;
&lt;li&gt;fully observable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is great for trustlessness, but it creates problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;any adversary can reconstruct your analytics logic&lt;/li&gt;
&lt;li&gt;wallet behavior is fully exposed&lt;/li&gt;
&lt;li&gt;proprietary scoring models cannot exist&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Confidential Indexing (Oasis Sapphire model)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Smart Contract -&amp;gt; Events
                     ↓
        Sapphire Confidential Runtime
                     ↓
          Private State Transformation
                     ↓
         Offchain Worker + Storage
                     ↓
            Sanitized Public API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key difference:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The transformation function (indexing logic) is no longer public.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Real-World Use Case
&lt;/h2&gt;

&lt;p&gt;We will build a &lt;strong&gt;confidential DeFi activity indexer&lt;/strong&gt; that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tracks swaps on a DEX&lt;/li&gt;
&lt;li&gt;computes internal risk scores&lt;/li&gt;
&lt;li&gt;classifies activity (low / medium / high risk)&lt;/li&gt;
&lt;li&gt;hides wallet-level behavior&lt;/li&gt;
&lt;li&gt;exposes only aggregated metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;institutional compliance dashboards&lt;/li&gt;
&lt;li&gt;private DeFi analytics platforms&lt;/li&gt;
&lt;li&gt;MEV-aware monitoring systems&lt;/li&gt;
&lt;li&gt;regulated crypto infrastructure&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Not Just Use Existing Indexers?
&lt;/h2&gt;

&lt;p&gt;Existing solutions like The Graph or custom ETL pipelines have a fundamental limitation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;they cannot hide the transformation logic itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even if you obfuscate the backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input events are public&lt;/li&gt;
&lt;li&gt;transformations are reproducible&lt;/li&gt;
&lt;li&gt;reverse-engineering is trivial&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Sapphire:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;logic executes in a TEE (Trusted Execution Environment)&lt;/li&gt;
&lt;li&gt;computation is not externally observable&lt;/li&gt;
&lt;li&gt;only final outputs are revealed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Structure (Production-Grade)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confidential-indexer/
│
├── contracts/
│   ├── Dex.sol
│   ├── ConfidentialIndexer.sol
│   ├── interfaces/
│
├── worker/
│   ├── indexer.ts
│   ├── eventDecoder.ts
│   ├── riskEngine.ts
│   ├── db.ts
│
├── api/
│   ├── server.ts
│   ├── routes/
│
├── scripts/
│   ├── deploy.ts
│
├── test/
│   ├── indexer.test.ts
│
└── hardhat.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1: Event Source Contract (DEX Simulation)
&lt;/h2&gt;

&lt;p&gt;We start with a simple decentralized exchange emitting swap events.&lt;/p&gt;

&lt;p&gt;This is intentionally minimal, the complexity happens in the indexing layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// contracts/Dex.sol
pragma solidity ^0.8.20;

contract Dex {

    event Swap(
        address indexed user,
        address indexed tokenIn,
        address indexed tokenOut,
        uint256 amount,
        uint256 timestamp
    );

    function swap(
        address tokenIn,
        address tokenOut,
        uint256 amount
    ) external {
        emit Swap(
            msg.sender,
            tokenIn,
            tokenOut,
            amount,
            block.timestamp
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key idea:
&lt;/h3&gt;

&lt;p&gt;We deliberately include structured data so the indexer can apply richer classification logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Confidential Indexer Contract (Sapphire Core Logic)
&lt;/h2&gt;

&lt;p&gt;This is the core of the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key properties:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;runs inside Sapphire confidential runtime&lt;/li&gt;
&lt;li&gt;decrypts event payloads&lt;/li&gt;
&lt;li&gt;applies private scoring rules&lt;/li&gt;
&lt;li&gt;stores encrypted internal state
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// contracts/ConfidentialIndexer.sol
pragma solidity ^0.8.20;

import "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol";

contract ConfidentialIndexer {

    struct SwapData {
        address user;
        address tokenIn;
        address tokenOut;
        uint256 amount;
        uint256 timestamp;
    }

    struct PoolState {
        uint256 riskScore;
        uint256 volume;
    }

    mapping(bytes32 =&amp;gt; PoolState) private pools;

    event Indexed(bytes32 indexed poolId, uint256 riskScore, uint256 volume);

    function processSwap(bytes calldata encryptedSwap) external {

        // Step 1: decrypt inside TEE
        bytes memory decrypted = Sapphire.decrypt(encryptedSwap);

        SwapData memory data = abi.decode(decrypted, (SwapData));

        // Step 2: derive pool identifier
        bytes32 poolId = keccak256(
            abi.encodePacked(data.tokenIn, data.tokenOut)
        );

        // Step 3: update private state
        PoolState storage state = pools[poolId];

        state.volume += data.amount;

        uint256 risk = computeRisk(data, state);

        state.riskScore = risk;

        // Step 4: emit only sanitized output
        emit Indexed(poolId, state.riskScore, state.volume);
    }

    function computeRisk(
        SwapData memory data,
        PoolState memory state
    )
        internal
        pure
        returns (uint256)
    {
        uint256 score = 0;

        // Large trade heuristic
        if (data.amount &amp;gt; 1_000_000 ether) {
            score += 5;
        }

        // Volume spike heuristic
        if (state.volume &amp;gt; 10_000_000 ether) {
            score += 3;
        }

        return score;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Why this is important:
&lt;/h3&gt;

&lt;p&gt;The key innovation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the risk model is never observable externally&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adversaries cannot game your scoring system&lt;/li&gt;
&lt;li&gt;analytics logic is not reproducible&lt;/li&gt;
&lt;li&gt;you can build proprietary indexers&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Event Worker (Offchain Index Aggregator)
&lt;/h2&gt;

&lt;p&gt;This component listens to Sapphire outputs and stores them.&lt;/p&gt;

&lt;p&gt;Unlike traditional indexers, it does NOT compute logic, only stores results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// worker/indexer.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RPC_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CONTRACT_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ABI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indexed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poolId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;riskScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indexed update received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;poolId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;poolId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;riskScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;riskScore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Public API Layer (Sanitized Exposure)
&lt;/h2&gt;

&lt;p&gt;This layer ensures no raw or sensitive data leaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// api/server.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/pool/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;riskScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;riskScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// intentionally no user-level data exposed&lt;/span&gt;
    &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aggregated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Design Insight: Privacy Boundary Model
&lt;/h2&gt;

&lt;p&gt;This architecture introduces a strict separation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Visibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Smart contract events&lt;/td&gt;
&lt;td&gt;Public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sapphire execution logic&lt;/td&gt;
&lt;td&gt;Confidential&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk model / heuristics&lt;/td&gt;
&lt;td&gt;Hidden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregated outputs&lt;/td&gt;
&lt;td&gt;Public (sanitized)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Hard Engineering Problems (Important Section)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Determinism vs Confidential Execution
&lt;/h3&gt;

&lt;p&gt;TEE environments must remain deterministic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same input -&amp;gt; same output&lt;/li&gt;
&lt;li&gt;but without revealing internal computation steps&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Trust in Index Correctness
&lt;/h3&gt;

&lt;p&gt;Because computation is hidden:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you rely on enclave guarantees&lt;/li&gt;
&lt;li&gt;or multi-worker validation systems&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Composability Limitations
&lt;/h3&gt;

&lt;p&gt;Unlike The Graph:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;results are not universally reproducible&lt;/li&gt;
&lt;li&gt;external contracts cannot verify raw computation&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. State Replay Problem
&lt;/h3&gt;

&lt;p&gt;Encrypted state introduces challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;historical recomputation is non-trivial&lt;/li&gt;
&lt;li&gt;debugging requires replay tooling inside TEE&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;This pattern enables a new category of systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private blockchain analytics&lt;/li&gt;
&lt;li&gt;institutional DeFi infrastructure&lt;/li&gt;
&lt;li&gt;MEV-resistant monitoring tools&lt;/li&gt;
&lt;li&gt;compliance-aware data pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It shifts the paradigm from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“everything is transparent and verifiable”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“computation is verifiable, but not necessarily observable”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Most blockchain infrastructure assumes transparency is always beneficial.&lt;/p&gt;

&lt;p&gt;But in real systems, especially financial and institutional contexts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;privacy is required&lt;/li&gt;
&lt;li&gt;analytics are proprietary&lt;/li&gt;
&lt;li&gt;and data exposure is a liability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oasis Sapphire enables a third design space:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;verifiable execution with confidential logic&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This indexer pattern is just one example of what becomes possible when indexing itself becomes programmable and private.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Verifiable Multi-Chain AI Execution Layer with Oasis ROFL + Sapphire + TDX</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Thu, 21 May 2026 07:02:11 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-verifiable-multi-chain-ai-execution-layer-with-oasis-rofl-sapphire-tdx-14na</link>
      <guid>https://dev.to/rollingindo/building-a-verifiable-multi-chain-ai-execution-layer-with-oasis-rofl-sapphire-tdx-14na</guid>
      <description>&lt;p&gt;Most AI on blockchain applications today still rely heavily on trust.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AI inference happens on centralized servers.
&lt;/li&gt;
&lt;li&gt;The wallet keys live in backend infrastructure.
&lt;/li&gt;
&lt;li&gt;Smart contracts trust APIs blindly.
&lt;/li&gt;
&lt;li&gt;And users have no way to verify what actually happened off-chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this tutorial, we’ll build something far more advanced:&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;verifiable confidential AI execution layer&lt;/strong&gt; using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Oasis Sapphire&lt;/li&gt;
&lt;li&gt;ROFL (Runtime Offchain Logic)&lt;/li&gt;
&lt;li&gt;Intel TDX Trusted Execution Environments&lt;/li&gt;
&lt;li&gt;Remote Attestation&lt;/li&gt;
&lt;li&gt;Enclave-managed wallet signing&lt;/li&gt;
&lt;li&gt;Cross-chain transaction execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private AI inference
&lt;/li&gt;
&lt;li&gt;Verifiable off-chain execution
&lt;/li&gt;
&lt;li&gt;Secure enclave wallet management
&lt;/li&gt;
&lt;li&gt;Cross-chain automation
&lt;/li&gt;
&lt;li&gt;Trust-minimized AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without needing zkML or expensive zkVM proving systems.&lt;/p&gt;




&lt;h1&gt;
  
  
  What We’re Building
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------+
|                 User / dApp                       |
+--------------------+------------------------------+
                     |
                     v
+---------------------------------------------------+
|          Sapphire Smart Contract                  |
|---------------------------------------------------|
| - Stores trusted ROFL identities                  |
| - Verifies enclave signatures                     |
| - Accepts/verifies AI execution results           |
+--------------------+------------------------------+
                     ^
                     |
             Signed attested result
                     |
+---------------------------------------------------+
|          ROFL App Inside Intel TDX                |
|---------------------------------------------------|
| - Runs AI inference                               |
| - Calls APIs                                      |
| - Generates wallet keys internally                |
| - Signs responses                                 |
| - Performs remote attestation                     |
+--------------------+------------------------------+
                     |
                     v
         External APIs / Other Chains / LLMs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Why This Matters
&lt;/h1&gt;

&lt;p&gt;Most AI agents today suffer from major trust issues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Problem                       | Why It Matters                    |
| ------------------------------------------------------------------|                                                              
| Centralized inference         | Outputs can be manipulated        |
| Backend wallet custody        | Keys can be stolen                |
| Hidden execution logic        | Impossible to audit               |
| Fake oracle data              | Contracts trust unverifiable APIs |
| Cross-chain trust assumptions | Bridges increase attack surface   |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ROFL changes this entirely.&lt;/p&gt;

&lt;p&gt;Instead of trusting a backend server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;computation runs inside a Trusted Execution Environment&lt;/li&gt;
&lt;li&gt;outputs are cryptographically signed&lt;/li&gt;
&lt;li&gt;enclave identities are attested&lt;/li&gt;
&lt;li&gt;smart contracts verify execution authenticity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates &lt;strong&gt;trustless off-chain compute&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Core Technologies
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Sapphire
&lt;/h2&gt;

&lt;p&gt;Sapphire is Oasis Network’s confidential EVM runtime.&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encrypted calldata&lt;/li&gt;
&lt;li&gt;encrypted state&lt;/li&gt;
&lt;li&gt;confidential smart contract execution&lt;/li&gt;
&lt;li&gt;Solidity/EVM compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike traditional EVM chains, node operators cannot inspect execution data.&lt;/p&gt;




&lt;h2&gt;
  
  
  ROFL (Runtime Offchain Logic)
&lt;/h2&gt;

&lt;p&gt;ROFL allows developers to run arbitrary off-chain logic inside TEEs.&lt;/p&gt;

&lt;p&gt;This means your application can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call APIs&lt;/li&gt;
&lt;li&gt;run AI models&lt;/li&gt;
&lt;li&gt;sign transactions&lt;/li&gt;
&lt;li&gt;manage secrets&lt;/li&gt;
&lt;li&gt;execute autonomous workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while remaining cryptographically verifiable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Intel TDX
&lt;/h2&gt;

&lt;p&gt;Intel TDX provides confidential virtual machines with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encrypted memory&lt;/li&gt;
&lt;li&gt;hardware isolation&lt;/li&gt;
&lt;li&gt;attestation support&lt;/li&gt;
&lt;li&gt;strong execution guarantees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ROFL supports TDX-backed confidential compute environments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Architecture Deep Dive
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        User Prompt
              |
              v
+-----------------------------------+
|      Sapphire Smart Contract      |
+-----------------------------------+
              |
              v
+-----------------------------------+
|       ROFL TDX Enclave            |
|-----------------------------------|
| 1. Receives encrypted request     |
| 2. Runs AI inference              |
| 3. Generates signed result        |
| 4. Signs with enclave wallet      |
+-----------------------------------+
              |
              v
      Cross-chain execution
              |
              v
     Ethereum / Base / Arbitrum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 1: Install Oasis CLI
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://get.oasis.io | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oasis &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 2: Create a ROFL Application
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;ai-executor
&lt;span class="nb"&gt;cd &lt;/span&gt;ai-executor

oasis rofl init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rofl.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manifest defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enclave configuration&lt;/li&gt;
&lt;li&gt;TEE type&lt;/li&gt;
&lt;li&gt;networking&lt;/li&gt;
&lt;li&gt;policies&lt;/li&gt;
&lt;li&gt;deployment settings&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Step 3: Configure ROFL Manifest
&lt;/h1&gt;

&lt;p&gt;Example advanced configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai-executor&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.0&lt;/span&gt;

&lt;span class="na"&gt;tee&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tdx&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers&lt;/span&gt;

&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8192&lt;/span&gt;
  &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;

&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;paratime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sapphire&lt;/span&gt;

&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;quotes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pcs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tdx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;

&lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Important Manifest Settings
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Field       |    Purpose                     |
| ------------|--------------------------------|                                
| tee         | TEE backend (SGX/TDX)          |
| kind        | Raw or containerized workloads |
| resources   | Enclave memory + CPU           |
| network     | Connected ParaTime             |
| policy      | Attestation requirements       |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 4: Build the Enclave
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oasis rofl build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates an ORC bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-app.orc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bundle includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enclave measurements&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;li&gt;binaries&lt;/li&gt;
&lt;li&gt;identity hashes&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Step 5: Generate Wallet Keys Inside the Enclave
&lt;/h1&gt;

&lt;p&gt;This is one of the most powerful features.&lt;/p&gt;

&lt;p&gt;Traditional AI agents store keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in &lt;code&gt;.env&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;in cloud KMS systems&lt;/li&gt;
&lt;li&gt;in backend infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet Key Generated INSIDE TEE
        ↓
Key Never Leaves Enclave
        ↓
Transactions Signed Securely
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No backend custody required.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example Rust Wallet Generation
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;k256&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ecdsa&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SigningKey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;rand_core&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OsRng&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;generate_wallet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SigningKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;SigningKey&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;OsRng&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The private key only exists inside enclave memory.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 6: Run AI Inference Securely
&lt;/h1&gt;

&lt;p&gt;Example inference service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;

&lt;span class="n"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-classification&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distilbert-base-uncased&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Execute strategy?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside ROFL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompts remain confidential&lt;/li&gt;
&lt;li&gt;inference is hidden from operators&lt;/li&gt;
&lt;li&gt;outputs are signed by the enclave&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Secure AI Execution Flow
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Prompt
    ↓
ROFL Enclave
    ↓
Private AI Inference
    ↓
Signed Output
    ↓
Sapphire Verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 7: Remote Attestation
&lt;/h1&gt;

&lt;p&gt;Attestation proves:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This exact code executed inside genuine TDX hardware.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without attestation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TEEs are just claims&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With attestation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contracts can verify enclave authenticity&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Attestation Flow
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ROFL App Starts
      ↓
TEE Generates Measurement
      ↓
Attestation Quote Produced
      ↓
Quote Registered On-Chain
      ↓
Sapphire Verifies Identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 8: Register the ROFL App
&lt;/h1&gt;

&lt;p&gt;Create the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oasis rofl create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; testnet &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--paratime&lt;/span&gt; sapphire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oasis rofl update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enclave identities&lt;/li&gt;
&lt;li&gt;measurements&lt;/li&gt;
&lt;li&gt;execution policy&lt;/li&gt;
&lt;li&gt;attestation configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;on-chain.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 9: Smart Contract Verification
&lt;/h1&gt;

&lt;p&gt;Now we create a Sapphire contract that verifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enclave identity&lt;/li&gt;
&lt;li&gt;signatures&lt;/li&gt;
&lt;li&gt;trusted execution&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Solidity Verification Contract
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma solidity ^0.8.20;

contract VerifiedAIExecutor {

    mapping(bytes32 =&amp;gt; bool) public trustedEnclaves;

    function registerEnclave(bytes32 enclaveId) external {
        trustedEnclaves[enclaveId] = true;
    }

    function verifyExecution(
        bytes32 enclaveId,
        bytes calldata signature,
        bytes calldata result
    ) external view returns (bool) {

        require(trustedEnclaves[enclaveId], "Untrusted enclave");

        // Signature verification logic
        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 10: Cross-Chain Transaction Execution
&lt;/h1&gt;

&lt;p&gt;This is where things become extremely powerful.&lt;/p&gt;

&lt;p&gt;The enclave can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manage Ethereum wallets&lt;/li&gt;
&lt;li&gt;sign Base transactions&lt;/li&gt;
&lt;li&gt;execute Arbitrum transactions&lt;/li&gt;
&lt;li&gt;interact with multiple chains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without bridges.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cross-Chain Execution Flow
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Strategy Generated
        ↓
TEE Decides Action
        ↓
TEE Signs Transaction
        ↓
Broadcast To Ethereum RPC
        ↓
Result Returned To Sapphire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;autonomous trading systems&lt;/li&gt;
&lt;li&gt;AI treasury management&lt;/li&gt;
&lt;li&gt;confidential DAO executors&lt;/li&gt;
&lt;li&gt;secure cross-chain agents&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Step 11: Deterministic Execution Logging
&lt;/h1&gt;

&lt;p&gt;AI outputs are often nondeterministic.&lt;/p&gt;

&lt;p&gt;To improve auditability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hash prompts&lt;/li&gt;
&lt;li&gt;hash outputs&lt;/li&gt;
&lt;li&gt;hash model versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;model_version&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;output hash&lt;/li&gt;
&lt;li&gt;enclave ID&lt;/li&gt;
&lt;li&gt;timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;on-chain.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security Considerations
&lt;/h1&gt;

&lt;p&gt;TEEs are powerful but not perfect.&lt;/p&gt;

&lt;p&gt;Potential risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;side-channel attacks&lt;/li&gt;
&lt;li&gt;hardware trust assumptions&lt;/li&gt;
&lt;li&gt;speculative execution vulnerabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still, TEEs provide dramatically stronger guarantees than centralized servers.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Architecture Is Important
&lt;/h1&gt;

&lt;p&gt;Traditional AI systems rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trusted cloud providers&lt;/li&gt;
&lt;li&gt;centralized APIs&lt;/li&gt;
&lt;li&gt;backend custody&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ROFL + Sapphire introduces:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traditional Backend&lt;/th&gt;
&lt;th&gt;ROFL + Sapphire&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Centralized inference&lt;/td&gt;
&lt;td&gt;TEE-attested inference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend wallet storage&lt;/td&gt;
&lt;td&gt;Enclave-managed keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hidden execution&lt;/td&gt;
&lt;td&gt;Verifiable execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API trust assumptions&lt;/td&gt;
&lt;td&gt;Hardware-backed trust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Centralized orchestration&lt;/td&gt;
&lt;td&gt;Permissionless infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Real-World Use Cases
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Verifiable AI Trading Agents
&lt;/h2&gt;

&lt;p&gt;The enclave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runs private strategies&lt;/li&gt;
&lt;li&gt;signs trades&lt;/li&gt;
&lt;li&gt;proves execution authenticity&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Confidential Healthcare AI
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;patient data remains private&lt;/li&gt;
&lt;li&gt;inference occurs inside TEEs&lt;/li&gt;
&lt;li&gt;outputs become auditable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Autonomous DAO Executors
&lt;/h2&gt;

&lt;p&gt;AI agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;propose governance actions&lt;/li&gt;
&lt;li&gt;execute treasury operations&lt;/li&gt;
&lt;li&gt;prove execution legitimacy&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Multi-Chain AI Wallet Infrastructure
&lt;/h2&gt;

&lt;p&gt;One enclave can securely control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ethereum&lt;/li&gt;
&lt;li&gt;Base&lt;/li&gt;
&lt;li&gt;Arbitrum&lt;/li&gt;
&lt;li&gt;future ecosystems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without custodial bridges.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Bigger Picture
&lt;/h1&gt;

&lt;p&gt;ROFL introduces something Web3 desperately needs:&lt;/p&gt;

&lt;h1&gt;
  
  
  Trustless Off-Chain Compute
&lt;/h1&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“another oracle”&lt;/li&gt;
&lt;li&gt;“another AI chain”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verifiable computation&lt;/li&gt;
&lt;li&gt;confidential execution&lt;/li&gt;
&lt;li&gt;autonomous infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a new primitive between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;smart contracts&lt;/li&gt;
&lt;li&gt;AI infrastructure&lt;/li&gt;
&lt;li&gt;confidential computing&lt;/li&gt;
&lt;li&gt;cross-chain orchestration&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Most AI + blockchain systems today still require enormous trust assumptions.&lt;/p&gt;

&lt;p&gt;With:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sapphire&lt;/li&gt;
&lt;li&gt;ROFL&lt;/li&gt;
&lt;li&gt;TDX&lt;/li&gt;
&lt;li&gt;attestation&lt;/li&gt;
&lt;li&gt;enclave-managed wallets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;developers can finally build systems where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;computation is private&lt;/li&gt;
&lt;li&gt;outputs are verifiable&lt;/li&gt;
&lt;li&gt;execution is cryptographically authenticated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This unlocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;autonomous AI agents&lt;/li&gt;
&lt;li&gt;confidential DeFi automation&lt;/li&gt;
&lt;li&gt;verifiable off-chain compute&lt;/li&gt;
&lt;li&gt;secure cross-chain execution&lt;/li&gt;
&lt;li&gt;trusted AI infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is likely only the beginning of what confidential compute on Oasis can enable.&lt;/p&gt;




&lt;h1&gt;
  
  
  Resources
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/" rel="noopener noreferrer"&gt;https://docs.oasis.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/rofl/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/rofl/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/rofl/workflow/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/rofl/workflow/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/tools/cli/rofl/" rel="noopener noreferrer"&gt;https://docs.oasis.io/build/tools/cli/rofl/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://oasis.net/sapphire" rel="noopener noreferrer"&gt;https://oasis.net/sapphire&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>privacy</category>
      <category>development</category>
    </item>
    <item>
      <title>Building a Confidential AI RAG Agent on Oasis Sapphire + ROFL</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Tue, 12 May 2026 08:42:31 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-confidential-ai-rag-agent-on-oasis-sapphire-rofl-4e5e</link>
      <guid>https://dev.to/rollingindo/building-a-confidential-ai-rag-agent-on-oasis-sapphire-rofl-4e5e</guid>
      <description>&lt;p&gt;&lt;em&gt;An advanced Oasis Network tutorial for developers who want to combine AI, confidential smart contracts, encrypted vector storage, and verifiable off-chain execution.&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Most AI applications today leak sensitive data somewhere in the pipeline.&lt;/p&gt;

&lt;p&gt;Even if the frontend is secure, prompts, embeddings, retrieval context, API calls, or inference metadata are usually visible to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud providers&lt;/li&gt;
&lt;li&gt;Backend operators&lt;/li&gt;
&lt;li&gt;Database administrators&lt;/li&gt;
&lt;li&gt;Analytics tools&lt;/li&gt;
&lt;li&gt;Infrastructure vendors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes a serious issue for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Financial AI assistants&lt;/li&gt;
&lt;li&gt;Healthcare copilots&lt;/li&gt;
&lt;li&gt;Private enterprise knowledge bases&lt;/li&gt;
&lt;li&gt;Identity systems&lt;/li&gt;
&lt;li&gt;Reputation engines&lt;/li&gt;
&lt;li&gt;On-chain AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this tutorial, we will build a &lt;strong&gt;Confidential Retrieval-Augmented Generation (RAG) Agent&lt;/strong&gt; using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Oasis Sapphire&lt;/li&gt;
&lt;li&gt;ROFL (Runtime Off-chain Logic)&lt;/li&gt;
&lt;li&gt;Encrypted storage&lt;/li&gt;
&lt;li&gt;Smart contracts&lt;/li&gt;
&lt;li&gt;Off-chain AI inference&lt;/li&gt;
&lt;li&gt;Verifiable confidential execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of storing prompts or embeddings publicly, we will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encrypt user knowledge before storage&lt;/li&gt;
&lt;li&gt;Perform confidential retrieval&lt;/li&gt;
&lt;li&gt;Execute AI inference off-chain through ROFL&lt;/li&gt;
&lt;li&gt;Return only approved outputs on-chain&lt;/li&gt;
&lt;li&gt;Keep sensitive data hidden from everyone except the user&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  What We Are Building
&lt;/h1&gt;

&lt;p&gt;We are building a confidential AI assistant where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users upload private documents&lt;/li&gt;
&lt;li&gt;Documents are encrypted before storage&lt;/li&gt;
&lt;li&gt;Embeddings are generated off-chain&lt;/li&gt;
&lt;li&gt;Retrieval happens confidentially&lt;/li&gt;
&lt;li&gt;AI inference runs through ROFL&lt;/li&gt;
&lt;li&gt;Smart contracts manage permissions and integrity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confidential EVM execution&lt;/li&gt;
&lt;li&gt;Off-chain compute&lt;/li&gt;
&lt;li&gt;AI pipelines&lt;/li&gt;
&lt;li&gt;Privacy-preserving storage&lt;/li&gt;
&lt;li&gt;Ethereum compatibility&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Architecture
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    A[User Uploads Document] --&amp;gt; B[Frontend Encrypts Data]
    B --&amp;gt; C[IPFS / Arweave Storage]
    B --&amp;gt; D[Oasis Sapphire Contract]

    D --&amp;gt; E[ROFL Worker]
    E --&amp;gt; F[Embedding Model]
    F --&amp;gt; G[Encrypted Vector Store]

    H[User Query] --&amp;gt; I[Confidential Retrieval]
    I --&amp;gt; G

    G --&amp;gt; J[Relevant Context]
    J --&amp;gt; K[LLM Inference]
    K --&amp;gt; L[Signed Response]
    L --&amp;gt; D

    D --&amp;gt; M[Verified Output Returned]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Why Sapphire Matters
&lt;/h1&gt;

&lt;p&gt;Traditional EVM chains expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contract state&lt;/li&gt;
&lt;li&gt;Function arguments&lt;/li&gt;
&lt;li&gt;Internal logic&lt;/li&gt;
&lt;li&gt;Metadata&lt;/li&gt;
&lt;li&gt;User activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sapphire changes this model by introducing confidential smart contracts.&lt;/p&gt;

&lt;p&gt;This allows developers to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encrypt application state&lt;/li&gt;
&lt;li&gt;Generate secrets securely&lt;/li&gt;
&lt;li&gt;Store confidential metadata&lt;/li&gt;
&lt;li&gt;Protect AI prompts&lt;/li&gt;
&lt;li&gt;Hide retrieval logic&lt;/li&gt;
&lt;li&gt;Secure wallet-based identity systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike “privacy through frontend encryption,” Sapphire provides confidentiality at the runtime layer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why ROFL Matters
&lt;/h1&gt;

&lt;p&gt;Large AI models cannot realistically run fully on-chain.&lt;/p&gt;

&lt;p&gt;Inference requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPUs&lt;/li&gt;
&lt;li&gt;External APIs&lt;/li&gt;
&lt;li&gt;Heavy compute&lt;/li&gt;
&lt;li&gt;Vector databases&lt;/li&gt;
&lt;li&gt;Long-running processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ROFL enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Off-chain execution&lt;/li&gt;
&lt;li&gt;Trusted compute&lt;/li&gt;
&lt;li&gt;Verifiable outputs&lt;/li&gt;
&lt;li&gt;Secure communication with Sapphire&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of ROFL as a confidential execution bridge between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smart contracts&lt;/li&gt;
&lt;li&gt;AI systems&lt;/li&gt;
&lt;li&gt;External infrastructure&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Tech Stack
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Smart Contracts&lt;/td&gt;
&lt;td&gt;Solidity + Sapphire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Off-chain Compute&lt;/td&gt;
&lt;td&gt;ROFL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;IPFS / Arweave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Embeddings&lt;/td&gt;
&lt;td&gt;Sentence Transformers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector Search&lt;/td&gt;
&lt;td&gt;ChromaDB / Qdrant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet&lt;/td&gt;
&lt;td&gt;RainbowKit + Wagmi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;AES-GCM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Inference&lt;/td&gt;
&lt;td&gt;OpenAI / Local LLM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Step 1: Setting Up Sapphire
&lt;/h1&gt;

&lt;p&gt;Create a new project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;confidential-rag-agent
&lt;span class="nb"&gt;cd &lt;/span&gt;confidential-rag-agent
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;hardhat ethers dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize Hardhat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add Sapphire configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@oasisprotocol/sapphire-hardhat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;solidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.8.20&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sapphire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://testnet.sapphire.oasis.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23295&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 2: Creating the Confidential Contract
&lt;/h1&gt;

&lt;p&gt;Our contract will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store encrypted metadata&lt;/li&gt;
&lt;li&gt;Register AI responses&lt;/li&gt;
&lt;li&gt;Verify ROFL signatures&lt;/li&gt;
&lt;li&gt;Manage access permissions
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ConfidentialRAG {

    struct Document {
        string cid;
        bytes encryptedKey;
        address owner;
    }

    mapping(uint256 =&amp;gt; Document) public documents;

    uint256 public docCounter;

    event DocumentStored(uint256 indexed id, address owner);

    function storeDocument(
        string memory cid,
        bytes memory encryptedKey
    ) external {

        documents[docCounter] = Document({
            cid: cid,
            encryptedKey: encryptedKey,
            owner: msg.sender
        });

        emit DocumentStored(docCounter, msg.sender);

        docCounter++;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 3: Encrypting User Data
&lt;/h1&gt;

&lt;p&gt;Never upload raw user data directly.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a local encryption key&lt;/li&gt;
&lt;li&gt;Encrypt the document client-side&lt;/li&gt;
&lt;li&gt;Upload encrypted data to IPFS&lt;/li&gt;
&lt;li&gt;Store only references on Sapphire&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant U as User
    participant F as Frontend
    participant I as IPFS
    participant S as Sapphire

    U-&amp;gt;&amp;gt;F: Upload document
    F-&amp;gt;&amp;gt;F: Generate AES key
    F-&amp;gt;&amp;gt;F: Encrypt document
    F-&amp;gt;&amp;gt;I: Upload encrypted file
    I--&amp;gt;&amp;gt;F: CID
    F-&amp;gt;&amp;gt;S: Store CID + encrypted key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example encryption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;encryptFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 4: Generating Embeddings Through ROFL
&lt;/h1&gt;

&lt;p&gt;We now process the encrypted content off-chain.&lt;/p&gt;

&lt;p&gt;ROFL workers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch encrypted files&lt;/li&gt;
&lt;li&gt;Decrypt securely&lt;/li&gt;
&lt;li&gt;Generate embeddings&lt;/li&gt;
&lt;li&gt;Store vectors&lt;/li&gt;
&lt;li&gt;Return signed proofs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ROFL Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    A[Sapphire Event] --&amp;gt; B[ROFL Worker]
    B --&amp;gt; C[Decrypt File]
    C --&amp;gt; D[Generate Embeddings]
    D --&amp;gt; E[Vector Database]
    E --&amp;gt; F[Signed Result]
    F --&amp;gt; G[Sapphire Contract]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example Python embedding pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 5: Confidential Retrieval
&lt;/h1&gt;

&lt;p&gt;When a user asks a question:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query embedding is generated&lt;/li&gt;
&lt;li&gt;Similar vectors are retrieved&lt;/li&gt;
&lt;li&gt;Relevant context is assembled&lt;/li&gt;
&lt;li&gt;Context stays encrypted/private&lt;/li&gt;
&lt;li&gt;LLM receives only authorized information&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This prevents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt leakage&lt;/li&gt;
&lt;li&gt;Public retrieval inspection&lt;/li&gt;
&lt;li&gt;Competitor scraping&lt;/li&gt;
&lt;li&gt;Metadata deanonymization&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Step 6: AI Inference Layer
&lt;/h1&gt;

&lt;p&gt;The inference engine can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI APIs&lt;/li&gt;
&lt;li&gt;Local Llama models&lt;/li&gt;
&lt;li&gt;Mistral&lt;/li&gt;
&lt;li&gt;DeepSeek&lt;/li&gt;
&lt;li&gt;Confidential GPU infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prompt assembly example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Context:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;retrieved_context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Question:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 7: Returning Verifiable Results
&lt;/h1&gt;

&lt;p&gt;ROFL signs outputs before returning them.&lt;/p&gt;

&lt;p&gt;The contract verifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authorized worker&lt;/li&gt;
&lt;li&gt;Valid signature&lt;/li&gt;
&lt;li&gt;Fresh timestamp&lt;/li&gt;
&lt;li&gt;Integrity proof&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a verifiable AI pipeline.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security Design
&lt;/h1&gt;

&lt;p&gt;Our architecture protects against:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threat&lt;/th&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public prompt leakage&lt;/td&gt;
&lt;td&gt;Sapphire confidentiality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage snooping&lt;/td&gt;
&lt;td&gt;Client-side encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector DB exposure&lt;/td&gt;
&lt;td&gt;Encrypted embeddings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fake AI outputs&lt;/td&gt;
&lt;td&gt;ROFL signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata correlation&lt;/td&gt;
&lt;td&gt;Confidential execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend compromise&lt;/td&gt;
&lt;td&gt;Wallet authorization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Is this Architecture good?
&lt;/h1&gt;

&lt;p&gt;Most Web3 AI projects today are not truly private.&lt;/p&gt;

&lt;p&gt;They usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store prompts publicly&lt;/li&gt;
&lt;li&gt;Leak retrieval context&lt;/li&gt;
&lt;li&gt;Expose embeddings&lt;/li&gt;
&lt;li&gt;Depend entirely on centralized APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oasis enables something different:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidential AI infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is a much bigger opportunity than simple “AI on blockchain” narratives. So yes, this is huge.&lt;/p&gt;




&lt;h1&gt;
  
  
  Improvements (!)
&lt;/h1&gt;

&lt;p&gt;You can extend this architecture with:&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Agent Systems
&lt;/h2&gt;

&lt;p&gt;Use multiple ROFL workers for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieval&lt;/li&gt;
&lt;li&gt;Ranking&lt;/li&gt;
&lt;li&gt;Moderation&lt;/li&gt;
&lt;li&gt;Inference&lt;/li&gt;
&lt;li&gt;Verification&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  zkML Integration
&lt;/h2&gt;

&lt;p&gt;Combine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ROFL confidentiality&lt;/li&gt;
&lt;li&gt;ZK proofs&lt;/li&gt;
&lt;li&gt;Verifiable inference&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Token-Gated Knowledge Access
&lt;/h2&gt;

&lt;p&gt;Require NFT or token ownership before retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Chain Identity
&lt;/h2&gt;

&lt;p&gt;Use confidential reputation scores across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ethereum&lt;/li&gt;
&lt;li&gt;Base&lt;/li&gt;
&lt;li&gt;Arbitrum&lt;/li&gt;
&lt;li&gt;Solana&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Potential Real-World Use Cases
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Industry&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Healthcare&lt;/td&gt;
&lt;td&gt;Private medical copilots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;td&gt;Confidential portfolio AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gaming&lt;/td&gt;
&lt;td&gt;Hidden strategy agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Secure internal knowledge AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DAOs&lt;/td&gt;
&lt;td&gt;Private governance research&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity&lt;/td&gt;
&lt;td&gt;Confidential trust scoring&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Performance Considerations
&lt;/h1&gt;

&lt;p&gt;AI systems are expensive.&lt;/p&gt;

&lt;p&gt;Optimize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embedding caching&lt;/li&gt;
&lt;li&gt;Batched retrieval&lt;/li&gt;
&lt;li&gt;Async inference&lt;/li&gt;
&lt;li&gt;Vector compression&lt;/li&gt;
&lt;li&gt;Partial encryption&lt;/li&gt;
&lt;li&gt;Event indexing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid pushing unnecessary data on-chain.&lt;/p&gt;

&lt;p&gt;The blockchain should coordinate trust, not store entire AI workloads.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common Mistakes Developers Make
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Storing Raw AI Data On-Chain
&lt;/h2&gt;

&lt;p&gt;Never store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompts&lt;/li&gt;
&lt;li&gt;Embeddings&lt;/li&gt;
&lt;li&gt;Conversations&lt;/li&gt;
&lt;li&gt;User documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Directly on public chains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treating Frontend Encryption as Enough
&lt;/h2&gt;

&lt;p&gt;Frontend-only privacy still exposes backend operators.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ignoring Metadata Privacy
&lt;/h2&gt;

&lt;p&gt;Even transaction patterns can reveal sensitive information.&lt;/p&gt;




&lt;h1&gt;
  
  
  Production Architecture
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TB
    subgraph User Layer
        A[Wallet User]
        B[Next.js Frontend]
    end

    subgraph Confidential Layer
        C[Sapphire Smart Contract]
        D[ROFL Workers]
    end

    subgraph AI Layer
        E[Embedding Models]
        F[LLM Inference]
        G[Vector Database]
    end

    subgraph Storage Layer
        H[IPFS]
        I[Arweave]
    end

    A --&amp;gt; B
    B --&amp;gt; C
    B --&amp;gt; H
    C --&amp;gt; D
    D --&amp;gt; E
    D --&amp;gt; F
    D --&amp;gt; G
    H --&amp;gt; D
    I --&amp;gt; D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Closingg Thoughts
&lt;/h1&gt;

&lt;p&gt;The next generation of AI applications will not be won by the teams with the biggest models.&lt;/p&gt;

&lt;p&gt;They will be won by the teams that solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy&lt;/li&gt;
&lt;li&gt;Data ownership&lt;/li&gt;
&lt;li&gt;Confidential computation&lt;/li&gt;
&lt;li&gt;Verifiable inference&lt;/li&gt;
&lt;li&gt;User-controlled AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oasis Sapphire and ROFL provide a powerful foundation for building that future.&lt;/p&gt;

&lt;p&gt;Instead of thinking about blockchain as "where data lives". think about it as a trust coordination layer for confidential AI systems.&lt;/p&gt;

&lt;p&gt;That shift changes everything.&lt;/p&gt;

&lt;h1&gt;
  
  
  Useful Resources
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Oasis Network
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://oasisprotocol.org" rel="noopener noreferrer"&gt;Oasis Network Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io" rel="noopener noreferrer"&gt;Oasis Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/sapphire/" rel="noopener noreferrer"&gt;Sapphire Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/rofl/" rel="noopener noreferrer"&gt;ROFL Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oasisprotocol" rel="noopener noreferrer"&gt;Oasis GitHub Organization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oasisprotocol/sapphire-paratime" rel="noopener noreferrer"&gt;Oasis Sapphire Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io/build/tutorials/" rel="noopener noreferrer"&gt;Oasis Developer Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ethereum / Smart Contract Development
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://book.getfoundry.sh" rel="noopener noreferrer"&gt;Foundry Book&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hardhat.org/docs" rel="noopener noreferrer"&gt;Hardhat Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openzeppelin.com/contracts" rel="noopener noreferrer"&gt;OpenZeppelin Contracts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ethers.org" rel="noopener noreferrer"&gt;Ethers.js Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Decentralized Storage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.ipfs.tech" rel="noopener noreferrer"&gt;IPFS Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pinata.cloud" rel="noopener noreferrer"&gt;Pinata IPFS Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.arweave.org" rel="noopener noreferrer"&gt;Arweave Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web3.storage" rel="noopener noreferrer"&gt;Web3.Storage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI / Vector Databases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.sbert.net" rel="noopener noreferrer"&gt;Sentence Transformers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.trychroma.com" rel="noopener noreferrer"&gt;ChromaDB Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qdrant.tech/documentation/" rel="noopener noreferrer"&gt;Qdrant Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://python.langchain.com/docs/introduction/" rel="noopener noreferrer"&gt;LangChain Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.llamaindex.ai" rel="noopener noreferrer"&gt;LlamaIndex Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frontend / Wallet Integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs" rel="noopener noreferrer"&gt;Next.js Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wagmi.sh" rel="noopener noreferrer"&gt;Wagmi Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rainbowkit.com/docs/introduction" rel="noopener noreferrer"&gt;RainbowKit Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security / Cryptography
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-project-smart-contract-top-10/" rel="noopener noreferrer"&gt;OWASP Smart Contract Top 10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.trailofbits.com" rel="noopener noreferrer"&gt;Trail of Bits Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://consensysdiligence.github.io/smart-contract-best-practices/" rel="noopener noreferrer"&gt;Consensys Smart Contract Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rag</category>
      <category>web3</category>
      <category>ai</category>
      <category>development</category>
    </item>
    <item>
      <title>Building a Transparent Crypto Donation Ledger with Oasis Network (Beginner Tutorial)</title>
      <dc:creator>rayQu</dc:creator>
      <pubDate>Mon, 20 Apr 2026 07:52:23 +0000</pubDate>
      <link>https://dev.to/rollingindo/building-a-transparent-crypto-donation-ledger-with-oasis-network-beginner-tutorial-5g9h</link>
      <guid>https://dev.to/rollingindo/building-a-transparent-crypto-donation-ledger-with-oasis-network-beginner-tutorial-5g9h</guid>
      <description>&lt;p&gt;Crypto donations are great for transparency… until your donors open a block explorer and see a wall of hashes.&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll build a &lt;strong&gt;simple, verifiable donation ledger&lt;/strong&gt; using Oasis Network, a blockchain that lets you combine &lt;strong&gt;privacy + transparency&lt;/strong&gt; using confidential smart contracts.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we’re building
&lt;/h2&gt;

&lt;p&gt;A minimal system where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Donations are recorded on-chain&lt;/li&gt;
&lt;li&gt;Expenses are recorded and linked to donations&lt;/li&gt;
&lt;li&gt;Anyone can see &lt;strong&gt;totals and categories (public)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Sensitive details stay &lt;strong&gt;private but provable&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Oasis?
&lt;/h2&gt;

&lt;p&gt;With Oasis Sapphire (its confidential EVM):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You write &lt;strong&gt;Solidity like Ethereum&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;But contract state can be &lt;strong&gt;encrypted&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You can expose only what matters (e.g. totals)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docs: &lt;a href="https://docs.oasis.io" rel="noopener noreferrer"&gt;https://docs.oasis.io&lt;/a&gt;&lt;br&gt;
Sapphire: &lt;a href="https://oasis.net/sapphire" rel="noopener noreferrer"&gt;https://oasis.net/sapphire&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1: Setup your environment
&lt;/h2&gt;

&lt;p&gt;You can use Hardhat like a normal EVM chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;oasis-ledger &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;oasis-ledger
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; hardhat
npx hardhat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add Oasis Sapphire testnet config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sapphire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://testnet.sapphire.oasis.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;solidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.8.20&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Write the smart contract
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;DonationLedger.sol&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract DonationLedger {

    struct Donation {
        address donor;
        uint amount;
    }

    struct Expense {
        uint amount;
        string category;
    }

    Donation[] public donations;
    Expense[] public expenses;

    uint public totalDonations;
    uint public totalSpent;

    function donate() external payable {
        donations.push(Donation(msg.sender, msg.value));
        totalDonations += msg.value;
    }

    function addExpense(uint _amount, string memory _category) external {
        expenses.push(Expense(_amount, _category));
        totalSpent += _amount;
    }

    function getBalance() public view returns (uint) {
        return totalDonations - totalSpent;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully transparent totals&lt;/li&gt;
&lt;li&gt;Expand later with private metadata (Oasis advantage)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Deploy to Oasis Sapphire
&lt;/h2&gt;

&lt;p&gt;Create a deploy script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DonationLedger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForDeployment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Deployed to:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAddress&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat run scripts/deploy.js &lt;span class="nt"&gt;--network&lt;/span&gt; sapphire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Interact with the contract
&lt;/h2&gt;

&lt;p&gt;Example (Hardhat console):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat console &lt;span class="nt"&gt;--network&lt;/span&gt; sapphire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DonationLedger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPLOYED_ADDRESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// donate&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;donate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// add expense&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addExpense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.05&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;food&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// check totals&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;totalDonations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;totalSpent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Build a simple frontend (optional)
&lt;/h2&gt;

&lt;p&gt;Even a basic UI can transform usability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total donations&lt;/li&gt;
&lt;li&gt;Total spent&lt;/li&gt;
&lt;li&gt;Remaining balance&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Add buttons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Donate”&lt;/li&gt;
&lt;li&gt;“Add Expense” (admin)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;You can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;ethers.js&lt;/li&gt;
&lt;li&gt;wagmi&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 6: Where Oasis shines (upgrade idea)
&lt;/h2&gt;

&lt;p&gt;Right now everything is public.&lt;/p&gt;

&lt;p&gt;With Oasis, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store &lt;strong&gt;private expense metadata&lt;/strong&gt; (e.g. invoice hashes)&lt;/li&gt;
&lt;li&gt;Keep donor notes confidential&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reveal only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;totals&lt;/li&gt;
&lt;li&gt;categories&lt;/li&gt;
&lt;li&gt;proofs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This is done using &lt;strong&gt;confidential state inside Sapphire contracts&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final result
&lt;/h2&gt;

&lt;p&gt;Instead of showing donors this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here’s our wallet, go check Etherscan”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You show them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;€100k raised&lt;/li&gt;
&lt;li&gt;€40k spent on food&lt;/li&gt;
&lt;li&gt;€20k spent on shelter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And every number is still &lt;strong&gt;verifiable on-chain&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Using Oasis, you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transparent donation systems&lt;/li&gt;
&lt;li&gt;Clean public dashboards&lt;/li&gt;
&lt;li&gt;Private but provable accounting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All without leaving the EVM ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://oasis.net" rel="noopener noreferrer"&gt;https://oasis.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oasis.io" rel="noopener noreferrer"&gt;https://docs.oasis.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://oasis.net/sapphire" rel="noopener noreferrer"&gt;https://oasis.net/sapphire&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
  </channel>
</rss>
