Homework 4 – Escrow Smart Contract
Truffle • Ganache • Console

Deploy & Work with an Escrow Contract

Follow the steps exactly. Use Ganache as your local blockchain and Truffle for compile/deploy. Keep screenshots of the key actions and outputs.

Reference material
GitHub: Chapter 1 (Escrow)

Part 1: Setting Up the Development Environment

Step 1: Install Node.js

  1. Go to the Node.js website and download the LTS version for your OS.
  2. Install following the instructions for your platform.
  3. Verify installation:
    node -v
    npm -v

Step 2: Install Truffle

  1. Install globally:
    npm install -g truffle
  2. Verify:
    truffle version

Step 3: Install Ganache

  1. Download & install from the Ganache website.
  2. Start Ganache → Quickstart Ethereum to launch a local blockchain.

Step 4: Set Up a New Truffle Project

  1. Create a project directory and init:
    mkdir Escrow
    cd Escrow
    truffle init
  2. Configure Truffle to connect to Ganache. Edit truffle-config.js to include:
module.exports = {
  networks: {
    development: {
      host: "127.0.0.1", // Localhost
      port: 7545,        // Ganache port
      network_id: "*",   // Match any network id
    },
  },
  compilers: {
    solc: {
      version: "0.8.0", // Specify the Solidity compiler version
    },
  },
};

Part 2: Creating the Escrow Smart Contract

Step 1: Create the Smart Contract

  1. In contracts/, create Escrow.sol.
  2. Paste the following code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Escrow {
    address public buyer;
    address public seller;
    uint public deposit;
    uint public timeToExpiry;
    uint public startTime;

    // Buyer sets up the escrow contract and pays the deposit
    constructor(address _seller, uint _timeToExpiry) payable {
        require(msg.value > 0, "Deposit must be greater than zero");
        buyer = msg.sender;
        seller = _seller;
        deposit = msg.value;
        timeToExpiry = _timeToExpiry;
        startTime = block.timestamp;
    }

    // Buyer releases deposit to seller
    function releaseToSeller() public {
        require(msg.sender == buyer, "Only buyer can release funds");
        require(!isExpired(), "Escrow is expired");
        payable(seller).transfer(deposit);
        deposit = 0; // Set deposit to zero to prevent re-entry
    }

    // Buyer can withdraw deposit if escrow is expired
    function withdraw() public {
        require(isExpired(), "Escrow is not expired yet");
        require(msg.sender == buyer, "Only buyer can withdraw funds");
        uint amount = deposit;
        deposit = 0; // Set deposit to zero to prevent re-entry
        payable(buyer).transfer(amount);
    }

    // Seller can cancel escrow and return all funds to buyer
    function cancel() public {
        require(msg.sender == seller, "Only seller can cancel the escrow");
        uint amount = deposit;
        deposit = 0; // Set deposit to zero to prevent re-entry
        payable(buyer).transfer(amount);
    }

    // Check if the escrow is expired
    function isExpired() public view returns (bool) {
        return block.timestamp > startTime + timeToExpiry;
    }
}

Step 2: Compile

truffle compile

Write a Migration Script & Deploy

Create migrations/2_deploy_contracts.js

const Escrow = artifacts.require("Escrow");

module.exports = function (deployer, network, accounts) {
  const sellerAddress = accounts[1]; // Example seller address
  const timeToExpiry = 3600; // 1 hour in seconds
  deployer.deploy(Escrow, sellerAddress, timeToExpiry, {
    value: web3.utils.toWei("1", "ether"), // 1 ETH deposit
    from: accounts[0], // Example buyer address
  });
};

Deploy

truffle migrate --reset
Expected: Contract address appears in the migration output. Ganache shows the deployment transaction and 1 ETH deducted from buyer (accounts[0]).

Part 3: Interacting with the Escrow Contract

Step 1: Start the Truffle Console

truffle console

Step 2: Get the Deployed Instance & Inspect State

const Escrow = artifacts.require("Escrow");
const escrowInstance = await Escrow.deployed();

const buyer = await escrowInstance.buyer();
const seller = await escrowInstance.seller();
const deposit = await escrowInstance.deposit();
const timeToExpiry = await escrowInstance.timeToExpiry();
const startTime = await escrowInstance.startTime();

console.log("Buyer:", buyer);
console.log("Seller:", seller);
console.log("Deposit (in wei):", deposit.toString());
console.log("Time to Expiry (seconds):", timeToExpiry.toString());
console.log("Start Time (timestamp):", startTime.toString());

Step 3: Call Functions

Use the correct roles. Buyer = accounts[0], Seller = accounts[1] as per migration.

const accounts = await web3.eth.getAccounts();

// Release funds to seller (only before expiry)
await escrowInstance.releaseToSeller({ from: buyer });

// If expired, buyer can withdraw
await escrowInstance.withdraw({ from: buyer });

// Seller can cancel (returns funds to buyer)
await escrowInstance.cancel({ from: seller });

// Check expiry status
const expired = await escrowInstance.isExpired();
console.log("Is the escrow expired?", expired);
Tip: If you need to test expiry quickly, wait a few seconds or redeploy with a smaller timeToExpiry (e.g., 10). Advanced (optional): use Ganache's time travel (evm_increaseTime, evm_mine).

Monitoring in Ganache

As you interact, watch Ganache: transactions, gas, and balances change. You should see the deployment, and then function calls that move the 1 ETH deposit depending on the path taken.

Submission Checklist

Name your file: HW4_FirstnameLastname.pdf with screenshots in order.