Building a Real Estate RWA Platform with Engine Cloud

Building a Real Estate RWA Platform with Engine Cloud

Building a Real Estate RWA Platform with Engine Cloud

Scaling your blockchain application with server-side transaction processing

Real-world asset (RWA) tokenization is one of the most promising applications of blockchain technology, offering the potential to transform traditional markets by bringing assets like real estate onto the blockchain. However, building user-friendly RWA platforms presents significant challenges, particularly when it comes to transaction management and user experience.

In this guide, we'll explore how to build a complete real estate tokenization platform using thirdweb's Engine Cloud - a powerful server-side transaction processing solution that enables backend wallet functionality and seamless scalability.

What is Engine Cloud?

Engine Cloud is thirdweb's solution for executing backend on-chain transactions with minimal setup. It provides:

  • Server Wallets: 100% non-custodial backend wallets based on a high-performance, end-to-end encrypted key management system
  • Concurrent Transactions: Ability to handle hundreds of thousands of requests simultaneously
  • Simple HTTP API: Easy integration with any application through a straightforward API

The key benefit of Engine Cloud is that it allows you to build applications where users don't need to sign blockchain transactions directly. Instead, your backend can process and execute transactions on behalf of users, creating a much smoother experience.

Our Example Project: Real Estate RWA Platform

We'll build a real estate tokenization service with the following features:

  1. Property Creation: Real estate agents can tokenize properties as NFTs
  2. Document Verification: Legal documents are stored on IPFS and linked to tokens
  3. Property Verification: Authorized verifiers can confirm property authenticity
  4. User Interface: A complete frontend for interacting with tokenized properties

The platform will use two server wallets:

  • Admin Wallet: For creating and managing properties
  • Verifier Wallet: For verifying property authenticity

Step 1: Setting Up Engine Cloud

First, create a new Engine Cloud instance in your thirdweb dashboard:

  1. Create a new project in your thirdweb dashboard
  2. Navigate to the Engine tab and select Engine Cloud
  3. Create a vault admin account (securely store the admin key)
  4. Create two server wallets: one for the admin and one for the verifier
// Example .env file
VITE_THIRDWEB_CLIENT_ID=your-client-id
VITE_THIRDWEB_SECRET_KEY=your-secret-key
VITE_SERVER_WALLET_ADDRESS=your-admin-wallet-address
VITE_VERIFIER_WALLET_ADDRESS=your-verifier-wallet-address
VITE_VAULT_ACCESS_TOKEN=your-vault-access-token
VITE_RWA_DEPLOYED_CONTRACT_ADDRESS=your-contract-address

Step 2: Creating the Smart Contract

Our smart contract is an ERC-721 NFT collection with additional functionality for property management. It includes:

  • Property details (address, price, square meters, legal identifier)
  • Document hash (for legal documents)
  • Verification system (approved verifiers)
  • Property state management (initial offering, for sale, pending, sold)

Here's the core structure of our RealEstateRWA.sol contract:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract RealEstateRWA is ERC721Enumerable, Ownable {
    // Property state enum
    enum PropertyState {
        INITIAL_OFFERING,
        FOR_SALE,
        PENDING_SALE,
        SOLD,
        NOT_FOR_SALE
    }

    // Property structure
    struct Property {
        string propertyAddress;
        uint256 price;
        uint256 squareMeters;
        string legalIdentifier;
        string documentHash;
        PropertyState state;
        address verifier;
        string tokenURI;
    }

    // Storage mappings
    mapping(uint256 => Property) public properties;
    mapping(address => bool) public approvedVerifiers;
    
    // Core functions
    function createProperty(
        string memory propertyAddress,
        uint256 price,
        uint256 squareMeters,
        string memory legalIdentifier,
        string memory documentHash,
        string memory imageURI
    ) external onlyOwner returns (uint256) {
        // Implementation
    }
    
    function verifyProperty(uint256 tokenId) external {
        require(approvedVerifiers[msg.sender], "Not an approved verifier");
        // Implementation
    }
    
    // Additional functions...
}

Step 3: Deploying the Contract

Deploy the contract using thirdweb's tools:

npx thirdweb deploy

Select the RealEstateRWA contract and deploy it to your chosen network (like Arbitrum Sepolia for testing). Once deployed, transfer ownership to your Engine Cloud admin wallet address.

Check the final versiion of the smart contracts on this repo.

Step 4: Building the Backend Integration

We need to create utility functions to interact with our contract through Engine Cloud. Here are the key functions we'll implement:

Client and Contract Setup

// client.ts
import { createThirdwebClient } from "thirdweb";

const clientId = import.meta.env.VITE_THIRDWEB_CLIENT_ID;
const secretKey = import.meta.env.VITE_THIRDWEB_SECRET_KEY;

export const client = createThirdwebClient({
  clientId,
  secretKey,
});

// RWAcontract.tsx
import { getContract } from "thirdweb";
import { client } from "../client";
import { arbitrumSepolia } from "thirdweb/chains";

export const rwaContract = getContract({
  client,
  chain: arbitrumSepolia,
  address: import.meta.env.VITE_RWA_DEPLOYED_CONTRACT_ADDRESS,
});

// ServerWallet.tsx
import { client } from "../client";
import { Engine } from "thirdweb";

export const serverWallet = Engine.serverWallet({
  client,
  address: import.meta.env.VITE_SERVER_WALLET_ADDRESS,
  vaultAccessToken: import.meta.env.VITE_VAULT_ACCESS_TOKEN,
});

