The Full Guide on how to Develop and Deploy a Simple Escrow Marketplace Smart Contract

Zile Cao
BridXe
Published in
13 min readJul 30, 2022

Using Solidity for contract development and Truffle (or Hardhat) to deploy and verify on any EVM-compatible blockchain (including your own private subnets)

An Escrow smart contract is a unique marketplace solution that guarantees the secure exchange of assets between a buyer and a seller through a middleman (the escrow). This can be especially useful when facilitating an exchange between cryptocurrency and physical goods. The buyer deposits their currency into the smart contract, and once they confirm that they successfully received the physical product, the funds are released by the escrow into the seller’s wallet.

The Escrow contract that this article will outline consists of the following structure below:

Diagram flowchart of the Escrow process
Simple flowchart of the Escrow Process

How to Interact with our Example Contract

Requirements: must have 1) the MetaMask Wallet chrome extension installed and 2) load up your wallet with some test tokens, which you can request for free from https://faucet.dimensions.network/

You can interact with our example contract, which was deployed on the Ropsten Ethereum Testnet, either through the Ropsten Etherscan, this smart contract UI, or my Replit (more on the usefulness of Replit to come below when we talk about how to deploy your own contracts)

Note: Currently when writing to the function sellerCreateSellOrder, user needs to send 0.0045 ETH to pay listingFee for transaction to succeed

Note 2: There is a 10% Escrow royalty fee for every sale so that the Escrow can make profits. When withdrawing, the Escrow wallet can only withdraw profits made from the 10% royalty to assure that users’ funds are safe inside the contract

Note 3: There are still some bugs with the current escrow contract, and more features will be implemented in the near future

tinyurl.com/BridXeEscrowUI2

The Escrow Flow Explained

Here are the intended steps in sequential order that users of our Escrow marketplace would perform to successfully exchange phygital goods with crypto. In plans to create a complete marketplace dApp, buttons will be created on the frontend which will allow interaction with our smart contract to read and write marketplace data to the blockchain. You can do this using the React.js and/or the Next.js framework, but this will be outside the scope of this article.

Follow along by opening up our example contract on the Ropsten Etherscan.

Step 0: getSellOrder(s) and other “Read” functions

These functions do not require user to be connected to the contract with a wallet. They read and return data that is stored within the contract, such as its current balance or the list of all sell orders currently created. You can find these in the “Read Contract” section on etherscan.

Step 1: sellerCreateSellOrder

This is the first function in the flow, and it allows a seller to list a sell order on the marketplace. It takes in two arguments: itemDescription and listingPrice (the price that buyers must pay to buy the item). In addition, because of the way the contract is set up, sellers wanting to create a sell order must pay a 0.0045 eth transaction fee for profit and design purposes of the marketplace (you can later change this to zero if you want). Another note: the listingPrice is in Wei units, which is the equivalent amount in Eth times 1⁰¹⁸.

Anyone can write to this function except the designated Escrow wallet (the account that deployed the contract)

Note: I opted to raise the gas price as the test net was congested at the time of recording

Step 2: buyerRequestToBuy

This is the second step in the Escrow flow. Buyers interested in one of the sell orders can send a request to buy the item. To write to this function, a buyer must specify the item ID and the amount they are wishing to send/pay (note that the function will currently throw an error if the amount they pay is lower than the item’s listing). Buyers can technically bid higher but an auction system could be a future challenge that you can code in. On another note: this time buyers must send Ropsten Ethereum, but later on, when we deploy this contract to other blockchains, you can choose the tokens that buyers/sellers exchange (effectively creating your own ecosystem if you decide that users can only use your marketplace with your company’s tokens).

The wallet which created the sell order and the designated Escrow wallet cannot write to this function

Step 3: sellerApproveBuyRequest

This function allows the seller to select the buyer they would like to reward the right to buy. This is useful when there are multiple bids on a certain item because it removes the “first-come-first-serve” time constraint issue. It also allows the seller to sell to a higher bidder even if they bid after. Of course, this functionality can be removed if you do not desire this marketplace protocol.

