Deploying Alpen smart contracts

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

Deploying a mixer contract

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
  1. 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);
    }
}
  1. Compile the contract: forge build

For the next step, you need a wallet that has an option to view your private key. The Alpen CLI does not have an option to view the private key for the Alpen wallet created. You can view the private key associated with a BIP39 seed passphrase using cast wallet private-key commands in Foundry. See the official Foundry documentation for more information.

  1. Before we deploy the contract, we need to make sure we are running an Alpen client or can connect to the Alpen network. Add the following lines to a .env file in your project folder:

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

  1. Deploy the contract:

  1. When it completes successfully, you should see something like the following. Note the Deployed to address which is the contract address.

Congratulations! We have now successfully deployed the mixer contract.

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:

You should now see some amount deducted from your account.

  1. Withdraw from the mixer.

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 of the previous section> "withdraw(address,bytes32,bytes32)" <RECIPIENT ADDRESS> <COMMITMENT from step 1> <NULLIFIER from step 2> --rpc-url $ETH_RPC_URL --private-key $PRIVATE_KEY

The recipient address should now have received the withdrawn amount.

Last updated