Creating Properties

// MintNewAsset.tsx
import { prepareContractCall, Engine } from "thirdweb";
import { serverWallet } from "./ServerWallet";
import { rwaContract } from "./RWAcontract";
import { client } from "../client";

export const createNewProperty = async (
  propertyAddress: string,
  price: bigint,
  squareMeters: number,
  legalIdentifier: string,
  documentHash: string,
  imageURI: string
) => {
  console.log("Creating new property...");
  
  // Prepare the transaction
  const transaction = prepareContractCall({
    contract: rwaContract,
    method: "function createProperty(string, uint256, uint256, string, string, string)",
    params: [
      propertyAddress,
      price,
      BigInt(squareMeters),
      legalIdentifier,
      documentHash,
      imageURI,
    ],
  });

  // Queue the transaction to be executed by the server wallet
  const { transactionId } = await serverWallet.enqueueTransaction({
    transaction,
  });

  // Wait for the transaction to be processed
  const txHash = await Engine.waitForTransactionHash({
    client,
    transactionId,
  });

  return txHash;
};

Verifying Properties

// VerifyProperty.tsx
import { prepareContractCall, Engine } from "thirdweb";
import { verifierWallet } from "./VerifierWallet";
import { rwaContract } from "./RWAcontract";
import { client } from "../client";

export const verifyProperty = async (tokenId: number) => {
  // Prepare the transaction
  const transaction = prepareContractCall({
    contract: rwaContract,
    method: "function verifyProperty(uint256)",
    params: [BigInt(tokenId)],
  });

  // Queue the transaction to be executed by the verifier wallet
  const { transactionId } = await verifierWallet.enqueueTransaction({
    transaction,
  });

  // Wait for transaction completion
  const txHash = await Engine.waitForTransactionHash({
    client,
    transactionId,
  });

  return {
    success: true,
    transactionHash: txHash,
    transactionId,
  };
};

Step 5: Creating the User Interface

Our application will have several key components:

Property Creation Form

function CreatePropertyForm() {
  const [formData, setFormData] = useState({
    propertyAddress: "",
    price: "",
    squareMeters: "",
    legalIdentifier: "",
    propertyName: "",
    propertyDescription: "",
  });
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [selectedImage, setSelectedImage] = useState(null);
  const [selectedDocument, setSelectedDocument] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsSubmitting(true);
    
    try {
      // 1. Upload image to IPFS
      const imageUri = await uploadToIPFS(selectedImage);
      
      // 2. Upload document to IPFS
      const documentUri = await uploadToIPFS(selectedDocument);
      
      // 3. Create metadata and upload to IPFS
      const tokenUri = await createAndUploadMetadata(
        formData.propertyName,
        formData.propertyDescription,
        imageUri
      );
      
      // 4. Mint the new property
      const txHash = await createNewProperty(
        formData.propertyAddress,
        BigInt(formData.price),
        parseInt(formData.squareMeters),
        formData.legalIdentifier,
        documentUri,
        tokenUri
      );
      
      // Handle success
    } catch (error) {
      // Handle error
    } finally {
      setIsSubmitting(false);
    }
  };
  
  // Form JSX...
}
function PropertyGrid() {
  const [properties, setProperties] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  
  // Fetch all properties on component mount
  useEffect(() => {
    const fetchProperties = async () => {
      try {
        const propertiesData = await getAllProperties();
        setProperties(propertiesData);
      } catch (error) {
        console.error("Error fetching properties:", error);
      } finally {
        setIsLoading(false);
      }
    };
    
    fetchProperties();
  }, []);
  
  // Gallery JSX...
}

Verifier Management

function AddVerifierForm() {
  const [verifierAddress, setVerifierAddress] = useState("");
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    try {
      const hash = await addVerifier(verifierAddress);
      // Handle success
    } catch (error) {
      // Handle error
    }
  };
  
  // Form JSX...
}

The Complete Flow

With all pieces in place, our RWA platform works as follows:

  1. Property Creation:

    • Real estate agency uploads property details and documents
    • Documents stored on IPFS, metadata created
    • Admin wallet sends transaction to create NFT without requiring user signature
  2. Property Verification:

    • Verifier reviews property details and documents
    • Verifier wallet signs verification transaction
    • Property gains verified status on-chain
  3. Property Viewing:

    • Users can browse all properties
    • Verified properties are clearly marked
    • Property details and documents are accessible

Centralization Considerations

It's important to note that this approach has centralization trade-offs:

  1. The real estate agency controls the platform and verification process
  2. Users must trust the agency to properly verify property authenticity
  3. The agency maintains compliance with local regulations

For a more decentralized approach, you could integrate oracle services like Chainlink to provide external verification of property data.

Conclusion

Engine Cloud enables building user-friendly RWA platforms by handling blockchain transactions on the backend. This approach significantly improves user experience, as end users don't need to understand blockchain technology or manage wallets to interact with the platform.

The real-world application of this technology is particularly powerful for industries like real estate, where complex legal requirements and user experience expectations have been barriers to blockchain adoption.

By leveraging Engine Cloud for backend transaction processing and IPFS for decentralized storage, we've created a complete real estate tokenization platform that balances the benefits of blockchain technology with the user experience expectations of traditional applications.


If you want to explore the full code for this project, check out our GitHub repository, which includes both the smart contract and the complete frontend application.