Only the seller wallet can call this function, and can only invoke a wallet which has placed a buy request on a particular item. Later, it will also allow only this particular address to request a refund if the delivery process fails.

Step 4: sellerPerformDelivery

This function helps the system know that delivery of an item has been initiated and sets the state of an item to allow the approved buyer to confirm or deny the delivery (See next).

Step 5: buyerConfirmDelivery

This function allows the approved buyer of an item to confirm whether an item has been delivered.

If confirmed: the contract will set the state of the item as “Confirmed” and send the funds from the item to the seller (note: the contract has a 10% royalty fee, so that only 90% of the listing price of the item will be transferred to the seller).

If denied (meaning the physical item was never delivered either due to supply chain or mailing complications): the item state will be set to “Disputed,” which will then allow the Escrow to refund the cost of the item to the buyer.

Step 6 (potential): escrowRefundItem

Returns 100% of all funds (does not take any royalty) to the buyer and sets the item state to “Refunded.” Currently this smart contract does not allow for the item to be relisted for sale, unless a new sell order is made.

Only the Escrow wallet address can call this function.

Step 7 (optional): escrowWithdrawFund

Allows the Escrow wallet to withdraw the 10% royalty profits that exist in the wallet balance. You can check how much of the current balance is available to be withdrawn as royalty profit by reading the function “getEscrowRoyalty.”

Only the Escrow wallet address can call this function.

The Contract Structure Explained

If you’re curious and would like to learn how to read the example Escrow smart contract, here are the functions/variables below to help you better understand (and potentially improve it in the future):

Events

  • event Action: emits a new event called Action which stores and updates info on an item ID, its current “status,” and the wallet address of the user who called the function onto the blockchain (everyone will be able to view this on some ledger like Etherscan)

Enums

  • Status: a user-defined data type called Status which indicates the current step in the Escrow process that an item is on
  • available: indicate if an item is available to buy

Structs

  • struct ItemStruct: contains item ID, description, listed price, paid price, timestamp, the current owner of the item, designated buyer, its status, and whether it's been delivered or confirmed

Variables

  • escAcc: the wallet address of the account which deployed the contract and has Escrow permissions
  • escBal: the current balance of the smart contract
  • escRoyalty: the currently available royalty profit available for the Escrow wallet to withdraw
  • escFee: the percentage fee which buyers and sellers pay to the Escrow for each transaction
  • totalItems: the number of sell orders
  • totalConfirmed: the number of completed sell orders
  • totalDisputed: the number of failed/incompleted sell orders
  • listingFee: the fee the Seller needs to send to the contract to list their sell order

Mappings

  • items: maps an itemID to a particular ItemStruct Object
  • itemsOf: maps a wallet address to its owned items
  • requested: maps a wallet address to a particular itemID and boolean
  • owner: maps an itemID to a wallet address
  • isAvailable: maps an itemID to the enum “Available”

Constructor

  • initiates the smart contract’s designated Escrow wallet address, balance, royalty profits, royalty fee, and listing fee.

— — — — — — — — — — — — — — — — — — — —

*Quickstart to Editing and Deploying Escrow Contracts on your own Machine*

Requirements: must have 1) a text editor (see below for recommendations) with the proper extensions, and 2) Hardhat and/or Truffle installed (look up how to install via Terminal command)

Strongly Recommended: 1) Download VSCode for your system and 2) install the Solidity extension and GitHub Repositories extension

To try playing around with our example Escrow smart contract and deploying it to various blockchains on your local computer, you can pull the code from our Github here: https://github.com/bridxe/escrow-contract2 and clone it to VSCode by following the instructions below:

Then, open up a terminal and run npm install . If you’re doing this from your computer terminal and not the VSCode, be sure to run cd escrow-contract2 first.

