Building on Morph
Tags: web3, ethereum, blockchain, typescript
Published: 2024-05-18 12:40:38

- [Introduction](#a)
	 - [The Morph Solution: Bridging Web3 gap by transitioning real-world applications Onchain](#b)
	 - [Morph's Architecture: Core Functionalities](#c)
- [Building Applications on Morph](#d)
	 - [Getting Started](#e)
	 - [Set up the environment](#f)
	 - [Building and Compiling the Smart contract](#g)
	 - [Deploying and Interacting with the Contract](#h)
- [What's Next?](#i)

# <a id = "a">Introduction </a>
In the fast-evolving blockchain industry, scaling solutions have emerged as crucial for enhancing the scalability and efficiency of underlying blockchain infrastructures. Blockchains like Ethereum, once revolutionary, now struggle with the demands of a growing user base. Transaction fees have skyrocketed, and transaction throughput low, hindering its ability to handle the growing demand. Enter Morph, a next-generation blockchain platform designed to address these limitations.

---

## <a id = "b">The Morph Solution: Bridging Web3 gap by transitioning real-world applications Onchain</a>
Morph is not just another scaling solution; it's a next-generation blockchain platform built from the ground up to address these critical limitations. Unlike traditional blockchains that store every transaction detail on-chain, Morph leverages a unique architecture that combines established and cutting-edge technologies. This unique blend empowers developers to build powerful and user-friendly applications without sacrificing scalability, security, or efficiency.

### <a id = "c">Morph's Architecture: Core Functionalities </a>
![enter image description here](https://docs.morphl2.io/assets/images/archi-d8b8b914a067e8d5f45b4a69e718da83.png)
Morph is designed with a modular architecture that consists of three functional modules that effectively collaborate with one another while preserving their individual autonomy:
- Decentralized Sequencer Network for Consensus and Execution, ensuring fair and transparent transaction processing through a decentralized network of nodes, reducing the risk of censorship and centralization.
- Rollup for Data Availability, ensuring that transaction data is stored and accessible off-chain while keeping the on-chain data footprint minimal. Rollups aggregate multiple transactions into a single batch, which is then recorded on the blockchain. This process significantly reduces the amount of data that needs to be stored on-chain, thereby improving scalability and reducing costs.
- Optimistic zkEVM for Settlement of transactions, validating them off-chain and only submitting proofs to the blockchain when necessary.

> Read more on [Morph Modular Design](https://docs.morphl2.io/docs/how-morph-works/morph-modular-design)

## <a id = "d">Building Applications on Morph</a>
Good news: It's just like building on Ethereum! There are very few changes needed. So if you're an Ethereum builder, you are already a Morph builder. 😎

In the next part, we'll be deploying a smart contract to the Morph Holesky Testnet. It's the same code from my [Confidential Smart Contracts](https://dev.to/tosynthegeek/confidential-smart-contracts-building-woasis-sapphire-2kkg#g) post.

### <a id = "e">Getting Started</a>
-   [Metamask Wallet Setup for Morph Testnet](https://docs.morphl2.io/docs/quick-start/wallet-setup): Crypto wallet.
-   [Hardhat](https://hardhat.org/): Ethereum development environment. It comes as an npm package.
- [Morph Holesky Testnet RPC URL](https://docs.morphl2.io/docs/quick-start/wallet-setup#manual-network-configuration)
- Get some Morph Testnet Ethereum [here](https://docs.morphl2.io/docs/quick-start/faucet#morph-holesky-faucet)

### <a id = "f">Set up environment</a>
Now that we've gathered our tools, it's time to set up our development environment. Here's a step-by-step guide:

-   Start by running the following commands:
```bash
mkdir auction

cd auction

npm init -y

npm install --save-dev hardhat (We will be using typescript)

npx hardhat init

npm install --save-dev @nomicfoundation/hardhat-toolbox

npm i dotenv

code .
```
-   To keep sensitive information like your Metamask private key, create a .env file in your project directory and store your keys there in the format below:
```bash
PRIVATE_KEY=""
```
- Modify your Hardhat configuration file (hardhat.config.ts) to recognize the keys from your .env file. Also, add morph testnet as a network.
```typescript
import { HardhatUserConfig } from  "hardhat/config";

import  "@nomicfoundation/hardhat-toolbox";

const  config: HardhatUserConfig = {
	solidity:  "0.8.24",
	networks: {
		morph: {
			url:  "https://rpc-quicknode-holesky.morphl2.io",
			accounts:
				process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
		},
	},
};

export  default  config;
```
## <a id = "g">Building and Compiling the Contract </a>
In the  `/contracts`  directory of your Hardhat project, create a new file named Auction.sol. I've added the smart contract below with some comments to explain what's going on.
```javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract Auction {
    // Auction details
    address payable public seller; // Address of the seller
    uint256 public startingPrice; // Starting price of the auction
    uint256 public highestBid; // Highest bid amount
    address public highestBidder; // Address of the highest bidder
    uint256 public auctionEndTime; // Timestamp when the auction ends
    bool public auctionEnded; // Flag to indicate if the auction has ended
    uint256 public bidCount;

    // Struct to store bid details
    struct Bid {
        address bidder;
        uint256 bid;
    }

    Bid[] public bids; // Array to store all bids

    // Events for auction lifecycle
    event BidCreated(address bidder, uint256 amount);
    event AuctionEnded(address winner, uint256 winningBid);

    // Constructor to initialize auction parameters
    constructor(
        address payable _seller, // Address of the seller
        uint256 _startingPrice, // Starting price of the auction
        uint256 _auctionEndTime // Duration of the auction
    ) {
        seller = _seller;
        startingPrice = _startingPrice;
        highestBid = startingPrice;
        auctionEndTime = block.timestamp + _auctionEndTime; // Set the end time of the auction
    }

    /**
     * @notice Allows a user to place a bid in the auction.
     * @dev The bid must be higher than the current highest bid, and the auction must not have ended.
     */
    function bid() external payable {
        require(block.timestamp < auctionEndTime, "Auction has ended");
        require(
            msg.value > highestBid,
            "Bid must be higher than the current highest bid"
        );
        if (highestBidder != address(0)) {
            payable(highestBidder).transfer(highestBid);
        }
        highestBid = msg.value;
        highestBidder = msg.sender;
        Bid memory newBid = Bid(msg.sender, msg.value);
        bids.push(newBid);
        bidCount++;
        emit BidCreated(msg.sender, msg.value);
    }

    /**
     * @notice Allows the highest bidder or seller to check the bid at a given index.
     * @dev The auction must have ended, and only the highest bidder or seller can check bids.
     * @param index The index of the bid to check.
     * @return The bidder's address and bid amount.
     */
    function checkBid(uint256 index) external view returns (address, uint256) {
        require(index <= bidCount, "Wrong Index");
        Bid memory getBid = bids[index];
        require(block.timestamp > auctionEndTime, "Auction is still ongoing");
        require(
            msg.sender == highestBidder || msg.sender == seller,
            "Only Highest Bidder and seller can check bids"
        );
        return (getBid.bidder, getBid.bid);
    }

    /**
     * @notice Returns the timestamp when the auction ends.
     * @return The timestamp when the auction ends.
     */
    function checkAuctionEndTime() external view returns (uint256) {
        return auctionEndTime;
    }

    /**
     * @notice Ends the auction and transfers the highest bid amount to the seller.
     * @dev The auction must have ended.
     */
    function endAuction() external {
        require(block.timestamp >= auctionEndTime, "Auction is still ongoing");
        auctionEnded = true; // Update auction status
        seller.transfer(highestBid); // Transfer the highest bid to the seller
        emit AuctionEnded(highestBidder, highestBid); // Emit AuctionEnded event
    }

    /**
     * @notice Returns the auction details.
     * @return The seller's address, starting price, highest bid amount, highest bidder's address,
     * auction end timestamp, auction end status, and total bid count.
     */
    function getAuctionDetails()
        external
        view
        returns (address, uint256, uint256, address, uint256, bool, uint256)
    {
        return (
            seller,
            startingPrice,
            highestBid,
            highestBidder,
            auctionEndTime,
            auctionEnded,
            bidCount
        );
    }
}
```
Compile the contract using the command:  `npx hardhat compile`  

```bash
npx hardhat compile 
Compiled 1 Solidity file successfully
```
### <a id = "h">Deploying and Interacting with the Contract </a>
Now that you have your contract compiled, the next step would be to write a script to deploy and interact with your contract. In the `/scripts` folder, you'll find a default script, `deploy.ts`, you can delete that. Go ahead and create a new file called `run-auction.ts` and add the following code into it:
```typescript
import  hre  from  "hardhat";

async  function  getStorageAt(address: string, slotNumber: string) {
	const  provider = hre.ethers.provider;
	const  result = await  provider.getStorage(address, slotNumber);

	return  result.toString();
}

/**
* @notice Decode the transaction input data using the ABI.
* @param  abi The ABI of the contract.
* @param  inputData The input data of the transaction.
* @return The decoded transaction data, or an empty object if decoding fails.
*/

function  decodeTransactionInput(abi: any[], inputData: string) {
	try {
		const  iauction = new  hre.ethers.Interface(abi);
		const  decodedData = iauction.parseTransaction({ data:  inputData});
	return  decodedData;
	} catch (error) {
		console.error("Error decoding transaction input:");
		return { args: [] };
	}
}

async  function  main(value: number) {
	const  index = 0;
	const  address = "0xDA01D79Ca36b493C7906F3C032D2365Fb3470aEC";
	const  Auction = await  hre.ethers.getContractFactory("Auction");
	const  auction = await  Auction.deploy(
	"0xd109e8c395741b4b3130e3d84041f8f62af765ef",
	100,
	60  // 10 minutes for the auction duration
	);
	console.log("Auction contract deployed on Morph to: ",
	await  auction.getAddress()
	);

	console.log("Bidding....");
	const  tx = await  auction.bid({
	value:  value.toString(),
	});
	await  tx.wait();
	console.log("Bid successful!");

// Trying to get the bid of an associated address
	try {
		console.log("Checking bid at Index: ", index);
		const  bid = await  auction.checkBid(index);
		console.log("Bid at Index 0 is:", bid);
	} catch (error) {
		console.error("Failed to check bid: Auction is still ongoing");
	}

	console.log("Waiting....");

	const  endTime = await  auction.checkAuctionEndTime();
	console.log("Auction endtime is: ", endTime);

	console.log("Still waiting....");

	try {
		await  new  Promise((resolve) =>  setTimeout(resolve, 100_000));
		console.log("Checking bid again");
		const  bid = await  auction.checkBid(index);
		console.log("Bid:", bid);
	} catch (error) {
		console.log("Failed to check bid: Auction is still ongoing");
	}

	const  decodedInput = decodeTransactionInput(
	auction.interface.format(),
	tx.data
	);

	console.log("Decoded data input: ", decodedInput?.args);  

	const  StateData = await  getStorageAt(await  auction.getAddress(), "0x0");
	console.log("State data at slot 0x0 is: ", StateData);
}

main(120).catch((error) => {
	console.error(error);
	process.exitCode = 1;
});
```
This script demonstrates how to interact with the auction contract by performing several actions:
-   **Deployment:**  Deploys the contract, initializing it with the starting price and auction duration.
-   **Bidding:**  Places a bid in the auction, confirming the transaction hash.
-   **Bid Access Control:**  Attempts to check a specific bid while the auction is ongoing, showcasing the access restriction that only allows the highest bidder or seller to view bid details during the auction. It then successfully checks the bid details after the auction ends.
-   **Transaction Input Decoding:**  Decodes the transaction input data used for checking the bid, extracting relevant information like the bid amount.
-   **State Retrieval Simulation:**  Simulates retrieving state data at a specific slot.

### Deploying to Morph Testnet
To deploy to Morph testnet, run the command:
```bash
npx hardhat run scripts/run-auction.ts --network morph
```
You should get an output that looks like this:
``` bash
Auction contract deployed on Morph to: 0x290946a5f508530023e08260B67957f408D6dB75
Bidding.... 
Bid successful! 
Checking bid at Index: 0 
Failed to check bid: Auction is still ongoing Waiting.... 
Auction endtime is: 1715895486n Still waiting.... 
Checking bid again 
Bid: Result(2) [ '0xDA01D79Ca36b493C7906F3C032D2365Fb3470aEC', 120n ] 
Decoded data input: Result(0) [] 
State data at slot 0x0 is: 0x000000000000000000000000d109e8c395741b4b3130e3d84041f8f62af765ef
```
This means your contract has been successfully deployed to the Morph Test Network!
Head over to the [Morph Testnet  Explorer](https://explorer-testnet.morphl2.io/) to scan your contract address.

## <a id = "i">What's Next?</a>
To continue building on the Morph network, I recommend the following resources:
- [Morph Developer Docs](https://docs.morphl2.io/docs/build-on-morph/intro): for technical specifications and functionalities.
- [Discord Channel](https://discord.com/invite/5SmG4yhzVZ): Join the Morph Community to get feedback and share thoughts.
- [Morph Blog](https://blog.morphl2.io/): Also check out the Morph Blog for News, Announcements and Guides like this.