Trustless Work
HomeHome

Trustless Work React Library#

A production-ready set of React blocks for integrating Trustless Work's escrow and dispute resolution flows.

Important

Would you like to customize the blocks?
You can do that by editing the blocks as you see fit.

What you get#

  • UI blocks (cards/tables/dialogs/forms) to list and manage escrows
  • Providers for API config, wallet context, dialogs and amounts
  • TanStack Query hooks for fetching and mutating escrows
  • Wallet-kit helpers and error handling utilities

List all available blocks#

With the CLI you can list all available blocks:

1npx trustless-work list
The init command will:
  • Show all available blocks.

Context API#

The context API is a global storage of escrows. It is used to store the escrows that are fetched from the API. It is also used to store the selected escrow.

Important

If you don't want to use our approach for retrieving the escrow data, you are completely free to change it. You can use Redux, Zustand, or any other solution instead. However, it is important that you ensure the desired escrow is passed to the endpoint.

Understanding how the context works in escrows endpoints.#

When implementing the endpoints, we need to pass the data of a specific escrow to each endpoint. But how do we do that? Our library provides a context called EscrowContext, which includes some very important utilities. Among them areselectedEscrowand setSelectedEscrow, which allow us to do the following:

Use of selectedEscrow#

Currently, selectedEscrow holds a specific escrow that we are pointing to. With this, all the endpoint hooks interact with that state in order to extract data from it, such as contractId, roles, etc. For example, in the change status select, the milestoneIndex values are loaded based on the currently selected escrow. Therefore, ifsetSelectedEscrow is undefined, they won't load.

/useChangeMilestoneStatus.ts
1const { selectedEscrow } = useEscrowContext();
2
3const handleSubmit = form.handleSubmit(async (payload) => {             
4  /**
5   * Create the final payload for the change milestone status mutation
6   *
7   * @param payload - The payload from the form
8   * @returns The final payload for the change milestone status mutation
9  */
10  const finalPayload: ChangeMilestoneStatusPayload = {
11    contractId: selectedEscrow?.contractId || '', // contractId from the selectedEscrow
12    milestoneIndex: payload.milestoneIndex,
13    newStatus: payload.status,
14    newEvidence: payload.evidence || undefined,
15    serviceProvider: walletAddress || '',
16  };
17
18  /**
19   * Call the change milestone status mutation
20   *
21   * @param payload - The final payload for the change milestone statusmutation
22   * @param type - The type of the escrow
23   * @param address - The address of the escrow
24  */
25  await changeMilestoneStatus.mutateAsync({
26    payload: finalPayload,
27    type: selectedEscrow?.type || 'multi-release',
28    address: walletAddress || '',
29  });
30}

Use of setSelectedEscrow#

The function setSelectedEscrow save the selected escrow in the context, so that all the endpoint hooks interact with that state in order to extract data from it, such as contractId, roles, etc. For example, in escrows cards by signer we save the selected escrow in the context, so that we can use it in details dialog.

/EscrowsCards.tsx
1const { setSelectedEscrow } = useEscrowContext();
2
3const onCardClick = (escrow: Escrow) => {
4  setSelectedEscrow(escrow);
5  dialogStates.second.setIsOpen(true);
6};

Use of updateEscrow#

Our updateEscrow function update the existing selectedEscrow in the context. It is useful to update a flag or others fields. For example, we use it to update the escrow status after a change milestone status mutation.

/useChangeMilestoneStatus.ts
1const { selectedEscrow, updateEscrow } = useEscrowContext();
2
3const handleSubmit = form.handleSubmit(async (payload) => { 
4  /**
5    * Call the change milestone status mutation
6    *
7    * @param payload - The final payload for the change milestone status mutation
8    * @param type - The type of the escrow
9    * @param address - The address of the escrow
10  */
11  await changeMilestoneStatus.mutateAsync({
12    payload: finalPayload,
13    type: selectedEscrow?.type || "multi-release", // type from the selectedEscrow
14    address: walletAddress || "",
15  });
16
17  toast.success("Milestone status updated successfully");
18
19  // Update the selected escrow in the context with the new status and evidence
20  updateEscrow({
21    ...selectedEscrow,
22    milestones: selectedEscrow?.milestones.map((milestone, index) => {
23      if (index === Number(payload.milestoneIndex)) {
24        return {
25          ...milestone,
26          status: payload.status,
27          evidence: payload.evidence || undefined,
28        };
29      }
30      return milestone;
31    }),
32  });
33}

Installation based on folder path#

If you need all the child blocks, you can install them by pointing to their parent directory, so you won't have to install them one by one.

1npx trustless-work escrows // or other parent's blocks directory

Understanding the Block Structure#

When you specify a parent folder like escrows, the CLI will install all blocks within that directory tree. Here's how the blocks are organized:

Trustless Work Blocks Structure

escrows← Install all escrow-related blocks
escrows-by-roleView escrows by user role
cardsCard layout components
tableTable layout components
detailsDetail dialog components
escrows-by-signerView escrows by signer
cardsCard layout components
tableTable layout components
detailsDetail dialog components
single-releaseSingle payment escrows
multi-releaseMilestone-based escrows
single-multi-releaseShared components

Install Parent Directory

1npx trustless-work add escrows

Installs ALL escrow blocks

Install Specific Subfolder

1npx trustless-work add escrows/single-release

Installs only single-release escrow blocks

💡 Pro Tip: Hierarchical Installation

The deeper you go in the folder structure, the more specific the blocks become. Start with parent directories for comprehensive functionality, then drill down to specific components as needed.