Next, create a new file in the root directory named “.env” and paste the following (you can add more to this list later if you want to deploy to more networks):

ROPSTEN_RPC_URL=RINKEBY_RPC_URL=PRIVATE_KEY_1=

And fill in the relevant information next to the equal signs (see below for how to generate URLs)

You will need to generate your RPC endpoint URLs from a website https://like infura.io/. This is a URL to which requests for blockchain data can be sent to and allows clients like MetaMask to interact with blockchains.

You can find the private key to your MetaMask wallet by pressing the three dots on any of your accounts, clicking “Account Details,” and “Export Private Key.”

IMPORTANT: Do not share your private key with anyone or post anywhere because anyone with this key can gain complete access to your wallet. The reason we have this stored in a .env is to mask it from outside users. HOWEVER, if you decide you want to publish your code or push it to a public Github Repository, be sure to create a .gitignore file in your root directory and make sure it prevents your .env file from being committed. Luckily, there is a .gitignore file provided in our source code so you do not have to stress this time around.

— — —

Now we are finally ready to deploy our contract. Luckily, Truffle and Hardhat make this very easy with only one command. We’ve already set everything up in our source code.

You can run either truffle migrate --network YOUR_DESIRED_NETWORK or npx hardhat run scripts/deploy.js --network YOUR_DESIRED_NETWORK . For now, we will use truffle because it is slightly easier to verify our contract on websites like Etherscan. However, in my opinion, Hardhat is easier to use if you want to deploy to more obscure blockchain networks or private ones that you host on your local machine, so we will utilize this as well later on.

If everything works correctly, your terminal should show a deployed contract address. You can search up this address on Testnet Etherscan and see your contract deployed, hurray!

If you come across any errors during this process: you can open an issue report on our GitHub page, to which I will get back to you on your problem. Otherwise, if you cannot wait, you can search for the error code and try to identify the issue yourself. Keep in mind that code may also become deprecated as time pass from the initial writing of this article.

Verifying your Contracts

After your contracts are deployed, they aren’t automatically verified on public ledgers such as Etherscan. However, you can still verify them manually, which will decompile the contract’s bytecode and allow users to read and interact with it online. Therefore, this step is very important. This is because in order to interact with contracts on the blockchain, apps must have access to that contract’s ABI.

Verifying when deployed using Truffle

Head over to whichever ledger you deployed your contract to. In my case, Etherscan. To verify it on etherscan, head over to Contract and select Verify and publish. Choose the options multi-file solidity, compiler 0.8.11, and MIT License. Keep everything else the same and when it prompts you to upload files on the next page, upload all 3 files (BridXeEscrow.sol, Migrations.sol, and ReentrancyGuard.sol) from your computer. Reference the below gif if you get stuck;

Verifying when deployed using Hardhat

The issue with verifying with Hardhat is that we do not migrate the imported ReentrancyGuard.sol contract directly into the BridXeEscrow.sol file. When we try to verify on Etherscan, it will throw an error because it does not like our import statement import './ReentrancyGuard.sol'; and instead wants all of our migrated contracts in one file. We need to actually copy over the entire abstract Reentracy Guard contract and paste it into the same .sol file as our Escrow contract.

Therefore, to do this, you must create a new .sol file in your contracts folder on VSCode, paste in your Escrow contract, and instead of your import statement, just replace it with the entire abstract contract ReentrancyGuard {…} segment. Only copy from the word abstract to the end curly bracket, which means to leave out SPDX license identifier comment at the top and the pragma solidity version declaration (you don’t want two of these in the same file, only the one from the original Escrow contract is fine).

Head over to Contract on Etherscan and select Verify and publish, then single-file solidity file, and simpy copy and paste the entire code that you attached together in your new file over to Etherscan when it prompts you to paste the contract code. Etherscan will then try to verify the contract code by comparing the ByteCode from the deployed contract and the solidity code that you pasted over. It’s easy to mess up this process, and thus is why I prefer to deploy using Truffle to have an easier time verifying your contracts.

