paint-brush
How to Set Up a Trustless Escrow Smart Contract on Rootstock for Secure Transactionsby@braham
229 reads

How to Set Up a Trustless Escrow Smart Contract on Rootstock for Secure Transactions

by AbrahamDecember 1st, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Build a trustless escrow system on Rootstock using Solidity for secure peer-to-peer transactions. This guide covers contract setup, deployment, and testing.
featured image - How to Set Up a Trustless Escrow Smart Contract on Rootstock for Secure Transactions
Abraham HackerNoon profile picture

While trust and security are at the core of blockchain, this smart contract is designed to provide a trustless escrow system for peer-to-peer trades on Rootstock, using Solidity. It ensures that funds or assets are securely held until both the buyer and seller confirm that the terms have been met. If there’s a dispute, a neutral arbiter can step in to resolve the issue and release the funds. This system removes the need for intermediaries, offering a transparent and secure transaction process.

Why Roostock?

Rootstock (RSK) is a great fit for this application because it combines the security of Bitcoin (through merged mining) with the flexibility of the Ethereum Virtual Machine (EVM), making it accessible for all Ethereum users. With low transaction costs, faster block times, and the ability to leverage Bitcoin liquidity through rBTC, RSK offers a more cost-effective and secure solution for building decentralized applications, especially for Bitcoin-native users.


Prerequisites for Building the Escrow Service

To successfully build and deploy the escrow smart contract on Rootstock (RSK), you'll need the following:


  1. Solidity for smart contract development
  2. JavaScript/TypeScript for frontend or integration scripting.
  3. Development Environment: A local setup with tools like Truffle, Hardhat, or Remix IDE. Including installed dependencies such as Node.js and npm/yarn.
  4. Testing Framework: Mocha/Chai or similar libraries for writing and running test cases. And access to a Rootstock testnet.
  5. Wallet Integration: Use Math wallet or any other that supports Rootstock for configuration to interact with the RSK network.


Rootstock Wallet Setup Requirements

  1. rBTC for Gas: Testnet rBTC for deploying and testing the smart contract.
  2. Rootstock Configuration: Add Rootstock testnet or mainnet RPC to your wallet (Math wallet) and development tools:
    • Mainnet RPC: https://public-node.rsk.co
    • Testnet RPC: https://public-node.testnet.rsk.co
    • Chain ID for Mainnet: 30 and Testnet: 31.
  3. Contract Deployment Tooling: Setup RSK-specific plugins/modules if you're using Truffle for deployment.


Audit & Security Guide

You can leverage automated tools like MythX, Slither, or Remix Analyzer.



Key Components of the Escrow System Smart Contract

  1. Variables:

    Addresses: buyer, seller, and arbiter define the parties involved.

    Amount: Tracks the escrowed funds.

    States: An enum (EscrowState) defines the current stage of the transaction (e.g., AWAITING_PAYMENT, COMPLETE).

  2. Functions:

  • Core Operations:
    • deposit(): Buyer deposits funds into the escrow.
    • approveDelivery() & confirmDelivery(): Buyer and seller approve the release of funds.
    • dispute(): Buyer or seller flags a dispute.
    • resolveDispute(): Arbiter resolves disputes and allocates funds.
  • Fail-Safes:
    • refundBuyer(): Refunds the buyer in case of non-delivery.


3. Events:

  • Not explicitly included but highly recommended for transaction logging:
    • FundsDeposited(address buyer, uint256 amount): Triggered on deposit.
    • FundsReleased(address recipient, uint256 amount): Triggered when funds are disbursed.
    • DisputeRaised(): Triggered when a dispute is flagged.
  • Modifiers:
    • Role-based access control (onlyBuyer, onlySeller, onlyArbiter) to ensure only authorized parties can perform specific actions.
    • State-based restrictions (inState) to prevent invalid operations.
  1. Internal Logic:
  • State Transitions: Smoothly transition through predefined stages (AWAITING_PAYMENTAWAITING_DELIVERYCOMPLETE or DISPUTED).
  • Conditional Finalization: Release funds only when both buyer and seller approve.


