DEV Community

rayQu
rayQu

Posted on

Building a Confidential Onchain Payroll & Contractor Escrow with Oasis Sapphire + ROFL

Traditional blockchain payroll has an uncomfortable property.

The payment itself may be decentralized and programmable, but the information surrounding that payment is usually public.

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.

That creates a problem for organizations that want the advantages of programmable settlement without turning their payroll system into a public business-intelligence feed.

Consider a simple payroll transaction:

Company Treasury
      |
      | 4 ETH
      v
0x91...A42
Enter fullscreen mode Exit fullscreen mode

On a public EVM chain, there is no inherent distinction between:

"The company paid an employee."

and:

"The company transferred 4 ETH to this address."

The blockchain can verify the second statement.

It does not understand the first.

This article explores a different architecture using Oasis Sapphire and Oasis ROFL:

                    PUBLIC
              ┌─────────────────┐
              │ Company Treasury│
              │ Funding         │
              └────────┬────────┘
                       │
                       │ funds
                       ▼
              ┌─────────────────┐
              │ Sapphire        │
              │ Escrow          │
              │                 │
              │ PRIVATE         │
              │ salaries        │
              │ schedules       │
              │ recipients      │
              │ policies        │
              └────────┬────────┘
                       ▲
                       │ attested
                       │ payroll update
              ┌────────┴────────┐
              │ ROFL            │
              │                 │
              │ payroll adapter │
              │ validation      │
              │ computation     │
              └────────┬────────┘
                       ▲
                       │
              ┌────────┴────────┐
              │ Payroll system  │
              │ / ERP / HRIS    │
              └─────────────────┘
Enter fullscreen mode Exit fullscreen mode

The key idea is simple:

Put settlement rules onchain without putting the entire payroll database onchain.

Oasis Sapphire is an EVM-compatible confidential runtime that supports confidential contract state and encrypted transactions.

ROFL extends that model to applications running in trusted execution environments, allowing offchain workloads to interact with Sapphire while maintaining an attested application identity.

This combination gives us a useful architecture for a class of applications that doesn't get much attention in Web3:

private business processes with programmable settlement.

What We're Building

Our example system is a confidential contractor payroll protocol.

A company wants to hire 50 independent contractors.

Each contractor has:

  • a wallet
  • a compensation rate
  • a payment schedule
  • an engagement start/end date
  • an optional performance bonus
  • a private internal identifier

The company wants the actual settlement to be programmable.

But it does not want the public blockchain to reveal:

Alice → $14,000/month
Bob   → $8,500/month
Carol → $31,000/month
Enter fullscreen mode Exit fullscreen mode

Instead, the company funds an onchain escrow.

The confidential contract maintains the payroll state.

A ROFL application receives an authorized payroll update from the organization's existing backend and submits the corresponding state transition to Sapphire.

The final architecture looks like this:

This is not intended to replace an enterprise HR system.

The blockchain is being used for something much narrower:

confidential, programmable settlement.

Why Not Just Use a Database?

A reasonable objection is:

Why put payroll information on a blockchain at all?

For many organizations, a database is completely sufficient.

The interesting case is when multiple parties need guarantees around settlement.

Imagine a company using an external payroll processor.

The company might want the processor to calculate:

contractor compensation
+
approved expenses
+
performance bonuses
-
withholding
=
final settlement
Enter fullscreen mode Exit fullscreen mode

But the company doesn't necessarily want the processor to have unrestricted authority over the funds.

Likewise, a contractor may want stronger guarantees that an approved payment cannot simply disappear because an internal database was modified.

This produces a separation of responsibilities:

Payroll system
    |
    | calculates
    v
ROFL
    |
    | attested update
    v
Sapphire
    |
    | enforces settlement policy
    v
Funds
Enter fullscreen mode Exit fullscreen mode

The payroll backend determines what should happen.

The confidential smart contract determines what is allowed to happen.

That distinction is one of the most important architectural decisions in this design.

Why Sapphire?