Deploying your Contracts to other Networks

To deploy your Escrow marketplace smart contract to other EVM blockchain networks, you simply have to repeat the above processes while adjusting your HardHat or Truffle configuration settings. I will show you how to do this in HardHat since it requires fewer things to copy over, but the process is the same in Truffle.

Head over to our hardhat.config.js file and locate the networks nest with several blockchain configurations separated by commas and formatted like this:

ropsten: {url: process.env.ROPSTEN_RPC_URL || “”,accounts:process.env.PRIVATE_KEY_1 !== undefined ? [process.env.PRIVATE_KEY_1] : [],},

All you have to do is make a new entry in the same format under zileNet3 (don’t forget the commas!). You will then need three things: a network rpc url endpoint, the network’s chain ID, and the private key to the wallet that will deploy the contract. We do not need to worry about private key, but we will need to update the other two. After you decide which network you want to add, you can find its unique chain ID on https://chainlist.org/. Then, for the RPC endpoint, you will need to generate a new url on a website like https://like infura.io/. Don’t forget to create a new entry in your .env file to store and protect this link from being exposed!

Then, you can save all your files by hitting cmd (or ctrl) + s and rerun your deploy script: npx hardhat run scripts/deploy.js --network NEW_NETWORK .

Extra: Creating and Deploying Contracts to your own Local Avalanche Subnet

For an added element of challenge, instead of deploying your contract to an already existing blockchain network, why not deploy to a subnet that you create on your own machine? You can do this easily with the Avalanche CLI. Here is a tl;dr of how to do it on your own machine, as adapted from their documentation: https://docs.avax.network/subnets/create-a-local-subnet

Avalanche CLI Quickstart

Run these in your terminal

curl -sSfL https://raw.githubusercontent.com/ava-labs/avalanche-cli/main/scripts/install.sh | sh -scd binexport PATH=$PWD:$PATH

After installing, customize and launch your own custom subnet:

avalanche subnet create <subnetName>avalanche subnet deploy <subnetName>

Shut down your local deployment with:

avalanche network stop

Restart your local deployment (from where you left off) with:

avalanche network start

You can then add this network and airdropped tokens to your MetaMask wallet by importing the “Funded address” account private key and adding a new network to your wallet by inputting RPC URL, ChainID, currency symbol info etc. The end result should look something like this:

After deploying your local subnet, your terminal will generate unqiue RC endpoint URLS for you, in which you can add to your .env file. Reference the zileNet3 template in hardhat.config.js

Finally, to deploy your Escrow smart contract to this new subnet, you can add a new entry in your configuration files, update your .env file, and then run your deploy scripts in VSCode. You can reference the zileNet3 template in hardhat.config.js .

However, an even easier way to deploy and interact with your new local Avalanche subnet is through Replit, because its Solidity Starter streamlines contract deployment to whatever network your MetaMask wallet is connected to. Thus, if your wallet is currently on the zileNet3 network, then Replit will help you deploy your contract to zileNet3. If my Replit is down, you can always create a new Solidity Starter project, paste over your smart contract code, and run the dApp.

As you can see from the screenshot above, these interactions do leave a transaction hash, but there aren’t any public ledgers like Etherscan to look up your transactions. You might be able to figure something out by following this guide.

Creating and Deploying Contracts to Avalanche Testnets/Mainnet

The process for creating your own testnets for the public to use on Avalanche C-Chain is a much more complicated process, including setting up and running your own Avalanche Node and following this guide: https://docs.avax.network/subnets/create-a-fuji-subnet. If you successfully set it up, you will be able to track your transactions on https://testnet.snowtrace.io. However, this will be outside the scope of this article.

Hey there! My name is Zile. I currently study economics and computer science at the University of Pennsylvania and am currently learning Web3 development. You’re welcome to connect with me on Linkedin and follow me on Twitter. Thanks for reading!

--

--