The Escrow System Smart Contract

This code is written with Solidity Programming language

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Escrow {
    address public buyer;
    address public seller;
    address public arbiter; // A third party who resolves disputes
    uint256 public amount;
    bool public buyerApproval;
    bool public sellerApproval;

    enum EscrowState { AWAITING_PAYMENT, AWAITING_DELIVERY, COMPLETE, DISPUTED }
    EscrowState public state;

    modifier onlyBuyer() {
        require(msg.sender == buyer, "Only the buyer can call this function.");
        _;
    }

    modifier onlySeller() {
        require(msg.sender == seller, "Only the seller can call this function.");
        _;
    }

    modifier onlyArbiter() {
        require(msg.sender == arbiter, "Only the arbiter can call this function.");
        _;
    }

    modifier inState(EscrowState _state) {
        require(state == _state, "Invalid state for this action.");
        _;
    }

    constructor(address _buyer, address _seller, address _arbiter) {
        buyer = _buyer;
        seller = _seller;
        arbiter = _arbiter;
        state = EscrowState.AWAITING_PAYMENT;
    }

    // Buyer deposits funds into escrow
    function deposit() external payable onlyBuyer inState(EscrowState.AWAITING_PAYMENT) {
        require(msg.value > 0, "Deposit amount must be greater than 0.");
        amount = msg.value;
        state = EscrowState.AWAITING_DELIVERY;
    }

    // Buyer approves the release of funds
    function approveDelivery() external onlyBuyer inState(EscrowState.AWAITING_DELIVERY) {
        buyerApproval = true;
        finalize();
    }

    // Seller confirms the transaction is complete
    function confirmDelivery() external onlySeller inState(EscrowState.AWAITING_DELIVERY) {
        sellerApproval = true;
        finalize();
    }

    // Arbiter resolves disputes
    function resolveDispute(bool releaseToSeller) external onlyArbiter inState(EscrowState.DISPUTED) {
        if (releaseToSeller) {
            payable(seller).transfer(amount);
        } else {
            payable(buyer).transfer(amount);
        }
        state = EscrowState.COMPLETE;
    }

    // Mark contract as disputed (only buyer or seller can do this)
    function dispute() external {
        require(msg.sender == buyer || msg.sender == seller, "Only buyer or seller can dispute.");
        state = EscrowState.DISPUTED;
    }

    // Finalize the transaction if both parties approve
    function finalize() internal {
        if (buyerApproval && sellerApproval) {
            payable(seller).transfer(amount);
            state = EscrowState.COMPLETE;
        }
    }

    // Refund the buyer if the seller fails to deliver
    function refundBuyer() external onlyBuyer inState(EscrowState.AWAITING_DELIVERY) {
        payable(buyer).transfer(amount);
        state = EscrowState.COMPLETE;
    }
}


Here is the breakdown of how this Smart Contract will work:

  1. Initialization:
    • The contract is deployed with the buyer, seller, and arbiter addresses specified.
    • The initial state is AWAITING_PAYMENT.
  2. Deposit:
    • The buyer deposits funds into the contract. The state changes to AWAITING_DELIVERY.
  3. Approval and Delivery:
    • The buyer approves delivery, and the seller confirms the receipt of funds.
    • If both parties approve, the funds are transferred to the seller, and the contract's state becomes COMPLETE.
  4. Disputes:
    • Either party can mark the contract as disputed. The arbiter resolves the dispute by transferring funds to the appropriate party.
  5. Refunds:
    • If the seller fails to deliver, the buyer can request a refund.


Deploying & Testing the Smart Contract

To deploy and test the smart contract on Rootstock, I will be using Truffle software to create a development environment, then configure the Rootstock testnet (or mainnet) for the Escrow System.

We will use rBTC faucet on Rootstock testnet to test the smart contract with Truffle testing environments, using the following deploy command:

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

module.exports = function (deployer) {
    deployer.deploy(Escrow, "buyer_address", "seller_address", "arbiter_address");
};


Let me know if you have any questions in the comments!