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