Skip to content

Deploying Strata smart contracts

In this walkthrough we will go through creating a very simple Tornado Cash-like mixer service, deploying it, and doing some transfers.

Note

This assumes that you have your local git configured

  1. Follow the official steps to install foundry.

  2. Initialize a project

    forge init simple_mixer
    cd simple_mixer
    
  3. There should already be a Counter.sol file inside src. Let's create a new one src/SimpleMixer.sol and write the following to that file:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    contract SimpleMixer {
        struct Deposit {
            uint256 amount;
            bytes32 commitment;
        }
    
        mapping(bytes32 => bool) public commitments; // Track unique deposits by commitment
        mapping(bytes32 => bool) public nullifiers;   // Prevent double withdrawals
        mapping(bytes32 => uint256) public deposits;  // Track deposit amounts per commitment
    
        event DepositMade(bytes32 indexed commitment, uint256 amount);
        event WithdrawalMade(address indexed recipient, uint256 amount);
    
        // Make a deposit with a unique commitment hash
        function deposit(bytes32 commitment) external payable {
            require(msg.value > 0, "Deposit must be greater than 0");
            require(!commitments[commitment], "Commitment already used");
    
            commitments[commitment] = true;
            deposits[commitment] = msg.value; // Track the deposited amount for the commitment
    
            emit DepositMade(commitment, msg.value);
        }
    
        // Withdraw anonymously using a nullifier and commitment proof (simplified here)
        function withdraw(
            address payable recipient, 
            bytes32 commitment, 
            bytes32 nullifier
        ) external {
            require(deposits[commitment] > 0, "Invalid or already withdrawn commitment");
            require(!nullifiers[nullifier], "Nullifier already used");
    
            // Here, you'd typically verify a zk-SNARK proof for true anonymity (skipped for this example)
    
            uint256 amount = deposits[commitment];
    
            // Mark the nullifier as used to prevent double spending
            nullifiers[nullifier] = true;
            deposits[commitment] = 0; // Prevent further withdrawals for this commitment
    
            recipient.transfer(amount); // Transfer only the amount associated with this deposit
    
            emit WithdrawalMade(recipient, amount);
        }
    }
    
  4. Compile the contract: forge build

  5. Before we deploy the contract, we need to make sure we are running a Strata client or can connect to a Strata network. Add the following to a .env file:

    export PRIVATE_KEY=<your private key>
    export RPC_URL=https://stratareth3666f0713.devnet-annapurna.stratabtc.org
    

And run source .env to load those in the shell session.

  1. Deploy the contract:

    forge create SimpleMixer --rpc-url $RPC_URL  --private-key $PRIVATE_KEY
    
  2. When it completes successfully, you should see something like the following. Note the Deployed to address which is the contract address.

    1
    2
    3
    Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
    Deployed to: 0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07
    Transaction hash: 0x8061e1e2f7bb32bab7b6ea6b8dc6a394b05ce3394ea1be88b1a478fc9d03ee3f
    

Congratulations! We have now successfully deployed the mixer service.

Depositing and withdrawing with cast

  1. Create a commitment with some data: cast keccak256 <some data string>
  2. Also create a nullifier: cast keccak256 <some nullifier string>
  3. Deposit some amount to the mixer:

    cast send <CONTRACT_ADDRESS from step 7> "deposit(bytes32)" <COMMITMENT from step 1> --value 1000000000000000 --rpc-url $RPC_URL --private-key $PRIVATE_KEY
    

    You should now see some amount deducted from your account.

  4. Withdraw from the mixer.

Note

For the withdrawal to be anonymous, you should first reconfigure your .env file to use the private key of a different address than the address you used for the deposit step. The deposit address should never have interacted with this withdrawal address.

cast send <CONTRACT_ADDRESS from step 7> "withdraw(address,bytes32,bytes32)" <RECIPIENT ADDRESS> <COMMITMENT from step 1> <NULLIFIER from step 2> --rpc-url $RPC_URL --private-key $PRIVATE_KEY

The recipient address should now have received the withdrawn amount.