Sapphire is particularly useful here because the application needs confidentiality at the smart-contract state level.

A normal EVM contract would expose storage such as:

mapping(address => uint256) salary;
Enter fullscreen mode Exit fullscreen mode

Even if the Solidity source doesn't provide a public getter, public blockchain state is generally observable through node/RPC interfaces and historical state.

Sapphire provides confidential contract state and encrypted communication between users and contracts.

That means we can maintain sensitive application state inside the confidential execution environment rather than treating the public blockchain as a payroll database.

The contract can therefore maintain records such as:

  • contractor wallet
  • salary
  • payment interval
  • start date
  • end date
  • bonus policy
  • last settlement
  • employment status

without automatically publishing those values as ordinary public application state.

Repository Structure

For the implementation, I'd structure the repository like this:

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
Enter fullscreen mode Exit fullscreen mode

The important point is that the ROFL application is not treated as a second smart contract.

It is an external confidential component with a specific authorization relationship to the Sapphire contract.

That gives us a much cleaner trust boundary.

The Core Data Model

Let's start with the internal payroll record.

struct Contractor {
    address wallet;
    uint64 startTime;
    uint64 endTime;
    uint128 monthlyRate;
    uint128 accrued;
    uint128 bonus;
    bool active;
}

Enter fullscreen mode Exit fullscreen mode

A production implementation would likely use a more sophisticated accounting model, but this is enough to demonstrate the architecture.

The mapping is deliberately private:

mapping(bytes32 => Contractor) private contractors;

Instead of using the employee's wallet as the primary identifier, we can derive a confidential internal identifier.

For example:

contractorId = keccak256(
    companyId ||
    internalEmployeeId ||
    deploymentSalt
)
Enter fullscreen mode Exit fullscreen mode

This isn't meant to be a magical privacy mechanism.

Hashing does not make low-entropy information confidential.

If the possible employee IDs are predictable, someone can brute-force them.

The purpose of the identifier is instead to separate the organization's internal record structure from its public wallet addresses.

The actual confidentiality comes from Sapphire's confidential execution model.

Payroll Accrual

The system needs to calculate how much a contractor has earned since their last settlement.

A simplified formula is:

elapsed = currentTime - lastSettlement
Enter fullscreen mode Exit fullscreen mode
earned =
    monthlyRate
    × elapsed
    ÷ billingPeriod
Enter fullscreen mode Exit fullscreen mode

For example:

monthlyRate = $10,000
billingPeriod = 30 days
elapsed = 15 days

earned = $5,000

In a real implementation, you should avoid floating point arithmetic entirely.

Use fixed-point integer arithmetic and explicitly define:

  • rounding direction
  • minimum settlement
  • maximum accrual
  • timestamp boundaries
  • partial periods
  • leap-month handling
  • token decimal conversion

Financial contracts should never leave rounding behavior implicit.

The Confidential Payroll Contract

The high-level contract can look like this:

// 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 => 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 <= startTime) {
            revert InvalidSchedule();
        }


        contractors[id] = Contractor({
            wallet: wallet,
            startTime: startTime,
            endTime: endTime,
            ratePerPeriod: rate,
            accrued: 0,
            lastSettlement: startTime,
            active: true
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

There is already an important property here:

The normal wallet cannot create arbitrary payroll records.

Only the authorized payroll application can modify the internal state.

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.

Settlement Should Be Separate From Accrual

One mistake would be to combine everything into a single function:

calculate salary
+
modify employee
+
authorize payment
+
transfer funds
Enter fullscreen mode Exit fullscreen mode

That creates a large security surface.

Instead, separate the lifecycle:

REGISTER
   ↓
ACTIVE
   ↓
ACCRUING
   ↓
SETTLEMENT READY
   ↓
PAID
   ↓
NEXT PERIOD

Enter fullscreen mode Exit fullscreen mode

This gives us much clearer invariants.

For example:

A contractor cannot be paid more than their accrued amount.

And:

An inactive contractor cannot generate additional compensation.

And:

A settlement cannot be executed twice for the same accounting period.

Those invariants are much easier to fuzz-test.

The Settlement Function

Conceptually:

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);
}
Enter fullscreen mode Exit fullscreen mode

