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 using a tool similar to this: https://iancoleman.io/bip39/. The tool requires you to enter your wallet seed phrase. Select Ethereum as Coin. Then select the private key corresponding to the derivation path m/44'/60'/0'/0/0.

  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 to a .env file:

export PRIVATE_KEY=<your private key>
export RPC_URL=https://rpc.testnet.alpenlabs.io

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
  1. When it completes successfully, you should see something like the following. Note the Deployed to address which is the contract address.

Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07
Transaction hash: 0x8061e1e2f7bb32bab7b6ea6b8dc6a394b05ce3394ea1be88b1a478fc9d03ee3f

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:

cast send <CONTRACT_ADDRESS from step 7 of the previous section> "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.

  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 $RPC_URL --private-key $PRIVATE_KEY

The recipient address should now have received the withdrawn amount.

Last updated