DEV Community

flat cash
flat cash

Posted on

BearerSwap: How Bearer Instruments Work On-Chain

Rebuilding Digital Cash on Ethereum: How BearerSwap Achieves Unlinkable Transfers Without ZK Ceremonies

Every transaction you execute on Ethereum is a public broadcast. While transparency is great for auditability, it turns accounting into a surveillance nightmare. Your salary, savings, treasury operations, and protocol interactions form a permanent, traceable graph open to anyone with a block explorer.

Traditional approaches to fixing this fall into two camps:

  1. Mixers (e.g., Tornado Cash): Heavy regulatory targets that rely on complex anonymity sets and face intense scrutiny.
  2. Zero-Knowledge Protocols: Require heavy cryptographic circuits, trusted setups, and often complex proving/verifying overhead.

Enter BearerSwap (deployed on Ethereum mainnet at 0xD46633C54058D28Cad5d77C897df042dCCdADF4c). It takes inspiration from physical cash—where whoever holds the instrument owns it, and handing it over leaves no ledger trail—and encodes that principle directly into a lightweight, trustless Solidity primitive.


The Core Concept: Digital Bearer Instruments

Physical cash is private because ownership is decoupled from identity. When you hand a $20 bill to a merchant, there is no ledger entry connecting your bank account to theirs.

BearerSwap replicates this on Ethereum via a commit-reveal mechanism:

  1. The Deposit (Sender): Alice hashes a secret string (keccak256(abi.encodePacked(secret))) and deposits tokens into the contract alongside that commitment hash.
  2. The Reveal (Receiver): Bob (or Alice using an entirely fresh wallet) calls the contract, submitting the raw secret. If keccak256(abi.encodePacked(secret)) matches the stored commitment, the contract releases the funds.

What the blockchain sees:

  • Tx 1: 0xA1b2... (Alice) $\rightarrow$ Deposit to Contract (Locks funds to a hash commitment).
  • Tx 2: 0xF9e8... (Bob) $\rightarrow$ Reveal & Claim from Contract.

There is no on-chain link between 0xA1b2... and 0xF9e8.... Graph analysis tools cannot connect the sender and receiver because the only bridge between them is an off-chain secret passed securely (e.g., via Signal, encrypted email, or an API).


Architectural Breakdown

BearerSwap V4 is designed as a minimalist, immutable primitive.

  • No Admin Keys: There is no proxy pattern, no upgrade mechanism, and no developer kill-switch.
  • No Trusted Relayers: Users interact directly with the contract.
  • Fee Structure: A nominal 0.1% fee is integrated to handle protocol mechanics and incentive loops.
  • Reclaim Safety: If a transfer is never claimed, deposits include a safety timeout mechanism (e.g., a 30-day reclaim delay) allowing the original depositor to recover unclaimed funds.

Solidity Implementation Pattern

To understand how clean this primitive is, let’s look at a simplified conceptual implementation of how a commit-reveal bearer transfer operates in Solidity.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title Simplified BearerSwap Core Concept
 * @notice Demonstrates the commit-reveal pattern for unlinkable token transfers.
 * Mainnet Contract: 0xD46633C54058D28Cad5d77C897df042dCCdADF4c
 */
contract BearerSwapSnippet is ReentrancyGuard {
    struct DepositInfo {
        address token;
        uint256 amount;
        uint256 timestamp;
        bool claimed;
    }

    // commitmentHash => DepositInfo
    mapping(bytes32 => DepositInfo) public deposits;

    uint256 public constant FEE_BPS = 10; // 0.1% (10 basis points)
    uint256 public constant RECLAIM_DELAY = 30 days;

    event Deposited(bytes32 indexed commitment, address indexed token, uint256 amount);
    event Claimed(bytes32 indexed commitment, address indexed receiver, uint256 amount);
    event Reclaimed(bytes32 indexed commitment, address indexed sender);

    /**
     * @notice Step 1: Sender locks tokens with a cryptographic commitment hash.
     * @param token The ERC20 token address (or address(0) for native ETH if handled).
     * @param amount The amount of tokens to deposit.
     * @param commitment keccak256(abi.encodePacked(secret))
     */
    function deposit(
        address token, 
        uint256 amount, 
        bytes32 commitment
    ) external nonReentrant {
        require(deposits[commitment].amount == 0, "Commitment already exists");
        require(amount > 0, "Amount must be greater than zero");

        // Transfer tokens from sender to contract
        bool success = IERC20(token).transferFrom(msg.sender, address(this), amount);
        require(success, "Token transfer failed");

        // Calculate fee (0.1%)
        uint256 fee = (amount * FEE_BPS) / 10000;
        uint256 netAmount = amount - fee;

        deposits[commitment] = DepositInfo({
            token: token,
            amount: netAmount,
            timestamp: block.timestamp,
            claimed: false
        });

        // Optionally route the fee to treasury/buyback mechanisms here
        if (fee > 0) {
            IERC20(token).transfer(msg.sender /* or fee collector */, fee);
        }

        emit Deposited(commitment, token, netAmount);
    }

    /**
     * @notice Step 2: Receiver claims tokens from a fresh address by revealing the secret.
     * @param secret The plaintext secret chosen by the sender.
     * @param commitment The matching commitment hash.
     */
    function reveal(bytes32 secret, bytes32 commitment) external nonReentrant {
        DepositInfo storage dep = deposits[commitment];

        require(!dep.claimed, "Already claimed");
        require(dep.amount > 0, "Deposit does not exist");

        // Verify the secret matches the commitment
        require(keccak256(abi.encodePacked(secret)) == commitment, "Invalid secret");

        dep.claimed = true;

        uint256 payout = dep.amount;
        bool success = IERC20(dep.token).transfer(msg.sender, payout);
        require(success, "Payout transfer failed");

        emit Claimed(commitment, msg.sender, payout);
    }

    /**
     * @notice Reclaim funds if left unclaimed past the safety delay.
     */
    function reclaim(bytes32 commitment, address receiver) external nonReentrant {
        DepositInfo storage dep = deposits[commitment];
        require(!dep.claimed, "Already claimed");
        require(block.timestamp >= dep.timestamp + RECLAIM_DELAY, "Too early to reclaim");

        dep.claimed = true;
        bool success = IERC20(dep.token).transfer(receiver, dep.amount);
        require(success, "Reclaim transfer failed");

        emit Reclaimed(commitment, receiver);
    }
}
Enter fullscreen mode Exit fullscreen mode

Composing Privacy Into Any Protocol

Because BearerSwap exposes a minimal API (deposit() and reveal()), it acts as a composable primitive rather than a walled garden. Developers can wrap interactions to introduce privacy natively into existing DeFi rails:

  • Private DAO Payrolls: DAOs can fund contributor commitments without linking treasury wallets directly to individual team member addresses.
  • MEV-Resistant OTC Deals: Large volume trades can be structured off-chain via commitments, settling trustlessly on-chain without exposing front-running vectors in standard mempools.
  • Autonomous AI Agents: AI agents executing programmatic tasks can utilize MCP (Model Context Protocol) servers connected to BearerSwap primitives to settle bounties or pay for compute resources anonymously without revealing operational wallets.

Summary

BearerSwap strips away the complexity of zero-knowledge circuits and the counterparty risks of centralized mixers, relying entirely on cryptographic hashes and standard EVM states. By treating tokens like digital bearer bonds, it restores the privacy property that physical cash always had, directly on the Ethereum mainnet.

Check out the immutable deployment on Etherscan at 0xD46633C54058D28Cad5d77C897df042dCCdADF4c or explore specifications at flat.cash/contracts.

Top comments (0)