The important security property isn't the transfer itself.

It is the accounting relationship around it.

We want:

new accrued balance
=
old accrued balance
+
newly earned
-
settled amount
Enter fullscreen mode Exit fullscreen mode

to remain true after every state transition.

That becomes the foundation of the test suite.

Don't Make the Payroll Adapter the Custodian

This is probably the most important design decision in the entire system.

A tempting architecture is:

Payroll backend
       ↓
ROFL
       ↓
Treasury private key
       ↓
Contractor
Enter fullscreen mode Exit fullscreen mode

That gives ROFL too much power.

If the ROFL application is compromised, the attacker potentially gets direct control over funds.

Instead:

Payroll backend
       ↓
ROFL
       ↓
Sapphire policy
       ↓
Escrow
       ↓
Contractor
Enter fullscreen mode Exit fullscreen mode

ROFL should be able to submit claims, not arbitrarily withdraw money.

The confidential contract should enforce:

claim <= accrued
claim <= configured limit
claim belongs to contractor
contractor is active
period hasn't already been settled
Enter fullscreen mode Exit fullscreen mode

This is classic least-privilege design.

The confidential execution environment is not an excuse to give one component unlimited authority.

Funding the Escrow

The company can fund the payroll contract separately.

For example:

Treasury
   |
   | 100,000 USDC
   v
Payroll Escrow
Enter fullscreen mode Exit fullscreen mode

The public blockchain can therefore show:

Payroll escrow balance = 100,000 USDC

while the internal distribution remains confidential.

That is a useful compromise.

The company may want the total reserve to be publicly auditable while keeping the individual salary distribution private.

This is an important distinction:

Confidentiality does not require making everything opaque.

A well-designed confidential application should expose exactly the information that external parties need to verify its public commitments.

Public vs Private State

I'd explicitly document this in the repository.

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
Enter fullscreen mode Exit fullscreen mode

This is much more useful than simply saying:

"The contract is private."

Privacy should be defined field-by-field.

Confidential Events

Events require particular care.

On ordinary EVM networks, event data is publicly visible.

Sapphire supports encrypted event payloads while retaining public event topics for indexing and offchain triggers.

That gives us an interesting pattern.

Suppose we want a contractor to receive a payment notification containing:

  • paymentId
  • period
  • amount
  • timestamp

We don't necessarily want all of those fields to become public plaintext.

Instead, the event can expose a public topic while keeping the sensitive payload encrypted for an authorized consumer.

Conceptually:

Public:

PayrollSettlement(contractorIdHash, paymentId)

Encrypted:

amount
period
internal payroll metadata

This gives infrastructure systems something to index without turning the event stream into a public payroll ledger.

Where ROFL Becomes Useful

The real world rarely stores payroll data directly onchain.

An organization might already have:

  • Workday
  • SAP
  • QuickBooks
  • Stripe
  • custom HR system
  • internal PostgreSQL

We don't want the smart contract trying to query these systems directly.

Instead, ROFL can operate as the confidential adapter.

ROFL applications run inside TEEs and can interact with external systems through authenticated connections while maintaining an attested application identity.

This is exactly the kind of workload that benefits from separating:

external data ingestion

from:

onchain settlement policy.

Example ROFL Workflow

Suppose the company's payroll system produces:

{
  "employee": "employee-9182",
  "period": "2026-08",
  "approvedAmount": "8500000000",
  "currency": "USDC"
}
Enter fullscreen mode Exit fullscreen mode

ROFL shouldn't blindly forward this.

It should validate:

  • Is this payroll period valid?
  • Is the employee registered?
  • Is the amount within policy?
  • Is this period already settled?
  • Is the payroll file authentic?
  • Is the source system authorized?
  • Is the deployment version expected?

Only after those checks should the ROFL application construct the Sapphire transaction.

