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
Follow the official steps to install Foundry.
Initialize a project
forge init simple_mixer
cd simple_mixer
There should already be a
Counter.sol
file insidesrc
. Let's create a new onesrc/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);
}
}
Compile the contract:
forge build
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.
Deploy the contract:
forge create SimpleMixer --rpc-url $RPC_URL --private-key $PRIVATE_KEY
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
Create a commitment with some data:
cast keccak256 <some data string>
Also create a nullifier:
cast keccak256 <some nullifier string>
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.
Withdraw from the mixer.
The recipient address should now have received the withdrawn amount.
Last updated