How to implement the proposal validation?

Here we will see how a buyer can validate a proposal with the createTransaction function.

Good to Know

In this example, we will use reactJS, Ether.js and formik to handle the form on the frontend. You will find a full code example with all imports at the end of tutorial.

① The proposal validation process

To validate a proposal the buyer have to send the validated amount to the escrow contract, in our Dapp, this step happen by clicking on the validate proposal button in the ValidateProposalModal component.

But let's see how the process is organized

1 - ServiceDetail component : you will have in this component the mapping of all proposal, it use the ProposalItem component.

<div className='grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4'>
  {validatedProposal ? (
    <ProposalItem proposal={validatedProposal} />
  ) : (
    proposals.map((proposal, i) => {
      return (
        <div key={i}>
          {(service.status === ServiceStatusEnum.Opened ||
            proposal.status === ProposalStatusEnum.Validated) && (
            <ProposalItem proposal={proposal} />
          )}
        </div>
      );
    })
  )}
</div>

2-ProposalItem component : this component will display all the proposal details and it use the ValidateProposalModal

<div className='flex flex-row gap-4 justify-between items-center border-t border-gray-100 pt-4'>
  <p className='text-gray-900 font-bold line-clamp-1 flex-1'>
    {renderTokenAmount(proposal.rateToken, proposal.rateAmount)}
  </p>
  {account && isBuyer && proposal.status === ProposalStatusEnum.Pending && (
    <ValidateProposalModal proposal={proposal} account={account} />
  )}
</div>

3-ValidateProposalModalcomponent : this component will display a modal with all the information of the proposal, including the fee's summary, total, account balance

  • Validate proposal button : on submit, it will call ValidateProposal function

const onSubmit = async () => {
    if (!signer || !provider) {
      return;
    }
    await validateProposal(
      signer,
      provider,
      proposal.service.id,
      proposal.seller.id,
      proposal.rateToken.address,
      proposal.cid,
      totalAmount,
    );
    setShow(false);
  };
  • Decline button : used to decline a proposal

  • Contact the seller button : used to contact and chat with the seller with the xmtp decentralized instant message service

Let's focus on ValidateProposalModal component

4-ValidateProposal component : this component is the core of the proposal validation process.

As you can see below it take a few parameter

export const validateProposal = async (
  signer: Signer,
  provider: Provider,
  serviceId: string,
  proposalId: string,
  rateToken: string,
  cid: string,
  value: ethers.BigNumber,
): Promise<void> => {
  const talentLayerEscrow = new Contract(
    config.contracts.talentLayerEscrow,
    TalentLayerEscrow.abi,
    signer,
  );
  1. signer: Signer: This parameter is of type Signer and is required. It represents the Ethereum account that will sign the transaction.

  2. provider: Provider: This parameter is of type Provider and is required. It represents the Ethereum network provider that will be used to submit the transaction.

  3. serviceId: string: This parameter is of type string and is required. It represents the ID of the service concerned by the proposal.

  4. proposalId: string: This parameter is of type string and is required. It represents the ID of the proposal that is being validated.

  5. rateToken: string: This parameter is of type string and is required. It represents the address of the ERC-20 token that is being used to pay for the service. If this is set to ethers.constants.AddressZero, then the payment is being made in Ether

  6. cid: string: This parameter is of type string and is required. It represents the IPFS CID of the evidence that is being used to validate the proposal.

  7. value: ethers.BigNumber: This parameter is of type ethers.BigNumber and is required. It represents the amount of tokens or that is being used to pay for the service.

Then you have the createTranscation call, the proposal will be validated as soon as the transcation is validated

==> If the used token is ETH then the createTransaction is directly call with the right parameter

if (rateToken === ethers.constants.AddressZero) {
  const tx1 = await talentLayerEscrow.createTransaction(
    parseInt(serviceId, 10),
    parseInt(proposalId, 10),
    metaEvidenceCid,
    cid,
    {
      value,
    },
);

==> If the token is another ERC-20 token then you have have to pass trought a few more validation step as

  • Check the ERC-20 token balance

const balance = await ERC20Token.balanceOf(signer.getAddress());
  if (balance.lt(value)) {
    throw new Error('Insufficient balance');
}
  • Check the token allowance

const allowance = await ERC20Token.allowance(
    signer.getAddress(),
    config.contracts.talentLayerEscrow,
  );

  if (allowance.lt(value)) {
    const tx1 = await ERC20Token.approve(config.contracts.talentLayerEscrow, value);
    const receipt1 = await toast.promise(provider.waitForTransaction(tx1.hash), {
      pending: {
        render() {
          return (
            <TransactionToast
              message='Your approval is in progress'
              transactionHash={tx1.hash}
            />
          );
        },
      },
      success: 'Transaction validated',
      error: 'An error occurred while updating your profile',
    });
    if (receipt1.status !== 1) {
      throw new Error('Approve Transaction failed');
    }
  }

Then we can create the new createTransaction

const tx2 = await talentLayerEscrow.createTransaction(
    parseInt(serviceId, 10),
    parseInt(proposalId, 10),
    metaEvidenceCid,
    cid,
);

See the Full Code Implemented on Our Demo DAPP

Last updated