In this tutorial, we will create an Escrow contract using Solidity and deploy it to an Ethereum testnet. An Escrow contract is a financial agreement where a third party (the arbitrator) holds and regulates payment of the funds required for two parties involved in a given transaction. It helps make transactions more secure as it holds the money until all the terms of an agreement are met.
Escrow Solidity Example
Step 1: Setting Up the Environment
For simplicity, we’ll use Remix IDE, a browser based IDE for Solidity.
- Go to https://remix.ethereum.org
- Create a new file and name it
Escrow.sol
. - Paste in the following code
The full code can be found here: https://github.com/jamesbachini/Solidity-Snippets/blob/main/contracts/escrow.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Escrow {
struct Agreement {
address bob;
address alice;
address arbitrator;
uint amount;
bool bobIn;
bool aliceIn;
}
Agreement[] public agreements;
function newAgreement(address _bob, address _alice, uint _amount) external returns (uint) {
require(_bob != _alice, "same buyer and seller");
agreements.push(Agreement(_bob,_alice,msg.sender,_amount,false,false));
return agreements.length - 1;
}
function deposit(uint _id) external payable {
if (msg.sender == agreements[_id].bob && msg.value == agreements[_id].amount) {
agreements[_id].bobIn = true;
}
else if (msg.sender == agreements[_id].alice && msg.value == agreements[_id].amount) {
agreements[_id].aliceIn = true;
}
}
function refund(uint _id) external {
if (msg.sender == agreements[_id].bob && agreements[_id].bobIn == true) {
agreements[_id].bobIn = false;
payable(agreements[_id].bob).transfer(agreements[_id].amount);
}
if (msg.sender == agreements[_id].alice && agreements[_id].aliceIn == true) {
agreements[_id].aliceIn = false;
payable(agreements[_id].alice).transfer(agreements[_id].amount);
}
}
function complete(uint _id, address _winner) external {
require(msg.sender == agreements[_id].arbitrator, "Only arbitrator can complete");
require(agreements[_id].bobIn == true, "Bob has not paid");
require(agreements[_id].aliceIn == true, "Alice has not paid");
if (agreements[_id].bob == _winner) {
agreements[_id].bobIn = false;
agreements[_id].aliceIn = false;
payable(agreements[_id].bob).transfer(agreements[_id].amount * 2);
}
else if (agreements[_id].alice == _winner) {
agreements[_id].bobIn = false;
agreements[_id].aliceIn = false;
payable(agreements[_id].alice).transfer(agreements[_id].amount * 2);
}
}
}
Escrow Solidity Code Walkthrough
Creating the Agreement Struct
This structure holds the details of each agreement.
struct Agreement { address bob; address alice; address arbitrator; uint amount; bool bobIn; bool aliceIn; }
These are then stored in a dynamic array to store all agreements.
Creating a New Agreement
The arbitrator can create a new agreement by calling new
newAgreement(address _bob, address _alice, uint _amount);
This function pushes a new agreement to the array. Note that the amount is wei i.e. ETH at 18 decimals so 1e18 = 1 ETH.
Depositing Funds
Each party can then deposit to the contract using the id for the agreement.
deposit(uint _id) external payable { // Deposit logic }
This will set the boolean for bobIn and aliceIn to true.
Refunding
Both participants can withdraw their funds if the deal isn’t completed by calling refund(uint _id);
Completing the Transaction
The arbitrator can complete the transaction by calling the complete function with the winners address which will receive both deposits.
complete(uint _id, address _winner);
Deploying and Testing
- Compile the contract in Remix using the Solidity compiler CTRL + S
- Deploy the contract to a local or remote test network using Remix.
- Test the functions:
- Create a new agreement.
- Deposit funds as Bob and Alice.
- Either complete the transaction or refund.
This tutorial covers the basic steps to create, deploy, and interact with an Escrow contract in Solidity. You can add more features like time constraints, dispute resolution mechanisms, or multi-signature requirements for the arbitrator’s decisions. Code is for example purposes only, always test thoroughly before deploying to the mainnet.