Need help with next few steps in web app

Hi, i'm not too sure if this is the correct place to post this but I currently am developing a web app with blockchain integration and am very new to all of this. I currently have the smart contract deployed on the testnet but am very lost on the next step or even if my current smart contract will work.

The logic behind the contracts is that users will be able to earn credit (tokens) for doing work posted by other users, which they will transfer from one another. This work is then recorded on the blockchain. I've split both these functionality into 2 smart contracts and from all the research I've done, I believe I need a 3rd one for the trusted forwarder for the relayer - which I've no clue how to begin.

If anyone could guide me with explaining how to improve or fix my smart contracts or even provide the next steps, I would really appreicate it. Currently I think my biggest problems are:

  1. Do I need a 3rd contract for the trusted forwarder?
  2. How do I interact with the smart contract deployed on Defender's testnet?
  3. Can users sign these transactions without using MetaMask?

:1234: Smart Contract Code

TraceCredit.sol is the smart contract for the ERC20 token

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TraceCredit is ERC20 {

  constructor(uint256 initialSupply) ERC20("Trace Credit", "TC") {
    _mint(msg.sender, initialSupply);
  }
}

NodeManager.sol is the smart contract for recording the work, sending credits from one user to another and reading the work that has already been done.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

import "./TraceCredit.sol";
import "hardhat/console.sol";
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";

contract NodeManager is ERC2771Context {
  struct Node {
    address sender;
    address receiver;
    uint256 amount;
    uint256 postId;
  }

  TraceCredit public traceCredit;

  mapping(uint256 => Node) public nodes;
  uint256 public nodesCount;

  constructor(address creditTokenAddress, address trustedForwarder) ERC2771Context(trustedForwarder) {
    traceCredit = TraceCredit(creditTokenAddress);
  }

  function createNode(address receiver, uint256 amount, uint256 postId) external {
    // Use the _msgSender() from ERC2771Context to get the actual sender of the meta transaction
    traceCredit.transferFrom(_msgSender(), receiver, amount);

    // Add to the chain
    Node storage newNode = nodes[nodesCount];
    newNode.sender = _msgSender();
    newNode.receiver = receiver;
    newNode.amount = amount;
    newNode.postId = postId;

    nodesCount++;
  }

  function getNodeDetails(uint256 nodeId) external view returns (address, address, uint256, uint256) {
    require(nodeId < nodesCount, "Invalid node ID");
    Node storage node = nodes[nodeId];
    return (node.sender, node.receiver, node.amount, node.postId);
  }
}

:computer: Environment

I'm currently using Solidity, HardHat and Defender sdk.

Again, any help would be appreciated. Currently very overwhelmed.