Pseudo-code:

async function processPayroll(record: PayrollRecord) {
  validateSchema(record);


  const employee = await payroll.lookup(record.employee);


  if (!employee.active) {
    throw new Error("Inactive employee");
  }


  if (!verifyPayrollSource(record)) {
    throw new Error("Invalid payroll source");
  }


  const amount = calculateSettlement(record);


  return sapphire.submitSettlement({
    contractorId: employee.privateId,
    amount,
    period: record.period
  });
}

Enter fullscreen mode Exit fullscreen mode

The important thing is that ROFL validates; Sapphire enforces.

Those are different responsibilities.

Preventing Replay Attacks

Payroll systems are particularly vulnerable to replay.

Imagine a valid payroll message:

Alice
August
$8,500

An attacker somehow submits the same message twice.

Without replay protection:

August payment
+
August payment
=
$17,000
Enter fullscreen mode Exit fullscreen mode

The contract needs a settlement nonce or period identifier.

For example:

mapping(bytes32 => mapping(uint64 => bool)) private settled;
Enter fullscreen mode Exit fullscreen mode

Then:

if (settled[id][period]) {
    revert AlreadySettled();
}
Enter fullscreen mode Exit fullscreen mode
settled[id][period] = true;
Enter fullscreen mode Exit fullscreen mode

The invariant becomes:

∀ contractor, period:
    settlement_count <= 1
Enter fullscreen mode Exit fullscreen mode

This should be tested extensively.

What Happens if ROFL Goes Offline?

This is another reason not to make ROFL the sole source of truth.

Suppose the ROFL application crashes for six hours.

The escrow shouldn't become corrupted.

The correct behavior should simply be:

No payroll update
        ↓
No state corruption
        ↓
Existing escrow remains intact
        ↓
ROFL recovers
        ↓
Pending payroll period processed
Enter fullscreen mode Exit fullscreen mode

This is where idempotency becomes important.

The payroll processor should be able to safely retry the same period without causing a double payment.

A good implementation therefore treats payroll updates as state transitions rather than one-shot commands.

Handling Corrections

Real payroll isn't perfectly linear.

Someone might receive:

base salary
+
bonus
-
expense correction
Enter fullscreen mode Exit fullscreen mode

after the original payroll calculation.

Instead of modifying historical payments, create a new adjustment:

August salary
        +
August bonus
        -
expense correction
        =
August final adjustment
Enter fullscreen mode Exit fullscreen mode

This produces an append-only accounting model.

The confidential state can remain private while the protocol still preserves an internal accounting history.

That makes audits much easier.

Testing the Important Invariants

A production implementation should not stop at unit tests.

I'd use Foundry fuzzing to test the accounting model.

For example:

function invariant_neverPayMoreThanAccrued()
    public
{
    assertLe(
        totalSettled,
        totalAccrued
    );
}
Enter fullscreen mode Exit fullscreen mode

Another invariant:

function invariant_noPeriodCanSettleTwice()
    public
{
    for (...) {
        assertLe(
            settlementCount[period],
            1
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

And:

function invariant_inactiveContractorCannotAccrue()
    public
{
    if (!contractor.active) {
        assertEq(
            accruedSinceDeactivation,
            0
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

The exact implementation will depend on the accounting model, but the philosophy is important:

test properties, not examples.

A test that says:

Alice receives $8,500

is useful.

A property that says:

no contractor can receive more than the amount authorized by the accounting state

is much more powerful.

Threat Model

Let's explicitly define the attackers.

Attacker 1: Malicious contractor

They control their wallet and attempt to claim more than they earned.

Defense:

  • contract-enforced accrual
  • period tracking
  • maximum claim
  • replay protection

Attacker 2: Compromised payroll backend

The backend generates a fraudulent payroll record.

Defense:

  • ROFL validation
  • authorized source
  • contract-level policy limits
  • maximum settlement bounds

Attacker 3: Compromised ROFL application

The ROFL process attempts to submit arbitrary payments.

Defense:

  • restricted contract interface
  • bounded permissions
  • per-contractor limits
  • period uniqueness
  • escrow accounting

Attacker 4: Replay attacker

They resubmit a valid payroll message.

Defense:

  • period nonce
  • settlement identifier
  • idempotent state transitions

Attacker 5: Curious blockchain observer

They attempt to reconstruct salary information from public chain data.

Defense:

  • confidential Sapphire state
  • encrypted transaction inputs
  • encrypted event payloads where appropriate
  • minimal public disclosure

Attacker 6: Compromised frontend

The frontend tries to trick a user into authorizing a malicious action.

Defense:

  • strict contract authorization
  • typed transaction validation
  • domain separation
  • least-privilege interfaces

The important observation is that confidentiality only solves one part of the threat model.

It does not make authorization, accounting, or business logic automatically secure.

What About the Company Treasury?

There is another architectural decision.

Should the treasury itself live on Sapphire?

There are several possibilities.

Fully confidential

Treasury
   ↓
Sapphire
   ↓
Payroll
Enter fullscreen mode Exit fullscreen mode

This provides maximum privacy but reduces public transparency.

Public treasury + confidential payroll

Public Treasury
       ↓
Sapphire Escrow
       ↓
Private Payroll
Enter fullscreen mode Exit fullscreen mode

This is probably the more interesting model for many organizations.

The public treasury can prove that the company has funded the payroll system.

The private contract controls individual distributions.

Hybrid

Public reserve
      +
Private employee allocations
      +
Public aggregate settlement
Enter fullscreen mode Exit fullscreen mode

This gives organizations control over exactly which information becomes publicly auditable.

That is the model I'd recommend exploring first.

Why This Is Different From a Traditional Payroll Smart Contract

A normal payroll contract might answer:

"Did address X receive Y tokens?"

This architecture asks a more useful question:

"Can the organization prove that its payroll policy was enforced without exposing the organization's entire payroll database?"

That distinction is where confidential blockchain infrastructure becomes genuinely useful.

The blockchain becomes a policy enforcement layer, not a database.

A Possible Production Deployment

A production deployment might look like:

  • This architecture gives every component a narrow responsibility.
  • The enterprise systems know the business.
  • ROFL handles confidential external computation and integration.
  • Sapphire enforces private state transitions.
  • The escrow controls settlement.
  • The public treasury provides the funding layer.

Operational Monitoring

Confidentiality also changes how monitoring should work.

A traditional blockchain monitoring system might inspect:

  • salary
  • recipient
  • amount
  • timestamp

and reconstruct the entire payroll history.

That isn't desirable here.

Instead, monitoring should focus on public commitments and system health.

For example:

  • Escrow funded
  • ROFL application active
  • Payroll epoch completed
  • Settlement batch committed
  • Contract version changed
  • Emergency mode activated

Sensitive details remain inside the confidential system.

This creates a useful principle:

Monitor the protocol's guarantees, not necessarily the protocol's private inputs.

Sapphire's encrypted-event support is useful here because applications can retain public event topics for indexing while keeping event payloads confidential.

What Sapphire Does Not Hide

This distinction is important enough to state explicitly.

A confidential contract does not mean that every piece of information surrounding a transaction automatically disappears from the public world.

Depending on the application architecture, observers may still learn things such as:

  • a transaction occurred
  • a contract was called
  • a public balance changed
  • a public funding transaction happened
  • a settlement-related event exists

The goal is therefore not:

"Make the blockchain invisible."

It is:

"Keep specifically identified sensitive state and execution inputs confidential while exposing the minimum information necessary for interoperability and verification."

That's a much more realistic security model.

Why ROFL Instead of Putting Everything in Sapphire?

Because not every task belongs inside a smart contract.

A smart contract is excellent at:

  • state transitions
  • authorization
  • accounting
  • settlement
  • deterministic rules

It is not the ideal place to:

  • parse enterprise payroll files
  • call an external HR API
  • process complex business data
  • maintain authenticated external connections
  • run arbitrary application logic

ROFL is designed specifically for offchain computation with private data and verifiable results, and it integrates with Sapphire for authenticated smart-contract interactions.

The combination is therefore more powerful than either component by itself.

The Repository's Security Checklist

Before considering the implementation production-ready, I'd want the repository to contain a checklist like:

[ ] ROFL caller authentication
[ ] Payroll source authentication
[ ] Replay protection
[ ] Period uniqueness
[ ] Maximum payment limits
[ ] Maximum contractor exposure
[ ] Escrow solvency checks
[ ] Integer overflow protection
[ ] Explicit rounding rules
[ ] Emergency pause
[ ] Emergency expiry
[ ] Recovery mechanism
[ ] Contract upgrade policy
[ ] ROFL deployment policy
[ ] Key rotation procedure
[ ] Confidential event policy
[ ] Fuzz tests
[ ] Invariant tests
[ ] Failure recovery tests
[ ] ROFL outage tests
[ ] Duplicate payroll tests
[ ] Malicious payroll input tests

This is the part I'd consider more important than adding another frontend feature.

A confidential financial application has a very large attack surface if the trust boundaries aren't explicitly documented.

Where This Could Go Beyond Payroll

Once the architecture exists, payroll is only one application.

The same primitive could support:

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
Enter fullscreen mode Exit fullscreen mode

The common primitive is:

private business state
        +
programmable settlement
        +
restricted authorization
        +
verifiable execution
Enter fullscreen mode Exit fullscreen mode

That combination is difficult to achieve cleanly with a conventional public EVM.

The More Interesting Question

The interesting question isn't really:

"Can blockchain do payroll?"

It obviously can.

The harder question is:

Can blockchain enforce a business process without forcing the business to publish its internal state?

That's where Oasis's architecture becomes interesting.

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.

Together, they allow developers to draw a much sharper boundary between:

What must be public

and:

What must merely be verifiable

Those aren't the same thing.

  • A company may need to prove that its payroll escrow is funded.
  • It doesn't necessarily need to publish every employee's salary.
  • A contractor may need to prove that a payment was authorized.
  • They don't necessarily need to expose the entire company's payroll ledger.
  • An auditor may need evidence that a policy was followed.
  • They don't necessarily need unrestricted access to every private record.

That is the fundamental design space.

Final Architecture

The complete system can be summarized as:

PUBLIC
                           │
                    ┌──────▼──────┐
                    │   Treasury  │
                    └──────┬──────┘
                           │
                       Funding
                           │
                           ▼
                 ┌───────────────────┐
                 │ Sapphire Escrow   │
                 │                   │
                 │ Confidential      │
                 │ settlement state  │
                 └─────────▲─────────┘
                           │
                    authenticated
                       ROFL call
                           │
                 ┌─────────┴─────────┐
                 │       ROFL        │
                 │                   │
                 │ Validation        │
                 │ Aggregation       │
                 │ External APIs     │
                 │ Attested compute  │
                 └─────────▲─────────┘
                           │
                           │
                 ┌─────────┴─────────┐
                 │ HR / Payroll / ERP│
                 └───────────────────┘


                           │
                           ▼


                 ┌───────────────────┐
                 │ Contractor Wallet │
                 └───────────────────┘
Enter fullscreen mode Exit fullscreen mode
  • The architecture deliberately avoids making one component responsible for everything.
  • The payroll system knows the business rules.
  • ROFL handles the confidential external computation.
  • Sapphire enforces the settlement policy.
  • The escrow controls the money.
  • And the public chain exposes only the information that actually needs to be public.

That's the more interesting promise of confidential blockchain infrastructure: not simply hiding transactions, but making privacy a property of the application's architecture.

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.

The result isn't a private database with a token attached.

It's a programmable business process where confidential state, external computation, and onchain settlement can coexist without requiring the entire workflow to become public.

Resources

Oasis Sapphire

Oasis ROFL

Encrypted Events

Oasis Build Documentation

Top comments (0)