# Introduction

## The TalentLayer TLDR

TalentLayer **is a protocol for building decentralized service marketplace applications** like Uber, Postmates, Rappi, and others.

TalentLayer can help you build new marketplaces - with the protocol replacing key backend components, helping you go to market faster.

**Why leverage a protocol for your marketplace app?** Tap into a unified network effect; many interfaces have access to one pool of users, offers, and products. This increases efficiency of supply and demand matching, and helps marketplaces overcome the chicken and egg problem.

**TalentLayer's Web 3 API and SDK** is live and available for integration. The Web 3 API and SDK assumes a web 3 native user experience on the platform level. Account delegation and gassless are enabled today so that platform developers can implement account abstraction optionally.

{% content-ref url="/pages/ufRvPTyBZsJQbq37mykA" %}
[Web 3 SDK & API](/technical-guides/web-3-sdk-and-api)
{% endcontent-ref %}

## Key Concepts in TalentLayer

Learn about how TalentLayer helps your platform and explore options for how to integrate today.

{% content-ref url="/pages/wxhWZJC7A0XxpLL2rNXO" %}
[Value Proposition](/readme/value-proposition)
{% endcontent-ref %}

{% content-ref url="/pages/5aO1XVvlDdJusu0WP9rG" %}
[Options for Integration](/readme/options-for-integration)
{% endcontent-ref %}

## Get Started Today

**TalentLayer's Web 3 API and SDK** is live and available for integration.

### Explore the SDK

Add TalentLayer to an existing marketplace or build one from scratch with the TalentLayer Web 3 SDK and API.

{% content-ref url="/pages/ufRvPTyBZsJQbq37mykA" %}
[Web 3 SDK & API](/technical-guides/web-3-sdk-and-api)
{% endcontent-ref %}

### Use a Starter Codebase

Are you building a marketplace app or adding work features to an app in a different vertical? Reduce your workload by up to 90% by using StarterKit - an open-source codebase for a fully-functioning web 3 native freelance marketplace.

{% content-ref url="/pages/zMheMAOJCyvDLNQPaYXT" %}
[StarterKit Template](/technical-guides/starterkit-setup)
{% endcontent-ref %}


# Value Proposition

TalentLayer is the API connecting the world's marketplaces.&#x20;

TalentLayer **helps marketplace applications like freelance marketplaces and ride-share apps access additional supply or demand when they need it** by tapping into a wider network of users.&#x20;

The supply and demand available at the network level is made up of the demand (requests for service) and supply (people available to work) pushed to the network by integrated platforms.&#x20;

If a user on your platform matches offers with a user supplied by the network, your platform splits the fee earnings on that transaction with whatever platform supplied the other side of the deal. This is a "cross-platform transaction".

Payment remittance, messaging, reviews, dispute resolution, and more are facilitated by TalentLayer for any cross-platform transactions.

<figure><img src="/files/FQt73otRK5GfgX0l1Ube" alt=""><figcaption></figcaption></figure>


# Options for Integration

There are two options for how to integrate TalentLayer: native integration and on-demand integration.&#x20;

{% content-ref url="/pages/LhCZ4YgLyiW4Pefw3x8S" %}
[Native Integration](/readme/options-for-integration/native-integration)
{% endcontent-ref %}

{% content-ref url="/pages/1lG5eOTBksl5adVu14mg" %}
[On-Demand Integration](/readme/options-for-integration/on-demand-integration)
{% endcontent-ref %}


# On-Demand Integration

## TalentLayer for Supplimenting Supply & Demand

Platforms that choose On-Demand Integration opt to use TalentLayer to supplement existing supply and demand on their marketplace.

On-Demand Integration is good for teams who may:

* want to list only excess supply and demand on the network for cross-platform transactions
* already have an existing user account system, payments system, or dispute resolution
* want to maintain custody of their own user's profiles and their current network effect

When you lack supply or demand for a specific category of service, you can pull in resources from the network on demand.&#x20;

> **On-Demand Fulfillment Workflow**\
> \
> Marketplace A has insufficient supply of Python developers to meet the demand of Python gigs available on their platform. \
> \
> Marketplace A pushes current available Python gigs to the network via TalentLayer API. \
> \
> Marketplace A receives proposals from users across the network, which it then displays to the hirers on it's platform. \
> \
> Hirers select the winning candidate and complete the transaction. Payment remittance is triggered on Marketplace A by the hirer and facilitated by TalentLayer. \
> \
> The candidate receives payment on the platform they are using.

All of your users and service requests will remain custodied by your platform in whatever manner they are today. There will be no network-native representation of your users available for other platforms to view or display.&#x20;

{% hint style="warning" %}
**On-Demand Integration** is a feature of the TalentLayer Abstracted API and SDK. While it is possible to build a platform with On-Demand Integration today, this implementation pattern is not documented and has no example code available.&#x20;
{% endhint %}


# Native Integration

## TalentLayer as a Backend

Platforms that choose Native Integration opt to use TalentLayer as a backend for their platform, where all supply and demand is listed at the network level for cross-platform deals.

Native Integration is good for teams who may:

* want to have 100% of their current supply and demand listed on the network for cross-platform transactions
* don't have an existing user account system, payments system, or dispute resolution
* are adding marketplace features into an app in a different vertical
* don't want to custody user data for GDPR reasons
* want to avoid building a backend from scratch, to go to market faster

All of your users and service requests will be listed on the network as available supply and demand; any other platform on the network can propose cross-platform transactions with your users.&#x20;

With Native Integrations, each user on your platform has an reputation and profile at the network level that can be viewed by and used on other platforms.&#x20;

{% hint style="info" %}
**Native Integration** is available today, with extensive documentation, code examples, and more.
{% endhint %}


# TalentLayer's Functions

The TalentLayer Core Protocol is the underlying tech behind the TalentLayer Web 3 SDK and API.

The TalentLayer Core Protocol is composed of the following key modules:

* **TalentLayerPlatformID:** Marketplace configuration (fees, dispute, access)
* **TalentLayerID:** Worker and hirer identity
* **TalentLayerReview:** Mutual review&#x20;
* **TalentLayerService:** Universal Services & Proposal System
* **TalentLayerEscrow:** Escrow & Dispute System

Together, these systems make cross-platform transactions possible.&#x20;


# PlatformID

## What is a Platform ID?

Platform ID is the way in which access control is implemented on TalentLayer. It gives exclusive rights for Platform ID holders to read and write to the TalentLayer backend for performing functionality such as minting TalentLayerIDs, creating Services, Proposals, and more.

## How Do I Get a Testnet Platform ID?

Mint your testnet ID by following this guide.

{% content-ref url="/pages/HlKN8HvkvXPbZpF0EHT5" %}
[Get a Platform ID](/get-a-platform-id)
{% endcontent-ref %}

## How Can I Configure Fees With My Platform ID?&#x20;

Learn about what fees are configurable by Platforms in this section of our documentation.&#x20;

{% content-ref url="/pages/TPkBilfgSg7bX3dl4vW2" %}
[Fees & Economics](/readme/basics/platformid/fees-and-economics)
{% endcontent-ref %}

## Learn More About The Tech

Learn more about the technical side of your Platform ID in our Technical Guide on the smart contract.&#x20;

{% content-ref url="/pages/7jOYXfWgCXR5ew7qMaEl" %}
[TalentLayerPlatformID.sol](/technical-guides/lower-level-guides/smart-contracts/talentlayerplatformid.sol)
{% endcontent-ref %}


# Fees & Economics

## Platform Fees

Each Platform can fully configure their fees strategy with 4 different variables:

### originServiceFeeRate

This is a fee that is configured by platforms for jobs that result in the release of an escrow on the TalentLayer network. This is remitted back to the platform that posted the job.&#x20;

*The default configuration of this fee is 0%.*&#x20;

### originValidatedProposalFeeRate

This is a fee that is configured by platforms for proposals that result in the release of an escrow on the TalentLayer network. This is remitted back to the platform that posted the job.&#x20;

*The default configuration of this fee is 0%.*&#x20;

{% hint style="info" %}
**Escrow-Related Fees:** originServiceFeeRate, originValidatedProposalFeeRate enable TalentLayers' interoperability between multiple marketplaces; allowing marketplaces to make money on users and jobs they onboard to the protocol even if they do transactions on other platforms or with users of other platforms.
{% endhint %}

### servicePostingFee

Because many platforms desire to levy fees on job posts, TalentLayer will offer an optional configurable job posting fee.&#x20;

*The default Job Posting Fee will be 0%.*&#x20;

### proposalPostingFee

Because many platforms desire to levy fees on proposal posts, TalentLayer will offer an optional configurable proposal posting fee.&#x20;

*The default Proposal Posting Fee will be 0%.*&#x20;

## Protocol Fee

This fee is used to support the development of the TalentLayer protocol. It is sent to the TalentLayer treasury. The treasury is currently managed by the TalentLayer Core team and will eventually be transitioned to be managed by the eventual TalentLayer DAO.&#x20;

*This fee is set at 1%.*

## Gas Fees

By making transactions, your users will experience any gas or transaction fees required by the blockchain you are interfacing with.&#x20;

## Covering Fees for Users

With our Delegation system, you can cover protocol fees and gas fees on behalf of your users. This allows you to create a "fee-less" experience for users.&#x20;

Fee Sponsorship can be configured with your Platform ID and involves you loading a wallet with cryptos to cover fees that users incur.

{% content-ref url="/pages/BeMQvSN20AF8rtAJ09yY" %}
[Broken mention](broken://pages/BeMQvSN20AF8rtAJ09yY)
{% endcontent-ref %}


# TalentLayerID

Basic understanding of the user identity on TalentLayer.

{% hint style="info" %}
In Native Integrations, each user of a platform will have a TalentLayer ID. In On-Demand Integrations manage one master TalentLayerID that engages with the protocol on behalf of the platform's users. Learn more in the Options for Integration section of the documentation.&#x20;
{% endhint %}

## What is a TalentLayerID?

TalentLayer ID is a decentralized identity that allows ownership and growth of reputation across many service marketplaces. TalentLayer IDs are ERC-721 NFTs that live inside crypto wallets; this means that reputation is self-custodied by the wallet owner and lives separately from integrated platforms. TalentLayer IDs are “soul-bound” in that they can not leave the originating wallet after they have been used in a service engagement.

TalentLayerID is the core identity element for service buyers and sellers when interacting with TalentLayer-integrated marketplaces and freelancing tools. The TalentLayer ID is associated with reviews and represents the overall reputation in the ecosystem.

More information on how it is implemented can be found in out guide on the [TalentLayerID smart contract](/technical-guides/lower-level-guides/smart-contracts/talentlayerid.sol).

## Role-Agnostic IDs

There is no concept of "separate accounts" for buyers and sellers.&#x20;

## TalentLayer ID Handles

Your TalentLayer ID Handle is a unique string of characters and numbers that you can choose when you create your TalentLayer ID. This handle is how others can search for your reputation.

You can have a maximum of 31 characters in your TalentLayer ID and use only low characters, numbers and - or \_.

{% hint style="info" %}
**Choose wisely:** Make sure you choose your handle carefully - after your ID has been initiated, your handle can not be changed.
{% endhint %}

## How to Get a TalentLayer ID

TalentLayer IDs can be minted on any platform integrated with TalentLayer during the user onboarding flow. They can also be claimed from TalentLayer directly on the following web page.

{% embed url="<https://claim.talentlayer.org>" %}

### How much do TalentLayer IDs cost

| CHARACTERS | PRICE (MATIC) |
| ---------- | ------------- |
| 5+         | 1             |
| 4          | 25            |
| 3          | 50            |
| 2          | 100           |
| 1          | 200           |

Standard TalentLayer IDs are between 5 and 31 characters long - they are available for minting for a symbolic 1 MATIC by anyone.\
\
Specialty TalentLayer IDs are between 1 and 4 characters long - these are available for purchase at the price rates below. Proceeds from TalentLayer ID sales go to fund TalentLayer’s open-source development.

### Can I trade TalentLayer IDs?

TalentLayer ID’s are Activity Bound NFTs - they are transferable until they are used to make any activity on TalentLayer. Activities include creating a job post, submitting a proposal, and other actions. Once a TalentLayer ID becomes active, they become soul-bound and can not be transferred.

## Learn More About The Tech

Learn more about the technical side of the TalentLayer ID in our Technical Guide on the smart contract.&#x20;

{% content-ref url="/pages/otKizYaPbizPPiy0XRoJ" %}
[TalentLayerID.sol](/technical-guides/lower-level-guides/smart-contracts/talentlayerid.sol)
{% endcontent-ref %}


# Reviews

TalentLayer ID’s build their reputation over time through receiving Reviews from counter-parties; people that have engaged in transactions as either a buyer or seller.

A Review is an ERC 721 soulbound NFT that is associated with a TalentLayer ID. New Reputation NFTs are minted whenever a service is completed on TalentLayer. They include information about the service completed and a review from the other party. A TalentLayer ID can have an unlimited number of Reputation NFTs. Service buyers leave Reputation NFTs for service sellers and service sellers leave Reputation NFTs for service buyers - a mutual-review system.

## Learn More About The Tech

Learn more about the technical side of reviews in our Technical Guide on the smart contract.

{% content-ref url="/pages/T574CoS33xXCZuOuG2KN" %}
[TalentLayerReview.sol](/technical-guides/lower-level-guides/smart-contracts/talentlayerreview.sol)
{% endcontent-ref %}


# Services

[TalentLayerService.sol](https://github.com/TalentLayer/talentlayer-id-contracts/blob/main/contracts/TalentLayerService.sol) is the smart contract that creates an instance of a Services and associates Proposals with that service as they are submitted. Services and Proposals are not minted as NFTs - rather, their data are stored in an on-chain registry within the smart contract and on IPFS for detailed informations. Services and Proposals updated easily depending on their status.

The Service Registry is a searchable on-chain repository of services such as jobs that can be posted to and read from by any integrated platform. This cross-posting allows for a broader range of users to view and submit proposals for a wide range of services.

The Service Registry contains a few sub-components that can be used together or separately based on the goals of your platform.

## Services

Services are created by Users on TalentLayer-integrated platforms. Services' metadata is stored in the ServiceRegistry.sol smart contract and on IPFS. Services are viewed on platforms by searching the TalentLayer Graph based on keywords, geographic origins, service types, and other factors.

### Service Status

&#x20;are assigned different statuses based on the stage of the service lifecycle they are in.

```
enum Status {
    Opened /// The service has been created, but not assigned to an employee yet
    Confirmed, /// The service has been consented to by an employee
    Finished, /// The service has been market completed by an employer
    Cancelled, /// The service has been canceled by the employer
    Uncompleted, /// The service has not been completed fully by the freelancer and funds has been reimbursed
}
```

## Proposals

Services with Proposals are executable across multiple platforms.

For example, User A, the employer can post a Service on Platform Y and User B can search for and submit a Proposal for that Service on Platform Z.

When a proposal is created by a user for a service, the proposal is stored in an array inside the metadata of that service.

A proposal is automatically marked as validated when the employer sends the money to the escrow.

Then on every release of money to the worker, platform Y and platform Z will have percentage fees on it depending on their own configuration.

## Learn More About The Tech

Learn more about the technical side of services and proposals in our Technical Guide on the smart contract.&#x20;

{% content-ref url="/pages/6bKA9UNPGK77KHqNTjy2" %}
[TalentLayerService.sol](/technical-guides/lower-level-guides/smart-contracts/talentlayerservice.sol)
{% endcontent-ref %}


# Escrow and Dispute

The TalentLayer Escrow and Dispute Resolution Module allows for secure payments between users on TalentLayer integrated marketplaces and between TalentLayer integrated marketplaces. TalentLayer's Escrow and Dispute Resolution allows for users on different marketplaces to remit transactions and handle disputes associated with [specific Services.](/readme/basics/jobs-and-proposals)

## Capabilities

TalentLayer Escrow is a highly flexible escrow contract that allows you to, via your platform's user interface, allow users to conduct payments including:&#x20;

* Partial release of escrow to the counter-party by the employer.&#x20;
* Milestone-based projects, where based on the delivery of certain sub-services, an employee can receive payments.
* Periodical payment of hourly work; for example, weekly pay for hours worked.

Escrow contracts can not currently be refilled after the full amount has been released. After the full amount has been released, the service will automatically be marked complete and prompt the users to leave a review. Future transactions will have to be completed in a subsequent service.

It is possible for a freelancer to voluntarily opt-out of a service and be compensated partially for work completed, with the employer receiving the remaining amount of escrow back to their own wallet.

## TalentLayer Escrow With or Without Dispute Management

At the platform level, you can decide whether to allow your users to initiate disputes or not. If you choose to allow for disputes, you must choose which type of arbitration to support.&#x20;

For current arbitration options, please see the following page:&#x20;

{% content-ref url="/pages/691tpTpFU07IfJXZvZeg" %}
[Arbitration](/readme/basics/escrow-and-dispute/arbitration)
{% endcontent-ref %}

## Fiat Management

Currently, if a platform wants to manage fiat-based payments, this requires off-ramps and on-ramps be created on either side of the transaction.&#x20;

In the soon-to-be-released TalentLayer Abstracted SDK and API we will support fiat off-ramp and on-ramp nativley, so users can leverage fiat payments in a seamless manner.&#x20;

## Learn More About The Tech

Learn more about the technical side of escrow and disputes in our Technical Guide on the smart contract.&#x20;

{% content-ref url="/pages/UBuLl4u6jTNwzR8Avtth" %}
[Escrow & Dispute Contracts](/technical-guides/lower-level-guides/smart-contracts/escrow-and-dispute)
{% endcontent-ref %}


# Dispute Workflow

## Terminology

* **Dispute:** a disagreement between two (or more) parties
* **Ruling:** a decision taken about the resolution of a dispute
* **Arbitrator:** the entity that has the power to give a ruling
* **Appeal:** an opportunity for a party to try to contest the current (but not final) ruling
* **Evidence:** material provided by a party to support his/her viewpoint

## Dispute Resolution Deep Dive

{% embed url="<https://youtu.be/3GlDAjYVC2E>" %}
Recorded January 2023 - Subject to Change
{% endembed %}

## Dispute Workflow

1. Two parties disagree on something.
2. They can't reach an agreement. One party submits a request for creating a *dispute.*
3. The other party has a period of time to accept the dispute and proceed with its creation by paying the arbitration fee as well. If the time period expires, the dispute will be resolved in favor of the party who requested its creation.
4. If the dispute gets created, each party can submit *evidence* to show their viewpoint and give reasons why they believe they are right
5. An *arbitrator* reviews the supporting materials, decides who’s right and gives a *ruling*
6. A period opens in which the parties can *appeal* the *ruling* taken by the *arbitrator*
7. The *arbitrator* considers the *appeals* and after the appeal period is over takes a final decision, entering the **ruling** (more on this later)

Example: dispute on the execution of a service

1. Alice has hired Bob on HireVibes to create a website for her business. The money is locked in an escrow and will be released to Bob once the job is done and meets Alice’s expectations.
2. When Bob is done with the website, Alice believes that the job was not done sufficiently for Bob to be paid. They disagree on this, so Alice decides to request the creation of a dispute.
3. Bob does not agree with Alice, so he decides to proceed with the creation of the dispute.
4. Both Alice and Bob submit materials to support their point of view
5. HireVibes’ customer support reviews the materials and believes that Alice is right
6. Bob can appeal HireVibes’ decision, giving extra information
7. HireVibes’ still believes that Alice is right and takes a final decision. The escrow is automatically released to Alice

{% hint style="info" %}
**Who is The Arbitrator?** Arbitrators can be either centralized (e.g. the customer support of the platform) or decentralized (e.g. Kleros’ network of jurors). TalentLayer supports two options for Dispute Resolution; Kleros and centralized dispute resolution, which is managed by the platform posting the job.&#x20;
{% endhint %}

## Costs

Arbitrators usually take a fee for ruling on a dispute.

When a party requests the creation of a dispute, it has to lock the arbitration fee in the escrow. In order to accept the dispute, the other party will have to pay the arbitration fee as well. The winner of the dispute will get the arbitration fee reimbursed.

Appeals also have a cost which is defined by the arbitrator.

## Standards

In order to be as modular and interoperable as possible, the dispute resolution implementation follows two of the standards defined for this purpose: the Arbitration and Evidence standards.

{% content-ref url="/pages/oRJPj2GFiLK0XqmF0YjE" %}
[ERC-792: Arbitration standard](/technical-guides/lower-level-guides/standards/erc-792-arbitration-standard)
{% endcontent-ref %}

{% content-ref url="/pages/hGj1pM3kBOoGfIpm3jBh" %}
[ERC-1497: Evidence Standard](/technical-guides/lower-level-guides/standards/erc-1497-evidence-standard)
{% endcontent-ref %}


# Arbitration

## Arbitration of Escrow

We have an escrow that allows to perform transactions between two parties (seller and buyer of a service). Seller and buyer can create disputes.

The escrow can be ruled by an arbitrator, which will have the power to decide where the funds will go in case of a dispute (either pay the seller or reimburse the buyer).

{% hint style="info" %}
The initial implementation offers only either a binary decision (one user receives 100% of the pay-out after a ruling), or a 50% - 50% resolution (funds are equally split between the two parties). We ideally want this to be more flexible, by instead allowing to release a specific percentage of the funds to one party and the rest to the other one (e.g. 25% - 75%). We intend to adapt this.
{% endhint %}

## **Arbitration Solutions**

We intend to offer support for various decentralized dispute resolution options. The goal is to have Arbitration be as modular as possible.&#x20;

### Kleros Arbitration

Currently, we support Kleros, which is a dispute protocol with a network of jurors that act as the arbitrator for disputes. Kleros only supports disputes on the Ethereum blockchain.&#x20;

{% content-ref url="/pages/ytsmzIczuNLow8EOhbns" %}
[Kleros Arbitration](/readme/basics/escrow-and-dispute/arbitration/disputes)
{% endcontent-ref %}

### Platform-Managed Arbitration

Since Kleros doesn’t exist on all chains, we need an alternative solution for these other chains. For now, we provide a centralized solution "Platform-Managed Arbitration" where platforms have the power to rule on the disputes which arise on services created with them. This is conducted similar to how most marketplaces handle disputes today - with customer service agents or administrators deciding rulings.

{% hint style="info" %}
The initial implementation has no appeals. We plan to implement appeals.&#x20;
{% endhint %}

{% content-ref url="/pages/gdAAgWD8ww0g9Q2jn0GN" %}
[Platform Managed Arbitration](/readme/basics/escrow-and-dispute/arbitration/platform-managed-arbitration)
{% endcontent-ref %}

Where multiple options for arbitration are available, for example, on Ethereum, the platform can choose which solution they want to support (either Kleros, or platform-managed, etc..).

## Arbitration Management and Customization

Each platform has the ability to choose a set of parameters to customize its arbitration strategy. This is currently possible by interacting with the smart contracts of the protocol. We intend to provide a frontend that platforms can use to easily manage arbitration.

### Updating Arbitration Solution

A platform can decide to change its arbitration solution at any time. For example, it can decide to switch from platform-managed dispute resolution to Kleros.

When changing arbitration strategy, the existing confirmed services will still be managed with the old arbitration solution.

### Updating Arbitration Fee Timeout

When a party requests to raise a dispute, the other party has a period of time to accept the dispute and proceed with its creation by paying the arbitration fee. If the time period expires, the dispute will be resolved in favor of the party who requested its creation.

A platform can customize the time period that a party has to accept a dispute.


# Kleros Arbitration

Through leveraging an augmented version of Kleros Escrow, TalentLayer's escrow system is fully compatible with the Kleros decentralized dispute resolution protocol. When one user initiates a dispute, the result of the dispute is judged by jurors in the Kleros Court system. A ruling is then sent back to the platform, to be displayed to the users.

By having decentralized multi-party juror dispute resolution, TalentLayer allows marketplaces to avoid the common pitfalls that come with centrally managed dispute resolutions; namley, biased decisions, high cost of resolution, and inefficiency.

### Learn About Kleros

{% embed url="<https://kleros.io/>" %}

### How does Kleros Court work?

Once a dispute has been initiated via TalentLayer, on the backend your dispute gets sent to Kleros Court. The lifecycle of disputes follows a five, step process.

> A dispute goes through several stages after (it) is created:
>
> 1. **Evidence** - Evidence can be submitted. This is also when drawing has to take place.
> 2. **Commit** - Jurors commit a hashed vote. This is skipped for courts without hidden votes.
> 3. **Vote -** Jurors reveal/cast their vote depending on whether the court has hidden votes or not.
> 4. **Appeal** - The dispute can be appealed.
> 5. **Execution** - Tokens are redistributed and the ruling is executed.
>
> The period of each stage is different for each (sub)court.
>
> From "What Happens During a Dispute" on [Kleros.Gitbook.io](https://kleros.gitbook.io/docs/products/court/what-happens-during-a-dispute)


# Platform Managed Arbitration

Platform-Managed Arbitration is a centralized solution where platforms have the power to rule on the disputes which arise on services created with them.

This is conducted similar to how most marketplaces handle disputes today - with customer service agents or administrators deciding rulings.

{% content-ref url="/pages/UBuLl4u6jTNwzR8Avtth" %}
[Escrow & Dispute Contracts](/technical-guides/lower-level-guides/smart-contracts/escrow-and-dispute)
{% endcontent-ref %}

### Updating arbitration price

Platform which choose to use platform-managed dispute resolution can decide what price they take for arbitrating on a dispute.

The price is initially set to 0 and can be updated at any time.


# Current Network Liquidity

TalentLayer's network currently is primarily composed of supply and demand in the software developer niche. This means that a TalentLayer integration is most valuable for marketplaces who are seeking to increase transactions in this market segment.&#x20;

We aim to bootstrap network effects in this niche before focusing on other niches.&#x20;

{% hint style="info" %}
It is possible today for teams to develop applications in any other niche of the services economy, but there will not be significant best-match liquidity available for them to leverage.&#x20;
{% endhint %}


# Decentralization

## Technical Components

TalentLayer intends to be a fully decentralized at the protocol level. We have composed all TalentLayer Core contracts in a decentralized way from day 1. Below you can explore the few centralized components that exist today; all relating to operations, onboarding, and third-party integrations.

{% hint style="info" %}
**Why Decentralization?** How we find and do work is one of the most important aspects of our lives as humans. At TalentLayer, we believe that the most resilient, uncensorable, and accessible systems are user-owned, decentralized, and autonomous. We're architecting our infrastructure and organizational structures in line with this vision. What does that mean for you as a platform? If you build on TalentLayer, you're building on a perpetual protocol that will always be around to help you serve your users.&#x20;
{% endhint %}

### Centralized Technical Components

#### TalentLayer Treasury

The TalentLayer Treasury is a multi-sig wallet that receives revenue from protocol fees.&#x20;

This treasury management will eventually be transitioned to TalentLayer's decentralized governance. Here token holders and those they elect will vote on how to allocate the funds to support TalentLayer's continued development and growth.&#x20;

Learn more about current fees below.

{% content-ref url="/pages/TPkBilfgSg7bX3dl4vW2" %}
[Fees & Economics](/readme/basics/platformid/fees-and-economics)
{% endcontent-ref %}

#### Platform ID Minting Capabilities

The Platform ID smart contract currently allows the contract owner to mint Platform IDs on behalf of platforms. There is currently no way to self-mint Platform IDs. Today, minting is managed by the TalentLayer Team.&#x20;

This right to mint Platform IDs will eventually be transitioned to TalentLayer's decentralized governance. The community can then decide how to manage approval for platforms.&#x20;

Learn more about Platform IDs here.

{% content-ref url="/pages/2KzyqiEeag9Ax7Z5ac68" %}
[PlatformID](/readme/basics/platformid)
{% endcontent-ref %}

#### Subgraph Hosting

TalentLayer's subgraph is currently live on on The Graph's Hosted Service (a service hosted by The Graph's team). The Graph is currently undergoing their own decentralization effort, which involves deploying support for more chains and storage systems on The Graph Network (a decentralized protocol).&#x20;

TalentLayer is in the process of migrating our various subgraphs as The Graph Network deploys support for those networks and maintains sufficient uptime on them to support our platform partner's transactions.&#x20;

### Decentralized Technical Components

All contracts and components not specifically mentioned in the section above are decentralized today - this includes every core smart contract of TalentLayer. This means even if the TalentLayer team was no longer around, the contracts would still run and function as intended according to the specifics of the code and documentation. This is one of the most powerful elements of blockchain technology; it enables "infinite machines" as Vitalik Buterin put it.&#x20;

## Development Operations

Today, TalentLayer is being maintained by a team of open-source developers. TalentLayer was founded by [Kirsten](https://kirstenpomales.com/) and [Romain](https://github.com/0xromain), who coordinate the team. This team manages the TalentLayer treasury and the few centralized components discussed in the prior section. &#x20;

In the future, once our decentralized governance launches, our development operations will also be decentralized. Token holders and representatives they elect will then have control over treasury and contract management; funding open-source development and growing the protocol as the community votes.&#x20;

## Decentralization Timeline

TalentLayer intends to launch our token and transition contract management to a decentralized organization when we have reached a sufficient number of integrated platforms. We believe this number will be between 20 and 50 platforms, depending on the level these platforms choose to co-create with us. This is because tokenomics and governance design of decentralized systems needs to be crafted with involvement from diverse ecosystem participants; in our case, this includes various types of integrating platforms and their users.&#x20;

We hope to closely involve our earliest marketplace partners in a process of tokenomics and governance co-creation to ensure that TalentLayer succeeds in it's goal of being a user-owned and governed network.&#x20;

If you have any questions on our decentralization timeline or where we are now, feel free to get in touch!

{% content-ref url="/pages/w2yeM4XbqEwSNUa7qoCm" %}
[Contact The Team](/quick-start-integration-guide)
{% endcontent-ref %}


# Technical Guides

Welcome to TalentLayer's technical concept guides!&#x20;

Click "Next" to go through the concepts guides in order.&#x20;


# Web 3 SDK & API

**TalentLayer's Web 3 API and SDK** is live and available for integration. The Web 3 API and SDK assumes a web 3 native user experience on the platform level.

The TalentLayer Web 3 API is currently the main way that platforms are building TalentLayer integrations.

## TalentLayer NPM Client

{% embed url="<https://www.npmjs.com/package/@talentlayer/client>" %}

## TalentLayer SDK Git

{% embed url="<https://github.com/TalentLayer/talentlayer-sdk>" %}


# StarterKit Template

"StarterKit" is an open-source fork-able mobile-first marketplace codebase that is a great starting point for teams building on TalentLayer.&#x20;

StarterKit is...

* a fully functioning freelance marketplace DAPP
* build with React, Tailwind, WalletConnect, and TalentLayer API
* 100% open-source
* mobile-first, with PWA support

It's a good first step to set up a local version of StarterKit to...

1. Get to know how frontends interface with TalentLayer
2. Use it as a foundation for your next DAPP.

## Explore the DAPP

We've hosted a version of StarterKit on Mumbai testnet for you to play around with! Have fun.

{% embed url="<https://www.starterkit.work/>" %}

## View on Github&#x20;

{% embed url="<https://github.com/TalentLayer-Labs/starter-kit>" %}

## Local Setup Instructions

### 1. Clone Repository from GitHub

```bash
git clone https://github.com/TalentLayer-Labs/starter-kit
```

### 2. Move to directory

```bash
cd starter-kit
```

### 3. Setup .env File

Setup your local environnement by copying the .env.example and adjust the variables.

Main variable to update:

* Get the NEXT\_WALLECT\_CONNECT\_PROJECT\_ID from  <https://walletconnect.com/>
* Get the NEXT\_INFURA\_ID and VITE\_INFURA\_SECRET at <https://www.infura.io/> by creating a new project for IPFS.
* Set NETWORK\_ID to for now to the ID for Mumbai Testnet. Find the chainID on [Chainlist](https://chainlist.org/).&#x20;
* NEXT\_PUBLIC\_PLATFORM\_ID: use 4, the default value, or [create your own platform](https://docs.talentlayer.org/get-a-platform-id) to setup your custom fees and more:

Advanced configuration:

* coming soon

### 4. Install dependencies

```bash
npm i
```

### 5. Run the dapp!

```bash
npm run dev
```


# Technical Schemas

## Smart Contract Schema

{% embed url="<https://miro.com/app/board/uXjVPLKzJbY=/>" %}


# Network Support

## Supported Networks&#x20;

| Network        | Status                   | Type    | Features\*                             |
| -------------- | ------------------------ | ------- | -------------------------------------- |
| Polygon Mumbai | Live                     | Testnet | All features except Kleros Arbitration |
| Polygon        | Deploying April 4th 2023 | Mainnet | All features except Kleros Arbitration |

## View Deployments

{% content-ref url="/pages/5ElZdzA4BVpXxKgZyJfQ" %}
[Deployments](/technical-guides/lower-level-guides/smart-contracts/deployments)
{% endcontent-ref %}


# Lower-Level Guides

The following guides and documentation sets pertain to direct integration to TalentLayer without the TalentLayer SDK and API.&#x20;

For most teams, we recommend using the TalentLayer SDK and API. These documents serve mostly as informational resources so you can better understand what's happening underneath the hood.&#x20;


# Smart Contracts

In this guide we will take a close look at each smart contract's structure and function

## Writing to TalentLayer Core

Writing is facilitated by connecting your platform with the various TalentLayer Core smart contracts and triggering different actions.

Writing is necessary to create a user’s TalentLayer ID, mint reputation updates, and create services.

To write to TalentLayer Core, you must connect with the appropriate TalentLayer smart contract for the action you intend to take.

We currently have a fork-able codebase that is set up to interact with TalentLayer’s smart contracts: the Indie DAPP.

{% content-ref url="/pages/ZzbEAiYgf6cN5VrZ2Shq" %}
[Broken mention](broken://pages/ZzbEAiYgf6cN5VrZ2Shq)
{% endcontent-ref %}

## Permission to Write

As of today, only platforms may write to TalentLayer. In order to get registered as a platform, learn more about requesting a Platform ID here:

{% content-ref url="/pages/2KzyqiEeag9Ax7Z5ac68" %}
[PlatformID](/readme/basics/platformid)
{% endcontent-ref %}


# Deployments

The latest deployment addresses can be found directly on GitHub.&#x20;

## Polygon Mainnet Deployment Addresses

{% embed url="<https://github.com/TalentLayer/talentlayer-contracts/blob/main/.deployment/polygon.json>" %}

## Mumbai Testnet Deployment Addresses

{% embed url="<https://github.com/TalentLayer/talentlayer-contracts/blob/main/.deployment/mumbai.json>" %}


# TalentLayerPlatformID.sol

## **About The Contract**

[**TalentLayerPlatformID.sol**](https://github.com/TalentLayer/talentlayer-id-contracts/blob/main/contracts/TalentLayerPlatformID.sol) is the contract that initializes an integrating platforms' ID. The contract can be used to:

* Mint a PlatformID
* Update platform off-chain data&#x20;
* Manage platform fees
* Manage dispute resolution strategy for the platform&#x20;
* Activate signing mechanism on service and proposal posting

## Data Structure

![](/files/xGoLY73uyX695D5NbEwP)

## Visualization

<figure><img src="/files/0AUmTAKX8CUEMsUJEKhx" alt=""><figcaption></figcaption></figure>

## Learn More

Learn more about why we have Platform IDs and how to get one here:

{% content-ref url="/pages/2KzyqiEeag9Ax7Z5ac68" %}
[PlatformID](/readme/basics/platformid)
{% endcontent-ref %}


# TalentLayerID.sol

[**TalentLayerID.sol**](https://github.com/TalentLayer/talentlayer-id-contracts/blob/main/contracts/TalentLayerID.sol) is the smart contract that initializes a user's TalentLayer ID. The contract can be used to:

* Mint a TalentLayer ID
* Update profile off-chain data&#x20;
* Transfer the NFT if there is no activity linked to it
* A TalentLayerID can delegate right to another address to execute transaction on the protocol (for example to pay for gas fees, or to automatize release)

## Data Structure

### ![](/files/gWorJ3pQBjfy2DFtwMkN)

## Visualization

<figure><img src="/files/spPdo3X74Kz8fHLM8TdW" alt=""><figcaption></figcaption></figure>

## Learn More

Learn more about why we have TalentLayer IDs and how they function in workflows:&#x20;

{% content-ref url="/pages/9GhtVMSPzMbDa5QyMRKE" %}
[TalentLayerID](/readme/basics/what-is-talentlayer-id)
{% endcontent-ref %}


# TalentLayerService.sol

[TalentLayerService.sol](https://github.com/TalentLayer/talentlayer-id-contracts/blob/main/contracts/TalentLayerService.sol) is the smart contract that creates an instance of a Services and associates Proposals with that service as they are submitted. Services and Proposals are not minted as NFTs - rather, their data is stored in an on-chain registry within the smart contract. Services and Proposals can be deleted or updated easily. The contract can be used to&#x20;

* Create a new service&#x20;
* Add a proposal for a open service&#x20;
* Update a service or a proposal&#x20;
* Look up service and proposal

## Data Structure

![](/files/hkEC2Xa9Cb9HPj6kYTvj)

## Visualization

<figure><img src="/files/cnibp5YwJa4vVUKXtbi3" alt=""><figcaption></figcaption></figure>

## Learn More

Learn more about why we have the Service Registry and how it functions in workflows:&#x20;

{% content-ref url="/pages/LvNzEQQqu1z0RQhwaoVJ" %}
[Services](/readme/basics/jobs-and-proposals)
{% endcontent-ref %}


# TalentLayerReview\.sol

[**TalentLayerReview.sol**](https://github.com/TalentLayer/talentlayer-id-contracts) is the smart contract that handles reviews written to users' TalentLayer IDs. Reviews are minted as NFTs.

It can used to:

* Mint a review to a user for a completed job
* Look up reviews

## Data Structure

![](/files/dLpfWSUeC290sFMM6j3V)

## Visualization

<figure><img src="/files/gLtH6KNSmwjlKQSC1Cpm" alt=""><figcaption></figcaption></figure>

## Learn More

Learn more about why we have Reviews and how they function in workflows:&#x20;

{% content-ref url="/pages/VfKlCQ55pRVUTNb38LNg" %}
[Reviews](/readme/basics/reviews-and-reputation)
{% endcontent-ref %}


# Escrow & Dispute Contracts

[**TalentLayerEscrow.sol**](https://github.com/TalentLayer/talentlayer-id-contracts/blob/main/contracts/TalentLayerEscrow.sol) is the smart contract that handles escrow transactions for each service, it custody the money and allow hirer to release the money by milestone. Also, in case of a dispute it handle all the workflow and link with the configured arbitrator.&#x20;

It can used to:

* Validate a proposal for a given service and deposit the money
* Release money for the worker&#x20;
* Let the worker reimburse the hirer
* Raise a dispute
* As a platform, claim the fees earned

## Data Structure

![](/files/jsi6ZWzbj7J4BebyNZZk)

## Visualization: TalentLayerEscrow\.sol

<figure><img src="/files/jKIzxuG3YPKQSdpNNDNa" alt=""><figcaption></figcaption></figure>

## Visualization: TalentLayerArbitrator.sol

<figure><img src="/files/98sskaIJxcM3W9ya9wIX" alt=""><figcaption></figcaption></figure>

## Learn More

Learn more about our escrow and dispute system and how they function in workflows:&#x20;

{% content-ref url="/pages/u73Tcoh8q4sCVJGnqTPa" %}
[Escrow and Dispute](/readme/basics/escrow-and-dispute)
{% endcontent-ref %}


# The Graph

Welcome to the TalentLayer API documentation. This documentation is intended to help you get started with the TalentLayer API. It is a work in progress and will be updated regularly.

TalentLayer provides querying mechanisms by using the tools provided by [The Graph](https://thegraph.com/en/). The [TalentLayer Subgraph](https://github.com/TalentLayer/talentlayer-id-subgraph) is the implementation of [The Graph](https://thegraph.com/en/) that should be used to query TalentLayer data. The Data that can be queried is defined by [the Subgraph Schema](https://github.com/TalentLayer/talentlayer-id-subgraph/blob/main/schema.graphql).

We have listed all deployed graph endpoints on the [introduction page](/technical-guides/lower-level-guides/graph-schema/introduction).

##


# Introduction

## Exploring the Subgraph

### Subgraph link

* [**Polygon Mumbai Testnet**](https://api.thegraph.com/subgraphs/name/talentlayer/talent-layer-mumbai)
* [**Polygon mainnet**](https://api.thegraph.com/subgraphs/name/talentlayer/talentlayer-polygon)

### Playground Link

{% hint style="info" %}
To ensure that your queries are working properly before using them in your project, you can use the Graph playground below.
{% endhint %}

* [**Polygon Mumbai playground**](https://thegraph.com/hosted-service/subgraph/talentlayer/talent-layer-mumbai)
* [**Polygon mainnet pLayground**](https://thegraph.com/hosted-service/subgraph/talentlayer/talentlayer-polygon)

Please check the demo video just below\
[how to test queries with The Graph Playground](https://loom.com/share/a95f65ebe9da4bd1908ca6aacf0b765b)

{% hint style="info" %}
Using the playground, you can create and save your own graphQL queries and try out the default queries **we provide to get you up and running!**
{% endhint %}

<figure><img src="/files/sqtw3Qg4HhwWa6Ck7zfh" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
On the right-hand side, you have access to some neat functionality that will help you explore the subgraph. Check out **GraphQL Explorer** as well as **Documentation Explorer** to create customized queries on the fly!
{% endhint %}

<figure><img src="/files/ndwt9q4MRVMfWxP29dfU" alt=""><figcaption></figcaption></figure>

### **An Introduction to Writing GraphQL Queries for the Talent Layer Subgraph**

The most commonly used entities have a related description entity that stores the off-chain data hosted on [IPFS](https://www.ipfs.com/).

| On-chain Entity | Off-chain Entity    |
| --------------- | ------------------- |
| Service         | ServiceDescription  |
| Proposal        | ProposalDescription |
| Review          | ReviewDescription   |
| User            | UserDescription     |
| Platform        | PlatformDescription |

The off-chain entity that is related to an on-chain entity can be accessed through the field description. Here is an example of what the relationship looks like in GraphQL.

```graphql
{
  services {
    id
    description {
      id
    }
  }
}gr
```

{% hint style="info" %}
This same pattern can be applied to other entities by simply changing **services** to either **users, proposals, reviews,** or **platforms.**
{% endhint %}


# Querying from an application

### Using GraphQL client

#### **AXIOS**

To make a subgraph request, we use Axios. Axios is a library that allows us to send HTTP requests from the browser or Node.js. You can use Axios (or other libraries) to query the TalentLayer subgraph. You can find the documentation [here](https://axios-http.com/docs/intro)

We will detail step by step how to query the TalentLayer subgraph using Axios and display the result on the front end

### **1 - Request the subgraph**

First we need to set up the process Request (please check the [file](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/utils/graphql.ts) to learn more)

```typescript
/* eslint-disable no-console */
import axios from "axios";
import { config } from "../config";

export const processRequest = async (query: string): Promise<any> => {
  try {
    return await axios.post(config.subgraphUrl, { query });
  } catch (err) {
    console.error(err);
    return null;
  }
};
```

This asynchronous function takes a query string as a parameter, sends it as a POST request to a configured subgraph URL using the axios library, and returns the response data. If an error occurs during the request, the error is logged to the console, and the function returns null

**2 - BUILD THE QUERY**

Now we can use the processRequest function to query the subgraph. Let's build a query! For example, if you want to get the user informations based on the user id, we can use the following query:

```typescript
export const getUserById = (id: string): Promise<any> => {
  const query = `
    {
      user(id: "${id}") {
        id
        address
        handle
        rating
        numReviews
        updatedAt
        createdAt
        description {
          about
          role
          name
          country
          headline
          id
          image_url
          video_url
          title
          timezone
          skills_raw
        }
      }
    }
    `;
  return processRequest(query);
};
```

You can test multiple queries on the [subgraph playground](https://thegraph.com/hosted-service/subgraph/talentlayer/talentlayer-polygon) We will detail in the next queries documentation file how to build your own queries and what kinf of data you can get from the TalentLayer subgraph.

**3- BUILD YOUR HOOK**

Now that we have our query, we can use it in a hook to get the user information.

```typescript
import { useState, useEffect } from "react";
import { getUserById } from "../queries/users";
import { IUser } from "../types";

const useUserById = (userId: string): IUser | null => {
  const [user, setUser] = useState<IUser | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await getUserById(userId);
        if (response?.data?.data?.user) {
          setUser(response.data.data.user);
        }
      } catch (err: any) {
        // eslint-disable-next-line no-console
        console.error(err);
      }
    };
    fetchData();
  }, [userId]);

  return user;
};

export default useUserById;
```

This code defines a custom React hook called **useUserById** , which accepts an **id** parameter and returns a user object or null. It calls the **getUserById** function module to fetch user data by id (detailed just above). If the data is successfully fetched, the user state is updated with the retrieved user object. In case of an error, the error is logged to the console. The custom hook returns the user object, making it easy to use within other components.

Please find the code [here](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/hooks/useUserById.ts)

**4- DISPLAY DATA ON THE FRONT END**

```typescript
// we import the hook
import useUserById from "../hooks/useUserById";

.......
........
.........

// we call the hook and pass the user id as a parameter and we store the object response in a variable
const userDescription = user?.id ? useUserById(user?.id)?.description : null;

.......
........
.........

return (
// we display the data
<p className='text-gray-900 font-medium'>{userDescription?.about}</p>
<p className='text-gray-900 text-xs'>{userDescription?.title}</p>
```

Please find the full code [here](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/UserDetail.tsx)


# Queries examples

Here, we will explore the various queries that you can create.

## Get user information by Talent Layer Id

{% tabs %}
{% tab title="Example query" %}

```graphql
{
  user(id: "59") {
    cid
    handle
    id
    numReviews
    description {
      about
      id
    }
    address
    updatedAt
  }
}
```

{% endtab %}

{% tab title="Example response" %}

```json
{
  "data": {
    "user": {
      "cid": "QmawFHVwFggkFC5FgY2i2dDPfqDiVGvzJJAM7NyxvCAU6W",
      "handle": "martin",
      "id": "59",
      "numReviews": "1",
      "description": {
        "about": "I'm a fan of robots, time rifts and giant bionic shark movies. And all things about #Web3, #Blockchain, #NFT, #Metaverse, #DEFI etc...",
        "id": "QmawFHVwFggkFC5FgY2i2dDPfqDiVGvzJJAM7NyxvCAU6W-1681747217",
      },
      "address": "0x1caab8ded4535bf42728fea90afa7da1ac637e1e",
      "updatedAt": "1681747217"
    }
  }
}
```

{% endtab %}
{% endtabs %}

## Get the service reviews by service id

{% tabs %}
{% tab title="Example query" %}

```graphql
{
  reviews {
    description {
      content
      id
    }
    rating
    to {
      id
      handle
    }
    service {
      id
      status
    }
  }
}
```

{% endtab %}

{% tab title="Example response" %}

```json
{
  "data": {
    "review": {
      "description": {
        "content": "Perfect!",
        "id": "QmW612hUxvg1SigPA5Y3msuBEuXfLaH3axp22dr1kwCXp8-1680027235"
      },
      "rating": "5",
      "to": {
        "id": "2",
        "handle": "migmig"
      },
      "service": {
        "id": "1",
        "status": "Finished"
      }
    }
  }
}
```

{% endtab %}
{% endtabs %}

## Get the first 5 services informations after the 18 March with the open status

{% tabs %}
{% tab title="Example query" %}

```graphql
{
  services(first: 3, where: {createdAt_gt: "1679149214", status: Opened}) {
    id
    createdAt
    updatedAt
    status
    description {
      about
      rateAmount
      rateToken
      startDate
      title
    }
  }
}
```

{% endtab %}

{% tab title="Example response" %}

```json
{
  "data": {
    "services": [
      {
        "id": "100",
        "createdAt": "1681996135",
        "updatedAt": "1681996135",
        "status": "Opened",
        "description": {
          "about": "We looking for a Solidity developer",
          "rateAmount": "1000000000000000000",
          "rateToken": "0x0000000000000000000000000000000000000000",
          "startDate": null,
          "title": "Solidity developer"
        }
      },
      {
        "id": "101",
        "createdAt": "1681997279",
        "updatedAt": "1681997279",
        "status": "Opened",
        "description": {
          "about": "We looking for a Rust developer",
          "rateAmount": "1000000000000000000",
          "rateToken": "0x0000000000000000000000000000000000000000",
          "startDate": null,
          "title": "Rust developer"
        }
      },
      {
        "id": "102",
        "createdAt": "1682017181",
        "updatedAt": "1682017181",
        "status": "Opened",
        "description": {
          "about": "We looking for a C++ developer",
          "rateAmount": "1000000000000000000",
          "rateToken": "0x0000000000000000000000000000000000000000",
          "startDate": null,
          "title": "C++ developer"
        }
      }
    ]
  }
```

{% endtab %}
{% endtabs %}

## Get the total gain and the platform name of the first 3 users with a rating greater than 4

{% tabs %}
{% tab title="Example query" %}

```graphql
{
  users(first: 3,where: {rating_gt: "4"}) {
    handle
    id
    numReviews
    platform {
      id
      name
    }
    totalGains {
      totalGain
    }
  }
}
```

{% endtab %}

{% tab title="Example response" %}

```json
{
  "data": {
    "users": [
      {
        "handle": "thomas",
        "id": "1",
        "numReviews": "1",
        "platform": {
          "id": "4",
          "name": "WorkX"
        },
        "totalGains": [
        {
            "totalGain": "14000000000"
          }
        ]
      },
      {
        "handle": "mattia",
        "id": "11",
        "numReviews": "1",
        "platform": "6clover",
        "totalGains": [
          {
            "totalGain": "100000000"
          }
        ]
      },
      {
        "handle": "migmig",
        "id": "2",
        "numReviews": "1",
        "platform": {
          "id": "4",
          "name": "indie"
        },
        "totalGains": [
          {
            "totalGain": "1000000000000000000"
          }
        ]
      }
    ]
  }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
As you can see, you are not limited in your query building. Please feel free to contact us if you cannot find or build exactly what you need.
{% endhint %}


# Implementing the pagination

In this guide, we'll explore how to implement pagination in your application using GraphQL queries. Pagination is essential for handling large data sets, providing a user-friendly way to navigate and interact with the information. We'll cover key concepts and techniques, enabling you to create efficient, scalable, and seamless user experiences with TalentLayer Graph

## 1-Set and adapt your query

```tsx
export const getUsers = (
  numberPerPage?: number,
  offset?: number,
  searchQuery?: string,
): Promise<any> => {
  const pagination = numberPerPage ? 'first: ' + numberPerPage + ', skip: ' + offset : '';
  let condition = ', where: {';
  condition += searchQuery ? `, handle_contains_nocase: "${searchQuery}"` : '';
  condition += '}';

  const query = `
    {
      users(orderBy: rating, orderDirection: desc ${pagination} ${condition}) {
        id
        address
        handle
        userStats {
          numReceivedReviews
        }
        rating
      }
    }
    `;
  return processRequest(query);
};
```

The function first construct a pagination string based on the provided `numberPerPage` and `offset` parameters. If `numberPerPage` is not provided, the pagination string will be empty, which means that the query will fetch all users without pagination.

Next, the function constructs a conditional string that will be used in the GraphQL query to filter the users based on the provided `searchQuery`. If no `searchQuery` is provided, the conditional string will only contain an empty object.

Then we build the query with our two parameters =>  **pagination** and **condition**

Please find the code [here](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/queries/users.ts)

### Other type of query for pagination

As you can see in the query just below is that we add to parameter **StartDate** and **EndDate** that will allow us to set up a filter by date on our front end

```tsx
export const getPaymentsForUser = (
  userId: string,
  numberPerPage?: number,
  offset?: number,
  startDate?: string,
  endDate?: string,
): Promise<any> => {
  const pagination = numberPerPage ? 'first: ' + numberPerPage + ', skip: ' + offset : '';

  const startDataCondition = startDate ? `, createdAt_gte: "${startDate}"` : '';
  const endDateCondition = endDate ? `, createdAt_lte: "${endDate}"` : '';

  const query = `
    {
      payments(where: {
        service_: {seller: "${userId}"}
        ${startDataCondition}
        ${endDateCondition}
      }, 
      orderBy: createdAt orderDirection: desc ${pagination} ) {
        id, 
        rateToken {
          address
          decimals
          name
          symbol
        }
        amount
        transactionHash
        paymentType
        createdAt
        service {
          id, 
          cid
        }
      }
    }
    `;
  return processRequest(query);
};
```

## 2-Adapt your hook

We add a few state variable and parameter in the **useUsers** hook

```tsx
import { useEffect, useState } from 'react';
import { getUsers } from '../queries/users';
import { IUser } from '../types';

const useUsers = (
  searchQuery?: string,
  numberPerPage?: number,
): { hasMoreData: boolean; loading: boolean; users: IUser[]; loadMore: () => void } => {
  const [users, setUsers] = useState<IUser[]>([]);
  const [hasMoreData, setHasMoreData] = useState(true);
  const [loading, setLoading] = useState(false);
  const [offset, setOffset] = useState(0);

  useEffect(() => {
    setUsers([]);
    setOffset(0);
  }, [searchQuery]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await getUsers(numberPerPage, offset, searchQuery);

        if (offset === 0) {
          setUsers(response.data.data.users || []);
        } else {
          setUsers([...users, ...response.data.data.users]);
        }

        if (numberPerPage && response?.data?.data?.users.length < numberPerPage) {
          setHasMoreData(false);
        } else {
          setHasMoreData(true);
        }
      } catch (err: any) {
        // eslint-disable-next-line no-console
        console.error(err);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [numberPerPage, offset, searchQuery]);

  const loadMore = () => {
    numberPerPage ? setOffset(offset + numberPerPage) : '';
  };

  return { users, hasMoreData: hasMoreData, loading, loadMore };
};

export default useUsers;

```

1. `hasMoreData`: This state variable is a boolean that indicates whether there are more users to fetch. It is initially set to `true`, which means that when the component is first rendered, it assumes there is more data to load. As data is fetched, this state variable will be updated based on the length of the fetched data. If the length of the fetched data is less than the specified `numberPerPage`, it sets `hasMoreData` to `false`, indicating that there are no more users to fetch. it allow you to display a button or a message.
2. `loading`: This state variable is a boolean that indicates whether data is being fetched. It is initially set to `false`. it allow you to display a loader during the fetch process.
3. `offset`: This state variable is a number that represents the pagination offset for the users list. It is initially set to `0`. When the `loadMore` function is called, the `offset` is updated by adding the value of `numberPerPage` to the current offset. The updated offset is then used in the `getUsers` function to fetch the next set of users.
4. `numberPerPage` is the element number you want to display per page, this parameter will be set in the component

Please find the code [here](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/hooks/useUsers.ts)

## 3-Set up the pagination in your component

First we need to set up the parameter needed for **useUsers** call

```tsx
const PAGE_SIZE = 36;
```

will the element number display per page

Then we call the hook **useUsers** and destructure the return object to get users, hasMoreData, loading and loadMore

```tsx
const { users, hasMoreData, loading, loadMore } = useUsers(
    searchQuery?.toLocaleLowerCase(),
    PAGE_SIZE,
 );
```

You map your users oject and display all the data you want in a dedicated component.

```tsx
 <div className='grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4'>
  {users.map((user, i) => {
    return <UserItem user={user} key={i} />;
   })}
 </div>
```

Then  we add a button who call **loadMore**, you can check the hook **useUsers** with the loadMore function that will trigger setOffset.

```tsx
<button
  type='submit'
  className={`......`}
  disabled={!hasMoreData}
  onClick={() => loadMore()}>
  Load More
</button>
```

Please find the code [here ](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/pages/Talents.tsx)


# Metadata

In this section we provide all metadata elements that are used as a part of TalentLayer today. Included below are comments explaining each piece of metadata and giving information on requirements of the various data pieces.&#x20;

{% hint style="info" %}
**Think We're Missing Something?** We're taking active feedback on our metadata and would love to hear what you think about our data structures. Have an opinion? Reach out to our team via the "Get Help" page of these docs.&#x20;
{% endhint %}

## **What Metadata Is Required For My Platform To Use?**&#x20;

All on-chain metadata (except for metadata relating to third-party on-chain integrations) is required operationally for TalentLayer. These required metadata elements are necessary for your platform to function using TalentLayer.

Off-chain metadata is a mixture of required, optional, and recommended. Please refer to each data element for the specifics.&#x20;

Metadata improves search-ability and user experience, but will still allow your workflows to function if left out.&#x20;

## TalentLayer ID

{% content-ref url="/pages/otKizYaPbizPPiy0XRoJ" %}
[TalentLayerID.sol](/technical-guides/lower-level-guides/smart-contracts/talentlayerid.sol)
{% endcontent-ref %}

## Platform ID

{% content-ref url="/pages/7jOYXfWgCXR5ew7qMaEl" %}
[TalentLayerPlatformID.sol](/technical-guides/lower-level-guides/smart-contracts/talentlayerplatformid.sol)
{% endcontent-ref %}

## Service & Proposal

{% content-ref url="/pages/6bKA9UNPGK77KHqNTjy2" %}
[TalentLayerService.sol](/technical-guides/lower-level-guides/smart-contracts/talentlayerservice.sol)
{% endcontent-ref %}

## Escrow

{% content-ref url="/pages/UBuLl4u6jTNwzR8Avtth" %}
[Escrow & Dispute Contracts](/technical-guides/lower-level-guides/smart-contracts/escrow-and-dispute)
{% endcontent-ref %}

## Arbitrator

{% content-ref url="/pages/UBuLl4u6jTNwzR8Avtth" %}
[Escrow & Dispute Contracts](/technical-guides/lower-level-guides/smart-contracts/escrow-and-dispute)
{% endcontent-ref %}

## Review

{% content-ref url="/pages/T574CoS33xXCZuOuG2KN" %}
[TalentLayerReview.sol](/technical-guides/lower-level-guides/smart-contracts/talentlayerreview.sol)
{% endcontent-ref %}


# Third-Party Modules

Third-party modules are integrations with other composable protocols or dev tools that enhance the value prop of platforms building on TalentLayer. These modules were built by TalentLayer Labs and are fully open-source and available for anyone to use in their platforms.

{% hint style="success" %}
All TalentLayer third-party modules are housed in separate folders in the TalentLayer Indie demo dapp, and can easily be added to platforms by copying the folder into your repo.&#x20;
{% endhint %}


# Lens Protocol - Social

TalentLayer has partnered with Lens, the decentralized social graph, to connect our protocols; enabling platforms building on Lens to offer work-related offerings like job posts, proposals, escrow payments and more, and platforms building on TalentLayer to let users display their social history alongside their work reputations.&#x20;

## Why TalentLayer and 🌱 Lens?

TalentLayer and Lens are both composable infrastructures that teams can build new platforms on top of! We both want to see a world where all users own their data and work platforms benefit from user sharing. So, it’s only natural to team up 🤝.

TalentLayer empowers builders in the Lens community to add freelance and work marketplace functionality to their social apps! PLUS: Work marketplace builders can add social feeds to their apps.

Read the full public announcement here:&#x20;

{% embed url="<https://twitter.com/kirstenrpomales/status/1608088909388386304>" %}

## Lens TalentLayer Indie Module: Display 🌱 Lens Feeds on Your Work Platform

The TalentLayer x Lens integration is ready to go! Want to learn how to integrate? Get started with forking our demo DAPP to learn how applications can reference TalentLayer and Lens profile data at the same time.

{% content-ref url="/pages/ZzbEAiYgf6cN5VrZ2Shq" %}
[Broken mention](broken://pages/ZzbEAiYgf6cN5VrZ2Shq)
{% endcontent-ref %}

<figure><img src="/files/4KvhbvB7elpebbWfE5pD" alt=""><figcaption></figcaption></figure>

## Are you Building Social Platforms on 🌱 Lens?

Are you building a social media platform on Lens Protocol? We want to help YOU integrate work tools into your platform. Get in touch here:&#x20;

{% content-ref url="/pages/w2yeM4XbqEwSNUa7qoCm" %}
[Contact The Team](/quick-start-integration-guide)
{% endcontent-ref %}


# XMTP - Messaging

## What is XMTP?

XMTP is an inter-wallet messaging protocol.

{% embed url="<https://xmtp.org/docs/dev-concepts/introduction>" %}

## Why XMTP and TalentLayer?

TalentLayer needs to provide our marketplace builders with tools to allow hirers and workers to message eachother. This is important within one platform, but considering TalentLayer enables transactions between workers and hirers on different platforms, on-chain and interoperable messaging is essential.&#x20;

XMTP provides secure wallet-to-wallet communication via it's composable protocol and toolkit.&#x20;

## XMTP TalentLayer Indie Module

We've developed a module in our Indie Demo Dapp with full on-chain messaging integration using XMTP - hirers can chat with workers and vice versa to discuss terms, payment, the work product, and more. Messaging is interoperable across marketplaces - hirers and workers can message counterparties on other platforms, if this is enabled.

{% hint style="success" %}
All TalentLayer third-party modules are housed in separate folders in the TalentLayer Indie demo dapp, and can easily be added to platforms by copying the folder into your repo.&#x20;
{% endhint %}

<figure><img src="/files/a9tBXaBhtMxdc3kQbEOU" alt=""><figcaption></figcaption></figure>

### View the Module

{% embed url="<https://github.com/TalentLayer-Labs/indie-frontend/tree/main/src/modules>" %}


# Sismo - Privacy

## What is Sismo?

Sismo is an attestation protocol that enables users to selectively reveal data on their wallets such as soul-bound NFTs or wallet activity. This preserves users' privacy by providing summaries of wallet characteristics as Sismo Badges without revealing the wallet address.&#x20;

{% embed url="<https://docs.sismo.io/sismo-docs/>" %}

## Why Sismo and TalentLayer?

Zero Knowledge Proof of Work aka zkPoW is a Sismo-based work reputation privacy layer that lets you prove facts about your work history without revealing all of your job details.

⭐ **Aggregating Reputation:** Unify many accounts and reputations under one Sismo Work Reputation Vault to summarize data on your history across everywhere you do work

⭐ **Protecting Privacy:** Only show marketplaces and hirers Sismo Badges summarizing the work you’ve done - not who you’ve worked for and why

With the combo of TalentLayer and Sismo in zkPoW, finally, workers can share their full reputations while also not sacrificing privacy. This happens with special Sismo Badges that summarize work history that users have gained on TalentLayer network job platforms.

We developed 6 different functions to leverage the TalentLayer data.&#x20;

With these, you can for example :&#x20;

\- prove that you earn a certain amount of money by month, and then use it to obtain a loan

\- prove that you worked for a certain company without revealing the detail of the job&#x20;

\- prove that you are skilled in a subject like solidity by having 5 jobs completed with at least 4 stars&#x20;

\- receive a badge for being the talent of the month on a given skill

\- create a voting power based on the number of times a worker got a 5 stars jobs on a give skill

\- gives a gamification aspect to freelancing, and enhance motivation to perform work using decentralized protocols.

## zkPoW TalentLayer Indie Module

We've developed a module in our Indie Demo Dapp that lets a platform display Sismo badges that a user has earned.

{% hint style="success" %}
All TalentLayer third-party modules are housed in separate folders in the TalentLayer Indie demo dapp, and can easily be added to platforms by copying the folder into your repo.&#x20;
{% endhint %}

<figure><img src="/files/CQsmBnEuI4ny1KiZt3h3" alt=""><figcaption></figcaption></figure>

### View the Module

{% embed url="<https://github.com/TalentLayer-Labs/indie-frontend/tree/main/src/modules>" %}

{% hint style="success" %}
zkPoW was built during the ETH Porto 2023 hackathon! [Learn more about the original build here. ](https://taikai.network/ethporto/hackathons/ethportohackathon2023/projects/clfd3v5pp104522101yfjvhrngbv/idea)
{% endhint %}


# Iexec - Web3Mail

Guide on setting up web3email notifications with TalentLayer

## 1. Introduction

### 1.1 What is Iexec & Web3mail?

IExec is a decentralized compute network that been privacy-focused from day 1 — leveraging confidential computing techniques to enable private data to be processed on untrusted remote servers. The team at IExec developed a tool enabling private email sending leveraging their technology — Web3 Mail.

{% embed url="<https://tools.docs.iex.ec/tools/web3mail>" %}
Iexec Web3mail docs
{% endembed %}

Web3Mail allows platforms to send emails to a user without disclosing his email address. A user can decide to encrypt his email and & allow a platform to send him emails using solely an Ethereum address. The user can decide to revoke this access at any time, or define in advance how many emails he allows the platform to send him. Users to control who can email them and on what terms

Platforms can also enable their own users to send emails to one another, without the users knowing each others email addresses.

Users can “allow” and “disallow” certain senders or demographics of senders (i.e. “I want to only receive outreach from platforms I’ve posted jobs on” or “I want to only receive outreach from hirers who have paid other freelancers successfully”)

They can put up configurable paywalls (i.e. “If someone wants to email me, charge them $0.05 per email” or “If someone wants to email me charge them $0.50 per email, except if the user is a member of Developer DAO”

### 1.2 Why web3mail and TalentLayer?

So far, in enterprise-grade and mainstream consumer facing applications of blockchain technology, user experience and user autonomy/privacy have become polarizing goals; it seems like both can't be achieved at the same time.

One of the best examples of this is how communications with users is handled. Nowadays, personal email is nearly always required to access a decentralized social media, centralized wallet, web 3 hiring platform etc.

The reason that these projects need to gather emails is because, today, email remains the most reliable way to reach users, however this can lead to adverse effects which discourage users to provide their personal email to platforms, such as:

* Users being inadvertently spammed by the platform
* Users being spammed by third parties who managed to get hold of their email (hacks, leaks...)
* Users incentivized to use a fake email to preserve their privacy
* Regulatory issues regarding the use of personal data with GDPR

At TalentLayer, every platform that is building hiring tech using our toolkit needs reliable ways to talk with users regarding events such as Alerts on job postings, payment alerts, promotions etc.

Email being the most reliable way to reach users, we decided to integrate Iexec's web3mail technology in order to allow platforms to send emails to users without disclosing their email address.

***

## 2. Technical Overview

TalentLayer integrated Iexec Web3mail tech by giving a choice to a user to let a platform send him emails for a set of different notifications types to which he can subscribe and unsubscribe anytime he wants. Notification preferences are set in TalentLayer user's metadata.

### 2.1 Workflow

In order to receive email notifications, the user first has to protect (encrypt) his email using Iexec's DataProtector. Then he will grant access to his encrypted email to a platform (represented by the Ethereum Address of the platform's owner). This allows a platform to send any email to this user. The user can revoke this access at any time.

In summary:

* The user protects his email using the DataProtector
* The user grants access to his email to a platform
* The user sets his notification preferences in his TalentLayer metadata

### 2.1 Use Cases

For now, TalentLayer has outlined seven distinct use cases where email notifications can be sent to keep users informed and engaged.

{% hint style="info" %}
Each come with a default value used automatically after the user grant access.
{% endhint %}

#### 1. **Active on New Service (`activeOnNewService`)**

* **Description**: When a new gig that matches the user skills becomes available, he will be notified via email.
* **Purpose**: Ensures the user do not miss out on new opportunities that align with his skillset.
* **Default value:** false

#### 2. **Active on New Proposal (`activeOnNewProposal`)**

* **Description**: Receive an email notification whenever a new proposal is posted on a hirer Gig.
* **Purpose**: Keeps the hirer updated on the interest and activity on his posted Gigs.
* **Default value:** true

#### 3. **Active on Proposal Validated (`activeOnProposalValidated`)**

* **Description**: Be informed via email when the worker proposal has been validated.
* **Purpose**: Lets the worker know the status of his proposal, allowing him to take the next steps.
* **Default value:** true

#### 4. **Active on Fund Release (`activeOnFundRelease`)**

* **Description**: Get an email alert when the worker receive new funds from one of his job, or when the hirer get funds reimbursed
* **Purpose**: Keeps users finances in check by notifying the user of fund releases.
* **Default value:** true

#### 5. **Active on Review (`activeOnReview`)**

* **Description**: An email will be sent when the worker or the hirer receive a new review.
* **Purpose**: Helps the user monitor feedback
* **Default value:** true

#### 6. **Active on Platform Marketing (`activeOnPlatformMarketing`)**

* **Description**: Receive email notifications about important announcements, new features, and partnerships from the platform
* **Purpose**: Ensures the user is up-to-date with the latest developments and features in the platform.
* **Default value:** false

#### 7. **Active on Protocol Marketing (`activeOnProtocolMarketing`)**

* **Description**: Receive email notifications about important announcements, new features, and partnerships from the protocol
* **Purpose**: Ensures the user is up-to-date with the latest developments and features in the protocol.
* **Default value:** false

***

## 3. Iexec Tools within TalentLayer

Iexec uses two different tools in order to enable web3 mails: the **DataProtector** & the **Web3Mail**. The DataProtector module will enable a user to protect his email, grant access to his email to different addresses, fetch the data which was granted to a platform etc. The Web3Mail module is build on top of DataProtector and come with two functions. The first one fetch contacts used to get easily all the contact of a platform and the second one sendEmail use to send an email to one user by address.

### 3.1 Architecture

<figure><img src="/files/Vee9ZEedLBHSsRB2MOCT" alt=""><figcaption><p>Web3mail architecture - New Service notification example</p></figcaption></figure>

#### Backend:

* **Responsibilities**: Each platform within TalentLayer is entrusted with sending its own emails to its users. This approach sustains the efficient functioning of our multi-platform system.
* **Challenge Addressed**: It resolves the identified issue of architecting web3 email within TalentLayer while preserving interoperability between two platforms. For instance, a hirer on Platform A, who allows emails from Platform A, will successfully receive an email notification when a worker from Platform B makes a proposal.
* **How does it work?**
  * A cron based run one script one particular type of email. It uses TalentLayer subgraph data to get the new events that happened on the network
  * Every time something new is dedicated, it check if the platform can send him an email, and if it's the case send the email.
  * An additional database is used here to manage the issue of duplicates and down time of cron.

#### Frontend:

* **Responsibilities**: The frontend is responsible for retrieving user authorization, ensuring only permitted communications reach the users.
* **How does it works?**
  * The user is invited after important action like creating a profile or creating a service to setup email notification.&#x20;
  * The user is redirect to a dedicated page where he can first protect his email data and the authorize the current platform to access it.&#x20;
  * Last step for the user is to validate his preferences

<figure><img src="/files/v0l09J2RhOK89IN2e9vC" alt=""><figcaption><p>User opting for web3 notifications workflow</p></figcaption></figure>

#### Admin:

* **Responsibilities**: Provides API and pages allowing owners to send emails directly, offering more control and efficiency in communication.
* **How does it works?**
  * The platform owner got access to new admin pages where he can send directly to selected pooled of user from his contact an marketing email.
  * The formular is connected with a backend API which handle the connection with iExec web3mail from the dedicated private key of the platform
  * Security note: the public/private key pair used for sending email is different from the one that manage the TalentLayer platform.

#### Graph:

* **Responsibilities**: Manages the storage of preferences, ensuring each user’s choices are honored in the email communication process.
* **How does it works?**
  * Each user got a TalentLayerId store on chain
  * Each TalentLayerId is linked to an offchain metadata in json stored on IPFS which contains all the extra information about a user.&#x20;
  * Our graph indexed these data to make it easly accessible for anyone from the API
  * Since different types of notifications are available, it's important that the user can decide for which ones he would be interested. These preferences are set it the User's metadata, in the "UserWeb3mailPreferences" entity, located in the "UserDescription":

```graphql
 type UserDescription @entity(immutable: true) { 
    id: ID! #cid 
    title: String
    about: String
    skills_raw: String
    skills: [Keyword!]
    timezone: BigInt
    headline: String
    country: String
    user: User!
    role: String # buyer, seller, both 
    name: String # Custom user name 
    video_url: String #url 
    image_url: String #url 
    web3mailPreferences: UserWeb3mailPreferences
}
```

```graphql
type UserWeb3mailPreferences @entity(immutable: true) {
    id: ID! #cid 
    activeOnNewService: Boolean 
    activeOnNewProposal: Boolean 
    activeOnProposalValidated: Boolean
    activeOnFundRelease: Boolean
    activeOnReview: Boolean
    activeOnPlatformMarketing: Boolean
    activeOnProtocolMarketing: Boolean 
}
```

####

{% hint style="info" %}

#### Bellecour sidechain is currently being used in the starterkit dedicated branch as web3mail is only working here

{% endhint %}

**Environment Variables**

There are a set of environment variables dedicated to wbe3mail which should be configured before use:

```xml
// Set to true if you with to enable web3Mail notifications
NEXT_PUBLIC_ACTIVE_WEB3MAIL=false

// Set here the Private & Public key of the ETH address which will be used to perform
// web3mails operations & signatures
NEXT_PRIVATE_WEB3MAIL_PLATFORM_PRIVATE_KEY="xaddyouprivatekeyhere"
NEXT_PUBLIC_WEB3MAIL_PLATFORM_PUBLIC_KEY="0x27FDabe8222d7f874406F3EaBBb9601D87EF1a82"

// Iexec web3mail app address
NEXT_PUBLIC_WEB3MAIL_APP_ADDRESS="web3mail.apps.iexec.eth"

// Set here the cron security key of your choice
CRON_SECRET="xxx"
```

### 3.2 DataProtector

Within TalentLayer, DataProtector functions as the backbone for secure email communication, safeguarding users’ email data with utmost integrity. It enables TalentLayer to utilize user data for personalized email notifications without exposing the actual data. By employing end-to-end encryption and confidential computing technology, DataProtector ensures that while TalentLayer can effectively use the data for email communication, the actual user data remains invisible and inaccessible, thereby upholding user privacy and data security seamlessly.

{% embed url="<https://tools.docs.iex.ec/tools/dataprotector>" %}

It can be used as such:

```typescript
/**
 * @dev: Generate DataProtector
 */
const PRIVATE_KEY = "set-your-private-key";
const protectorWebProvider = getProtectorProvider(PRIVATE_KEY);
const dataProtector = new IExecDataProtector(protectorWebProvider);

/**
 * @dev: Used to protect the user's email
 * @param: name: string - The name of the protected data, in our case "TalentLayer email" 
 * but it should be set to your platform's name in order to be able to filter on it later
 * @param: email: string
 */
const protectedEmail = await dataProtector.protectData({
  name: 'TalentLayer email',
  data: {
    email: 'myEmail@talentLayer.com',
  },
});
```

This method returns a `ProtectedDataWithSecretProps` object, which has an ETH address. Then the user can grant access to his email to a platform using the following method:

```typescript
/**
 * @dev: Used to grant access to a platform to the user's email
 * @param: protectedData: string - The protected email's ETH address
 * @param: userAddress: string - The ETH address of the user which protected his email
 * @param: authorizedUser: string - The ETH address of the user which will be sending emails (Platform owner)
 */
  //Iexec web3mail app address
    const NEXT_PUBLIC_WEB3MAIL_APP_ADDRESS = 'web3mail.apps.iexec.eth'

    // protected email's ETH address
    const protectedData: ProtectedData[] = await dataProtector.fetchProtectedData({
      owner: userAddress,
      requiredSchema: {
        email: 'string',
      },
    });
    
    const protectedEmail = protectedData.find(item => item.name === 'TalentLayer email');

    const grantAccessArgs: GrantAccessParams = {
      protectedData: protectedEmail,
      authorizedApp: NEXT_PUBLIC_WEB3MAIL_APP_ADDRESS,
      authorizedUser: '0x....',
    };

    await dataProtector.grantAccess(grantAccessArgs);
```

In order to check whether access was granted to a platform for a protectedData item, the following method can be used:

```typescript
/**
 * @dev: Used to fetch the list of granted access to a protectedData item
 * @param: protectedData: string - The address of the protected data (protected email in our case)
 * @return: listGrantedAccess: GrantedAccess[] - The list of granted access
 */
  const listGrantedAccess = await dataProtector.fetchGrantedAccess({
    protectedData: protectedData.address,
    authorizedApp: process.env.NEXT_PUBLIC_WEB3MAIL_APP_ADDRESS,
  });
```

After this step, the user's email is protected and access was granted to a platform, which can now send him emails using the Web3Mail provider.

***

### 3.3 Web3Mail

In TalentLayer, Web3Mail operates in conjunction with DataProtector, enhancing the security and efficiency of email communication. It mainly functions to send emails to individual users, ensuring each communication is securely delivered. Web3Mail retrieves encrypted email addresses (contacts) for which access has been granted and sends emails to those addresses

{% embed url="<https://tools.docs.iex.ec/tools/web3mail>" %}

It can be used as such:

```typescript
const PRIVATE_KEY = "set-your-private-key";

const mailWeb3Provider = getMailProvider(privateKey);
const web3mail = new IExecWeb3mail(mailWeb3Provider);

/**
 * @dev: Used to fetch contacts which granted access to their encrypted email to a platform
 */
const contactList: Contact[] = await web3mail.fetchMyContacts();
```

After fetching the contacts, a platform can send emails to them using the following method on each contact:

```typescript
/**
 * @dev: Used to send an email to a contact
 * @param: contactList: Contact[] - The list of contacts
 * @param: emailSubject: string - The subject of the email
 * @param: emailContent: string - The content of the email
 */
for (const contact of contactList) {
  await web3mail.sendEmail({
    protectedData: contact.address,
    emailSubject: emailSubject,
    emailContent: emailContent,
  });
}
```

For sending emails to only a set of addresses, it's important to check whether the provided address had granted access to his email to the platform. Otherwise, the sendEmail function will crash. This can be done using the DataProtector provider, by first fetching the addresses' protected data, then checking whether access was granted to the platform.

This can be done using the following method:

```typescript
/**
 * @dev: Fetch all data a user protected
 */
  const protectedData = await dataProtector.fetchProtectedData({
    owner: userAddress,
    requiredSchema: {
      email: 'string',
    },
  });

/**
 * @dev: Filter on the protected email for TalentLayer
 */
const protectedEmail = protectedData.find(item => item.name === 'TalentLayer email');

/**
 * @dev: Check whether access was granted
 */
if (protectedEmail) {
  const listGrantedAccess = await dataProtector.fetchGrantedAccess({
    protectedData: protectedEmail.address,
    authorizedApp: process.env.NEXT_PUBLIC_WEB3MAIL_APP_ADDRESS,
  });
}
```

If no access was granted, "listGrantedAccess.length" will = 0.

***

## 4. Integration in TalentLayer StarterKit

In order to enable web3mail sending within the TalentLayer ecosystem,&#x20;

### 4.1 FrontEnd - User authorization

//TODO

\=> Protecting email & Granting Access => Setting Notification preferences in Metadata

### 4.2 Backend - Notification sending

There are two kinds of notifications, punctual ones such as "Platform Marketing campaigns", which should be sent when required by a platform's owner; and regular automatic ones, such as "New Services listed matching my skills", which should be triggered by smart contract events. The Starter Kit provides an example of backend to handle these notifications using one API endpoint per Notification type. The automatic endpoints will be handled by a cron.

####

#### *Punctual*:

Notifications campaigns which will be sent on a non-programmed manner will be sent using the "sendEmail" function of the Web3Mail provider. The only checks to be done is whether the user granted access to his email to the platform & whether the user opted for this feature.

<figure><img src="/files/7vJHim8NJl0gTTo3dP38" alt=""><figcaption><p>Platform Marketing Notification workflow</p></figcaption></figure>

The StarterKit provides an API example to send Platform marketing notifications:

*Platform Marketing Campaigns*

```
@params: 
    emailSubject: string, 
    emailContent: string,
    signature: string,
    throwable? :boolean
    
/api/web3mail/platform-marketing
```

In order to secure this API endpoint and make sure that only the platform owner can call it, a signature is required upon sending emails, and sent as a param in the body of the http request.

{% hint style="info" %}
The message signed for this security check is the subject of the email
{% endhint %}

The API will first check whether the signature in the body of the request was sent by the ETH address of the platform owner.

In summary:

1 - Fetch all contacts using web3mail provider

2 - Check which of these contacts opted for this feature using a graph call to TalenyLayer graph to check user's metadata

3 - Provide signature to the API call

4 - Send the email to all Users checking these 2 conditions

####

#### *Automatic*:

The automatic notifications will require a script to regularly fetch data from the TalentLayer graph in order to check whether the watched event has occured. If we want to notify users when a new proposal was made for his service, we will need to regularly fetch the list of proposals for each service, and check whether a new one was made (checking this against the "updatedAt" graph field). If so, we will need to send an email to the service's owner.

In the StarterKit, the automatic notifications are handled by a cron, which will regularly fetch the data from the graph, and send the notifications if required. The used cron is the integrated Vercel cron system, but you can use any system you prefer.

<figure><img src="/files/vMEwHVcBG2ZblxLQJKBc" alt=""><figcaption><p>Vercel Cron Notification workflow for "New Proposal" notification</p></figcaption></figure>

***Example of a cron job: New Payment Notification***

In order to illustrate the workflow of an automatic notification, we will take the example of the "New Payment" notification, which will be sent when a payment is made for a service.

The notification will be sent using an API endpoint, which will be called by the cron job. In order for this Endpoint to be ONLY callable by the cron, a cron key will be set in the .env file, and the cron will have to provide this key as a request param to be authorized to call the API. This way, the endpoint will only be callable by the cron.

1 - **Setting the cron Pattern**

Cron patterns are set in the vercel.json file at the root of the project.

Each API endpoint is associated with an API endpoint which will be called by the cron. We want to check for new notifications every hour, so we need to set a cron pattern for the "fund-release" endpoint, which will be called every hour.

<figure><img src="/files/vyesmruddlLbvXucwt2M" alt=""><figcaption></figcaption></figure>

2 - **Secure API Endpoint with a key**

Each of these endpoints are secured by a secret key to ensure that only the cron can call them. If you set an environment variable called CRON\_SECRET="xxx", then Vercel will add this in the Authorization headers of each API called by the cron.

Each cron API call begins by a series of checks found in the "*prepareCronApi()*" function, one of them being a comparison between this header value and your environment variable. This way you can freely develop an open source project while keeping your your routes secure.

{% hint style="warning" %}
So remember to set the CRON\_SECRET environment variable prior to deployment.
{% endhint %}

3 - **Fetching Data from the Graph**

After this first check, the list of payments executed within the last hour will be fetched from the TL Graph:

```
{
payments(
orderBy: createdAt
where: {service_: {platform: "${id}"} , createdAt_gt: "${timestamp}"}
) {
    id
    amount
    createdAt
    paymentType
    ...
    }
}
```

The "createdAt\_gt" graph param will be used for this check. The "timestamp" variable will be set to the last cron execution minus one hour.

4 - **Integrating a retry factor**

With this system, if an email does not get sent for any technical reason, it will not be sent again. Therefore, we decided to integrate a **retry factor**, which will multiply the cron duration by the retry factor, so that the graph query will fetch data further in time. With a retry factor of 3, the cron will fetch data for the last 3 hours, and send the email if a payment was made during this period. This way, if an email was not sent, there will be 3 attempts.

5 - **Recording sent notifications in a database**

In case a retry factor was set and in order to avoid sending the same notification twice, we decided to record the sent notifications in a database. This way, each time the cron runs it can check in the database whether a detected notification was already sent, and avoid sending it again. In the Starter Kit we provide a solution using MongoDB, and persist sent notifications as such:

```typescript
/**
 * @dev: Web3MailNotification MongoDD schema
 * @param: id: string - The unique id of the notification
 * @param: sentAt: number - The timestamp at which the notification was sent
 * @param: type: EmailType enum - The type of the notification
 */
const web3MailSchema = new Schema({
  id: {
    type: String,
    required: true,
    unique: true,
  },
  sentAt: {
    type: Number,
    required: true,
    unique: false,
  },
  type: {
    type: String,
    enum: EmailType,
    required: true,
  },
});
```

For the "New Payment" notification, after each sent email, an entity will be recorded with its id following this format: `<"paymentId"-"EmailType.fundRelease">` This way unicity is granted.

#### Summary: Automatic notification workflow

Now all is technically ready for a cron to send the notification. The workflow is the following:

* Check all required API data (summed up in the "prepareCronApi()" function)
* Connect to the database
* Calculate cron duration & last execution timestamp
* Fetch new payments data from the graph
* Check whether a notification was already sent for each payment
* If notifications need to be sent, check whether the user opted for this feature in his metadata
* If yes, check whether the user granted access to his email to the platform
* If yes, send the notification using the Web3Mail provider
* If email was sent, record it in the database

#### List of available notifications & API endpoints

The available punctual notification is:

**Platform Marketing**

*Notification punctually sent by platforms for marketing purposes*

```
/api/web3mail/platform-marketing
```

The list of available automatic notifications & associated API endpoints are:

New Service

*Sent to users when a new service matching at least one of their skills was published*

```
/api/web3mail/new-service
```

Fund Release

*Sent to a seller when a fund release has be sent to his account, or to the buyer if a reimbursement has been sent to his account.*

```
/api/web3mail/fund-release
```

Proposal Validated

*Sent to a seller when one of their proposals has been validated*

```
/api/web3mail/proposal-validated
```

New Proposal

*Sent to a buyer when a new proposal has been made for one of their services*

```
/api/web3mail/new-proposal
```

New Review

*Sent to users when they receive a review*

```
/api/web3mail/review
```


# Messaging

TalentLayer integrates web3 messaging technology to allow a complete interoperable experience for users. The following guide will show you how we integrated XMTP into our indie frontend and how you can easily integrate it into your platform.


# Integrating XMTP

Guide on how to integrate XMTP messaging in your Dapp

When reiviewing a service or a proposal, it is important for users to be able to communicate with the author. TalentLayer provides an integration example of XMTP, a messaging system that allows users to communicate with each other using their ETH address. This guide will show you how to integrate the XMTP messaging system into your DAPP.

## Technical overview

XMTP is based on asymmetric encryption. When a user signs up using his ETH address, he is prompted to use his wallet signature to generate a pair of Private/Public keys. The private key is stored on chain, and requires the user's wallet signature to be retrieved; whereas the public key is stored publicly on chain. The public key is available to other XMTP users who wish to communicate with this user, and is used to encrypt messages before sending them. The private key is used to decrypt messages. The sender's public key is included in the message so that the recipient can verify the sender's identity.

## Workflow

When registering to XMTP, wallet signature is required to register the wallet as a User.

<div align="center"><figure><img src="/files/T25cl5bbHxkPs9UpzUwC" alt=""><figcaption><p>XMTP Identity creation window </p></figcaption></figure></div>

(This will be asked only once).

Then for each login, a wallet signature will be asked to decrypt this Private Key and create an XMTP client based on this key. Messages are stored on XMTP private nodes for now, but will be stored in a decentralized manner in a near future.

## SDK analysis

XMTP provides a [complete SDK](https://xmtp.org/docs/client-sdk/javascript/concepts/intro-to-sdk) to interact with the XMTP protocol.

***

### 1. Client

All calls to XMTP protocol are done through the XMTP [Client](https://xmtp.org/docs/client-sdk/javascript/reference/classes/Client): This Client is used to retrieve the keys of the user using his wallet signature, and use them to initialize a functional client that can be used to send and receive messages.

```typescript
import { Client } from '@xmtp/xmtp-js';

const keys = await Client.getKeys(signer, { env: 'production' });
const client = await Client.create(null, {
  env: 'production',
  privateKeyOverride: keys,
});
```

***

### 2. Conversations

The Client can be used to create a conversation with another user, and send messages to this conversation. A conversation is an object gathering all the contextual data related to a conversation between two users, as well as functions used to send and receive messages as well as WebSocket listeners, used to stream incoming messages.

```typescript
export declare class ConversationV2 {
  topic: string;
  keyMaterial: Uint8Array;
  context?: InvitationContext;
  private header;
  private client;
  peerAddress: string;
  constructor(client: Client, invitation: InvitationV1, header: SealedInvitationHeaderV1, peerAddress: string);
  static create(client: Client, invitation: InvitationV1, header: SealedInvitationHeaderV1): Promise<ConversationV2>;
  get createdAt(): Date;
  /**
   * Returns a list of all messages to/from the peerAddress
   */
  messages(opts?: ListMessagesOptions): Promise<DecodedMessage[]>;
  messagesPaginated(opts?: ListMessagesPaginatedOptions): AsyncGenerator<DecodedMessage[]>;
  /**
   * Returns a Stream of any new messages to/from the peerAddress
   */
  streamMessages(): Promise<Stream<DecodedMessage>>;
  /**
   * Send a message into the conversation
   */
  send(content: any, // eslint-disable-line @typescript-eslint/no-explicit-any
       options?: SendOptions): Promise<DecodedMessage>;
  get clientAddress(): string;
  private encodeMessage;
  decodeMessage(env: messageApi.Envelope): Promise<DecodedMessage>;
}

export declare type InvitationContext = {
    conversationId: string;
    metadata: {
      [k: string]: string;
  };
};
```

Example of retrieving a list of a user's conversation: (The client being already initialized with the user's private key)

```typescript
const listConversations = async (): Promise<Conversation[]> => {
    try {
      const conv: Conversation[] = (await client.conversations.list());
    } catch (e) {
      console.error(e);
    }
    return conv;
    };
```

***

### 3. Messages

The client object has a “conversations.list()” function, which once called, returns an array of “Conversation” objects. The conversation object has a “messages()” function, which once called, returns an array of “DecodedMessage” objects:

```typescript
export declare class DecodedMessage {
  id: string;
  messageVersion: 'v1' | 'v2';
  senderAddress: string;
  recipientAddress?: string;
  sent: Date;
  contentTopic: string;
  conversation: Conversation;
  contentType: ContentTypeId;
  content: any;
  error?: Error;
  constructor({ id, messageVersion, senderAddress, recipientAddress, conversation, contentType, contentTopic, content, sent, error, }: DecodedMessage);
  static fromV1Message(message: MessageV1, content: any, // eslint-disable-line @typescript-eslint/no-explicit-any
                       contentType: ContentTypeId, contentTopic: string, conversation: Conversation, error?: Error): DecodedMessage;
  static fromV2Message(message: MessageV2, content: any, // eslint-disable-line @typescript-eslint/no-explicit-any
                       contentType: ContentTypeId, contentTopic: string, conversation: Conversation, error?: Error): DecodedMessage;
}
```

The message itself is stored in the “content” attribute.

Example of retrieving a list of conversation's messages: (The client being already initialized with the user's private key)

```typescript
import {DecodedMessage} from "@xmtp/xmtp-js";

const listMessages = async (): Promise<DecodedMessage[]> => {
  try {
    const messages: DecodedMessage[] = await conversation.messages();
  } catch (e) {
    console.error(e);
  }
  return messages;
};
```

***

### 4. Sending messages

Sending a message is done through the Conversation object. The client object has a “conversations.newConversation(peerAddress, context?)” function, which once called, returns a “Conversation” object.

This method will either retrieve an existing conversation or create a new one if none is found.

The "context" object is optional, and can be used to add contextual data to the conversation, such as a conversation ID, or any other metadata. Further details on what TalentLayer implemented can be found in the SDK usage section.

If a conversation between two peers already exists, but the "newConversation()" function is called **with a different context**, a new conversation will be created.

Example of sending a message without context:

```typescript
const sendMessage = async (message: string): Promise<DecodedMessage> => {
  const conversation = await client.conversations.newConversation(peerAddress);
  return await conversation.send(message);
};
```

***

### 5. WebSocket listeners

There are 2 WebSocket listeners available in the SDK:

* “client.conversations.stream()” Available from the client object, which streams incoming conversations to the user
* “conversation.streamMessages()” Specific for each conversation, which streams incoming messages to the user

Example of using the client conversation stream:

```typescript
import {Conversation} from "@xmtp/xmtp-js";

const streamConversations = async () => {
  const stream: Stream<Conversation> | undefined = await client.conversations.stream();
  if (!stream) return;
  for await (const conversation: Conversation of stream) {
  // Your logic here
  }
};
```

Example of using the message stream:

```typescript
const streamMessages = async (conversation: Conversation) => {
const stream: Stream<DecodedMessage> | undefined = await conversation.streamMessages();
  if (!stream) return;
  for await (const message: DecodedMessage of stream) {
  // Your logic here
  }
};
```

***

## SDK Usage in TalentLayer Indie Frontend

TalentLayer Indie Frontend provides an example implementation of the XMTP protocol in a marketplace. This document will explain how the XMTP SDK was used in the Indie Frontend's implementation, and how XMTP was integrated in the workflow.

### 1. Getting started - Workflow

The idea is to enable TalentLayer users to send messages to each other, and to be able to chat with each other **after they published either a service or a proposal**. Since Users cannot be messaged if they are not registered to XMTP, the first step for a user wishing to publish a service or a proposal is to register to XMTP.

#### 1.1. Registering to XMTP

This box will appear on the user's profile page if they are not registered to XMTP:

<figure><img src="/files/7YJnoG9FxYfzKWcxTyuR" alt=""><figcaption><p>XMTP registration window</p></figcaption></figure>

After clicking on the button, a modal will appear, asking the user to sign a message with their wallet, which will register them to XMTP.

#### 1.2. Contacting a user

Contacting a user is done by clicking on the "Contact" button either on the service or proposal detail page:

Example on a service detail page:

<figure><img src="/files/8FH1xJqNKfPtIXt2mrNN" alt=""><figcaption></figcaption></figure>

Clicking on this button will open the messaging page, with the existing or new conversation with the user already opened. This action will prompt the user to register to XMTP if they are not yet registered.

***

### 2. File Organization & code

TalentLayer has implemented 2 messaging protocols in the Indie Frontend: XMTP and PUSH. Some React components are common to both, and can be found in the "messaging" folder. All XMTP-related components can be found in the "mesaging/XMTP" folder.

<div align="left"><figure><img src="/files/zuSHpa0XTofKadpI8iID" alt=""><figcaption><p>Messaging file organization</p></figcaption></figure></div>

The messaging page's code is located in the "pages/XmtpMessaging.tsx" file. This is the entry point to the XMTP messaging app. It is available through the following routes:

```tsx
  <Route path='/messaging' element={<XmtpMessaging />} />
  <Route path='/messaging/:address' element={<XmtpMessaging />} />
```

The first route leading to the messaging page is the default route, and will display the list of the connected user's conversations. The second route is used to display a specific conversation, and is used when clicking on a conversation in the list.

#### 2.1. Environment variables

The XMTP SDK requires 2 environment variables to be set in order to work properly. These variables are set in the .env file.

```dotenv
# MESSAGING
// 'xmtp' or 'push'
VITE_MESSENGING_TECH='xmtp'
// 'dev' or 'production'
VITE_MESSENGING_ENV='production'
```

Input 'xmtp' & "dev' for testnets implementation | 'xmtp' & 'production' for your live Dapp.

#### 2.2. General messaging Context

As mentioned before, the workflow starts by checking whether the user has an XMTP account. If not, he will be prompted to register to XMTP. Finally the user will be redirected to the messaging page, with an open conversation with the recipient.

This requires that a few actions must happen as pre-requisites:

* The user must have a TalentLayer ID.
* The user must be registered to XMTP. These actions happen outside of the messaging page; therefore all the functions necessary for these actions are centralized in a general Messaging React context provider: "MessagingContext.tsx". This context handles both XMTP & Push protocols following actions:
* Checks if the user has an XMTP account. This is done by checking the 'userExists" variable in the XmtpContext.tsx file (detailed in XmtpContext section below)

```typescript
userExists = (): boolean
```

* Register the user to XMTP if needed

```typescript
handleRegisterToMessaging = async (): Promise<void>
```

* Redirect the sender to the messaging page, with an open conversation with the recipient

```typescript
handleMessageUser = async (userAddress: string): Promise<void>
```

Example of redirection to an open conversation with a user:&#x20;

<figure><img src="/files/3jU1YZ59LXNM3V74TiHH" alt=""><figcaption><p>Open XMTP Conversation</p></figcaption></figure>

#### 2.3. XMTP messaging Context

**Content**

The XMTP messaging context is used to handle all the XMTP-related actions which happen in the messaging page, such as retrieving conversations & messages, sending messages, etc. It can be found in the "messaging/context/XmtpContext.tsx" file.

The following properties are available in this context:

```typescript
interface IProviderProps {
  client: Client | undefined;
  initClient: ((wallet: Signer) => Promise<void>) | undefined;
  loadingConversations: boolean;
  loadingMessages: boolean;
  conversations: Map<string, Conversation>;
  conversationMessages: Map<string, XmtpChatMessage[]>;
  userExists: boolean;
  disconnect: (() => void) | undefined;
}
```

At the initialization of the context, as soon as the user connects his wallet, a listener is set up to check if the user has an XMTP account:

```typescript
useEffect(() => {
    const checkUserExistence = async (): Promise<void> => {
      if (signer) {
        const userExists = await Client.canMessage(walletAddress as string, {
          env: import.meta.env.VITE_MESSENGING_ENV,
        });
        setProviderState({ ...providerState, userExists, initClient });
      }
    };
    checkUserExistence();
  }, [signer]);
```

If the user does not have an XMTP account, the "userExists" variable will be set to false. This variable is called in the MessagingContext.tsx file, to check if the user needs to be registered to XMTP.

**Client Initialisation**

The first action to be done in the messaging page is to initialize the XMTP client. This is done using the "initClient" function, which takes the user's wallet "Signer" object as a parameter. After this function is called, the user will decrypt (or create) his private key using his wallet signature, and the "client" object will be set in the context, with the connected user's private key. This initialized client instance will be used to retrieve conversations, messages, etc.

```typescript
const initClient = async (wallet: Signer) => {
//(...)
}
```

**Conversation & messages fetching**

After the client is initialized, the messaging context will load all the user's conversations and messages. This is done through the following function:

```typescript
const listConversations = async (): Promise<void> => {
//(...)
}
```

Conversations will be accessible through the "conversations" variable, and messages through the "conversationMessages" variable after this step. These variables are mappings using the recipient's ETH address as a key, and the messages or conversations objects as values.

**XmtpChatMessage** During the fetching of the conversation's messages, the messages are converted to a custom type: "XmtpChatMessage", which is lighter than the "DecodedMessage" type from the XMTP SDK. This action is done through the function "buildChatMessage" in the "messaging/utils.ts" file. This message conversion is also done in the message & conversation listeners (see below).

#### 2.4. XMTP hooks

Three hooks are provided to help you use the XMTP messaging context in your components:

**UseSendMessage()**

This hook is used to send a message to a user. It is used in the main messaging page, and sets up a "send" function with the selected recipient when the user clicks on its conversation on the left sidebar. It will first instantiate a new conversation, then use its "send()" function. The hook takes the following parameters:

```typescript
 const sendMessage = async (message: string): Promise<DecodedMessage> => {
    if (!client || !peerAddress || !peerUser?.id || !senderId) {
      throw new Error('Message sending failed');
    }

    const conversationId = buildConversationId(senderId, peerUser.id);

    const context: InvitationContext = {
      conversationId: conversationId,
      metadata: { ['domain']: 'TalentLayer' },
    };
    const conversation = await client.conversations.newConversation(peerAddress, context);

    if (!conversation) throw new Error('Conversation not found');
    return await conversation.send(message);
  };

    export const CONVERSATION_PREFIX = 'talentLayer/dm';
    
    export const buildConversationId = (talentLayerId1: string, talentLayerId2: string) => {
      const profileIdAParsed = parseInt(talentLayerId1, 16);
      const profileIdBParsed = parseInt(talentLayerId2, 16);
    
      return profileIdAParsed < profileIdBParsed
        ? `${CONVERSATION_PREFIX}/${talentLayerId1}-${talentLayerId2}`
        : `${CONVERSATION_PREFIX}/${talentLayerId2}-${talentLayerId1}`;
    };
```

Notice the "buildConversationId" function. TalentLayer decided to set a context to the conversations initiated on TalentLayer protocol. There are several reasons for this decision, the first being to enable filtering out all XMTP conversations which are not TalentLayer-related. The second reason is to enable the user to have multiple conversations with the same user, but on different topics. this can be achieved using the "metadata" field in the context. (An example 'domain' field is shown in the code snippet above)

The way the conversationId is built [**follows the pattern which Lens protocol used**](https://xmtp.org/docs/client-sdk/javascript/tutorials/build-key-xmtp-chat-features-in-a-lens-app#build-the-lens-dm-conversation-id). This way the domain name can be clearly visible in the XMTP general chat app:

<figure><img src="/files/dM8n4sgDLWwaENoeH1lw" alt=""><figcaption><p>Conversation domain ids visible on <a href="https://xmtp.chat/">xmtp.chat</a> </p></figcaption></figure>

TalentLayer decided to filter out all conversations which are not TalentLayer-related, by filtering out all conversations which id's do not match the TalentLayer pattern (see "listConversations" function in XMTPContext.tsx).

**UseStreamConversations()**

This hook sets up a conversation listener to retrieve all the new conversations which are initiated by users trying to contact the connected user. It takes no parameters and is called in the main messaging page.

**UseStreamMessages()**

This hook sets up a message listener to retrieve all the new incoming messages of a conversation. It is used in the "MessageList.tsx" component, and is only active on an active conversation.

#### 2.5. XMTP messaging components

The main messaging page is located in the "pages/XmtpMessaging.tsx" file. The page is built using all the components located in the "messaging/xmtp/components" folder.

It's important to activate the conversation listener on this page to retrieve all the new conversations initiated by other users.

Each time a user selects a conversation, the conversation messages are fetched, the "MessageList" component is rendered and the "useSendMessage" hook is called to set up a "send" function to send messages to the selected user.&#x20;

The "MessageList" component is also responsible for activating the message listener on the selected conversation, since new incoming messages are only listened for the active conversation.

**Components**

The main messaging page is built using the following components:

* **CardHeader**: simple component which displays the address of the recipient user.&#x20;
* **ConversationList**: displays all the user's conversations, and is used to select a conversation. It is also responsible for activating the conversation listener.&#x20;
* **MessageList**: displays all the messages of a conversation, and is responsible for activating the message listener. This component not rendered message when no conversation is selected.&#x20;
* **MessageComposer:** displays a text input to send messages to the selected user.


# Standards

In order to be as composable and modular as possible, some components of TalentLayer follow some standards defined in the ecosystem.

We have summarized some of the standards here for a quick and easy understanding.


# ERC-792: Arbitration standard

## About The Standard

{% embed url="<https://github.com/ethereum/EIPs/issues/792>" %}

Defines a contracts standard for arbitration. Specifically, defines two types of contracts:

* **`Arbitrable` contracts:** where disputes can arise (e.g. an escrow contract)
* **`Arbitrator` contracts:** used to resolve disputes.

Every `Arbitrable` contract can be adjudicated by every `Arbitrator` contract The standard defines the way that `Arbitrable` and `Arbitrator` contracts should interact with each other.

Using two contracts allows separation between the ruling and its enforcement. In this way, an arbitrable app can easily switch from one arbitration service to another one with no breaking changes.

## Arbitrator

{% embed url="<https://github.com/kleros/erc-792/blob/master/contracts/IArbitrator.sol>" %}

**Functionalities:**

* allow dispute creation (`createDispute` function, must be called by the `Arbitrable` contract)
* allow to appeal a ruling (`appeal` function, must be called by the `Arbitrable` contract)
* allow giving rulings (by calling the `rule` function of the `Arbitrable` contract)

**Arbitration costs:**

The creation of a dispute has a cost to be paid to the arbitrator. Appeals also have a cost.

To create a dispute, both parties should pay the arbitration fee. Whoever wins the dispute should get the funds and should get reimbursed for the arbitration fee.

*E.g.: if the arbitration cost is C, both parties will pay C to create a dispute summing up to a total of 2C paid in fees. Half of the fees (C) will go to the arbitrator and the other half (C) will be reimbursed to the winner of the dispute*

**Appeals:**

It is up to the arbitrator to decide whether or not to allow appeals:

* no appeals: the ruling given by the arbitrator should be immediatly final
* with appeals: after a ruling is given an appeal period opens, in which parties can appeal the current decision.

Also, it is up to the arbitrator to define how to handle appeal periods and what happens when an appeal is received.

*An example could be: after a decision is taken the parties have some time to appeal the decision. If no one appeals within this timeframe then the decision becomes final, otherwise the arbitrator has to take a decision again (which can be the same one as before) until no one appeals. Appealing has a cost so parties won't do it forever if that doesn't change the arbitrator's mind.*

However, arbitrators should only give a final ruling (call `rule` on the `Arbitrable` contract) when all appeals are exhausted.

**Dispute status:**

* `Waiting`: the dispute is this status when it gets created
* `Appealable`: the dispute got a *ruling* and the `Arbitrator` allows to *appeal* it.
* `Solved`: the dispute got a ***ruling*** and the **ruling** is final. This doesn’t imply that the ruling has been enforced, it just means that the decision on the dispute is final and to be executed.
  * if a dispute is not appealed within the appealing time period, it becomes `Solved`

## Arbitrable

{% embed url="<https://github.com/kleros/erc-792/blob/master/contracts/IArbitrable.sol>" %}

**Functionalities**

* determines in which cases a dispute occurs, defining logic to create disputes (by calling `createDispute` on the `Arbitrator` contract and paying the required fee)
* determines in which cases appeal is possible, defining logic to appeal disputes (by calling `appeal` on the `Arbitrator` contract and paying the required fee)
  * Note: it just defines in which situation it would be possible to appeal a dispute, based on the context of the arbitrable dApp. It still depends on the arbitrator to decide whether to allow appeals or not.
* enforces decisions given by the `Arbitrator` contract (`rule` function, must be called by the `Arbitrator` contract)

#### Workflow

* The `Arbitrable` contract calls `createDispute` on the `Arbitrator` contract to create a dispute.
* The `Arbitrator` contract gives a ruling. The appeal period opens.
* Appeal is done through the `Arbitrable` contract calling `appeal` on the `Arbitrator` contract
* When the appeal period is over the `Arbitrator` contract gives a final ruling, calling `rule` on the `Arbitrable` contract
* The `Arbitrable` contract enforces the ruling


# ERC-1497: Evidence Standard

## About The Standard

{% embed url="<https://github.com/ethereum/EIPs/issues/1497>" %}

Defines a standard for submitting evidence in dispute resolution. Evidence is not stored on-chain due to gas considerations. Instead it is represented with off-chain resources, standardized JSON objects that can be hosted anywhere.

Evidence are submitted and looked up via smart contract event logs. We can leverage the immutability and availability of the blockchain to create a permanent log of submission that any interface can look up and use to access the evidence JSON.

It defines a standard way for dApps that are part of the dispute resolution process to share context and information:

* for Arbitrable dApps: gives a way to provide the details of disputes to the Arbitrator.
* for Arbitrators: gives a way to see context and evidence of disputes that need to be ruled.

### `Evidence`

Material provided by each party of a dispute in order to support their viewpoint, submit extra information for arbitrators and give reasons why they believe they are right.

It’s extra information that parties can submit for arbitrators that tries to prove that the dispute should be ruled in favor.

E.g.: emails, screenshots, contracts, testimony

**How it’s used:**

* `Arbitrable` contracts emit an event that contains a reference to an evidence JSON file when new evidence is submitted.

### `MetaEvidence`

Gives context to a dispute so that arbitrators are able to accurately and fairly evaluate it. Used to convey general information about the dispute to the arbitrator.

E.g.: the agreement, parties involved, what has to be decided (the question the arbitrators have to answer), the human readable meanings of rulings and specific modes of display for evidence.

**How it’s used:**

* Each dispute includes only one piece of `MetaEvidence`, however, the same `MetaEvidence` can be used for multiple disputes
* It is up to the `Arbitrable`contract to determine how `MetaEvidence` is submitted and assigned to a dispute
* In some use cases, `MetaEvidence` is all that the `Arbitrator` will need in order to make a ruling.
* `MetaEvidence`has to be created before a dispute can arise. It should be created at the same time as the agreement so that it can be impartial.


# How-To Guides

In this section you will find "how-to" guides for integrating your application frontend with the various TalentLayer Core smart contracts and our subgraphs! This section is great for teams that are building custom frontends and looking to learn how to configure how data is written and displayed!&#x20;

{% hint style="info" %}
**Keep Them Coming!** We have additional guides in development. Want a specific guide now? Get in touch!
{% endhint %}


# How to implement minting TalentLayer IDs?

Minting a TalentLayer ID is the first step that your users will need to do when registering for an account on your platform.

## 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.&#x20;

## ① Create a Form for Choosing a TalentLayer ID Handle

*The user will create his handle.*

```javascript
// Imports can be find in the full example at the end of the tutorial

interface IFormValues {
  handle: string;
}

const initialValues: IFormValues = {
  handle: '',
};

function TalentLayerIdForm() {
  const validationSchema = Yup.object().shape({
    handle: Yup.string()
      .min(2)
      .max(10)
      .when('isConnected', {
        is: account && account.isConnected,
        then: schema => schema.required('handle is required'),
      }),
  });

  
  const onSubmit = async (
    submittedValues: IFormValues,
    { setSubmitting }: { setSubmitting: (isSubmitting: boolean) => void },
  ) => {
    // We will handle submit here on the next step
  };

  return (
    <Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
      {({ isSubmitting }) => (
        <Form>
            <Field
                type='text'
                placeholder='Choose your handle'
                id='handle'
                name='handle'
                required
            />
            <button type='submit'>Submit</button>
        </Form>
      )}
    </Formik>
  );
}

export default TalentLayerIdForm;
```

**Bonus:** You can check if an handle is already taken and include it in the form validation by using a subgraph query.

```javascript
export const getUserByHandle = (handle: string): Promise<any> => {
  const query = `
  {
    users(where: {handle_contains_nocase: "${handle}"}, first: 1) {
      id
    }
  }
  `;
  return processRequest(query);
};
```

## ②  Handle Submit and Post to the Blockchain

*On submit, we create a new transaction and call the mint function of the TalentLayerId contract.*

```javascript
const onSubmit = async (
    submittedValues: IFormValues,
    { setSubmitting }: { setSubmitting: (isSubmitting: boolean) => void },
  ) => {
    try {
        const contract = new ethers.Contract(
            config.contracts.talentLayerId,
            TalentLayerID.abi,
            signer,
        );

        const tx = await contract.mint('1', submittedValues.handle);
        setSubmitting(false);
    } catch (error) {
        console.error(error);
    }
  };
```

## ③  Get Your New User Information With Subgraph API

*After the transaction succeeds, the new user will be viewable via our subgraph api.*

```javascript
export const getUserByAddress = (address: string): Promise<any> => {
  const query = `
    {
      users(where: {address: "${address.toLocaleLowerCase()}"}, first: 1) {
        id
        address
        handle
      }
    }
    `;
  return processRequest(query);
};
```

## See the Full Code Implemented on Our Demo DAPP

* Form and submit: <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Form/TalentLayerIdForm.tsx>
* processRequest utils function: <https://github.com/TalentLayer-Labs/indie-frontend/blob/00e882431f3ad820374841070b7f7fe1a8053c44/src/utils/graphql.ts>


# How to implement the service creation?

In the Post Job section, users can create a service. We will use and detail the function createService

## 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.&#x20;

## ① Create a Form for the service creation

*The user will create a service (*[*ServiceForm component*](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Form/ServiceForm.tsx)*)*

```tsx
// The interface for the inupt field of your form
interface IFormValues {
  title: string;
  about: string;
  keywords: string;
  rateToken: string;
  rateAmount: number;
}

// As we using Formik we use the validationSchema to check all the input value
const validationSchema = Yup.object({
    title: Yup.string().required('Please provide a title for your service'),
    about: Yup.string().required('Please provide a description of your service'),
    keywords: Yup.string().required('Please provide keywords for your service'),
    rateToken: Yup.string().required('Please select a payment token'),
    rateAmount: Yup.number()
      .required('Please provide an amount for your service')
      .when('rateToken', {
        is: (rateToken: string) => rateToken !== '',
        then: schema =>
          schema.moreThan(
            selectedToken
              ? FixedNumber.from(
                  ethers.utils.formatUnits(
                    selectedToken?.minimumTransactionAmount as BigNumberish,
                    selectedToken?.decimals,
                  ),
                ).toUnsafeFloat()
              : 0,
            `Amount must be greater than ${
              selectedToken
                ? FixedNumber.from(
                    ethers.utils.formatUnits(
                      selectedToken?.minimumTransactionAmount as BigNumberish,
                      selectedToken?.decimals,
                    ),
                  ).toUnsafeFloat()
                : 0
            }`,
          ),
      }),
  });

```

Let's create our form&#x20;

```tsx
return (
  <Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
    {({ isSubmitting, setFieldValue }) => (
      <Form>
        <div className='grid grid-cols-1 gap-6 border border-gray-200 rounded-md p-8'>
          <label className='block'>
            <span className='text-gray-700'>Title</span>
            <Field
              type='text'
              id='title'
              name='title'
              className='mt-1 mb-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50'
              placeholder=''
            />
            <span className='text-red-500'>
              <ErrorMessage name='title' />
            </span>
          </label>

          <label className='block'>
            <span className='text-gray-700'>About</span>
            <Field
              as='textarea'
              id='about'
              name='about'
              className='mt-1 mb-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50'
              placeholder=''
            />
            <span className='text-red-500'>
              <ErrorMessage name='about' />
            </span>
          </label>
          ....................
          ....................
          
        /*
         Formik will catch the submit button and trigger the onSubmit function
        in the next section
        */ 
        <SubmitButton isSubmitting={isSubmitting} label='Post' />
  );

```

## ②  Handle Submit and Post the service

*On submit, we create a new transaction and call the createService for that we will need our contract instance and all the parameter including the **CID** (offchain data stored on IPFS)*

```tsx
const onSubmit = async (
    values: IFormValues,
    {
      setSubmitting,
      resetForm,
    }: { setSubmitting: (isSubmitting: boolean) => void; resetForm: () => void },
  ) => {
  
  /*.............................
   we proceed to different check and date formatting 
  .............................*/
  
  /*
   as we using the defender API to manage signature we need to get it
   as it's a parameter of the service creation function 
   */

  // Get platform signature
  const signature = await getServiceSignature({ profileId: Number(user?.id), cid });

  
  // We need to create the CID to post some data off-chain
   const cid = await postToIPFS(
        JSON.stringify({
          title: values.title,
          about: values.about,
          keywords: values.keywords,
          role: 'buyer',
          rateToken: values.rateToken,
          rateAmount: parsedRateAmountString,
      }),
    );
    
  // We need the contract instance to call the function
  const contract = new ethers.Contract(
    config.contracts.serviceRegistry,
    ServiceRegistry.abi,
    signer,
  );
  
  
  // we call the createService function
  const tx = await contract.createService(
      user?.id,
      process.env.NEXT_PUBLIC_PLATFORM_ID,
      cid,
      signature,
    );

/*............................. */
    
```

## ③  Get the Proposal by User  With Subgraph API

*Here is an example of a Graph Query you can use to get the service by id*

```graphql
export const getServiceById = (id: string): Promise<any> => {
  const query = `
    {
      service(id: "${id}") {
        ${serviceQueryFields}
        description {
          ${serviceDescriptionQueryFields}
        }
      }
    }
    `;
  return processRequest(query);
};
```

## See the Full Code Implemented on Our Demo DAPP

* Form and submit: <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Form/ProposalForm.tsx>
* Services query: <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/queries/services.ts>
* Contract: <https://github.com/TalentLayer/talentlayer-contracts/blob/main/contracts/TalentLayerService.sol>
* processRequest utils function:

**GraphQL** :  <https://github.com/TalentLayer-Labs/indie-frontend/blob/00e882431f3ad820374841070b7f7fe1a8053c44/src/utils/graphql.ts>

**IPFS :** <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/utils/ipfs.ts>


# How to implement the proposal creation?

In the Find Job section, users can create a proposal for the job they want to apply for. We will use and detail the use of function createProposal or updateProposal

## 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.&#x20;

## ① Create a Form for the proposal creation

*The user will create a proposal for a job* [*(ProposalForm component)*](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Form/ProposalForm.tsx)

```tsx
// The interface for the inupt field of your form
interface IFormValues {
  about: string;
  rateToken: string;
  rateAmount: number;
  expirationDate: number;
  videoUrl: string;
}

// As we using Formik we use the validationSchema to check all the input value.
const validationSchema = Yup.object({
  about: Yup.string().required('Please provide a description of your service'),
  rateToken: Yup.string().required('Please select a payment token'),
  rateAmount: Yup.string().required('Please provide an amount for your service'),
  expirationDate: Yup.number().integer().required('Please provide an expiration date'),
});

```

Let's create our form&#x20;

```tsx
// We set up our input field to get the data to create our IPFS CID

return (
  <Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
        {({ isSubmitting }) => (
          <Form>
            <h2 className='mb-2 text-gray-900 font-bold'>For the job:</h2>
            <ServiceItem service={service} />
  
            <h2 className=' mt-8 mb-2 text-gray-900 font-bold'>Describe your proposal in details:</h2>
            <div className='grid grid-cols-1 gap-6 border border-gray-200 rounded-md p-8'>
              <label className='block'>
                <span className='text-gray-700'>about</span>
                <Field
                  as='textarea'
                  id='about'
                  rows={8}
                  name='about'
                  className='mt-1 mb-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50'
                  placeholder=''
                />
                <span className='text-red-500'>
                  <ErrorMessage name='about' />
                </span>
              </label>
              ....................
              ....................
              
          /*
           Formik will catch the submit button and trigger the onSubmit function
          in the next section
          */ 
          <SubmitButton isSubmitting={isSubmitting} label='Post' />

  );

```

## ②  Handle Submit and Post the proposal

*On submit, we create a new transaction and call the createProposal or the updateProposal for that we will need our contract instance and all the parameter including the **CID** (data stored on IPFS)*

<pre class="language-tsx"><code class="lang-tsx"><strong>const onSubmit = async (
</strong>    values: IFormValues,
    {
      setSubmitting,
      resetForm,
    }: { setSubmitting: (isSubmitting: boolean) => void; resetForm: () => void },
  ) => {
  
  /*.............................
   we proceed to different check and date formatting 
  .............................*/
  
  /*
   as we using the defender API to manage platform signature we need to get it
   as it's a parameter of the createProposal function 
   */
  const signature = await getProposalSignature({
        profileId: Number(user.id),
        cid,
        serviceId: Number(service.id),
  });

  // We need to create the CID to post some data off-chain
   const cid = await postToIPFS(
      JSON.stringify({
        about: values.about,
        video_url: values.videoUrl,
      }),
    );
    
  // We need the contract instance to call the function
  const contract = new ethers.Contract(
    config.contracts.serviceRegistry, // contract address
    ServiceRegistry.abi, // the contract ABI
    signer, // the signer
  );
  
  /*
   If a proposal existingProposal exist we call updateProposal otherwise we call 
    createProposal
  */
<strong>  const tx = existingProposal
</strong>    ? await contract.updateProposal(
        user.id,
        service.id,
        values.rateToken,
        parsedRateAmountString,
        cid,
        convertExpirationDateString,
      )
    : await contract.createProposal(
        user.id,
        service.id,
        values.rateToken,
        parsedRateAmountString,
        process.env.NEXT_PUBLIC_PLATFORM_ID,
        cid,
        convertExpirationDateString,
        signature,
      );

  
  
</code></pre>

## ③  Get the Proposal by User  With Subgraph API

*Here is an example of a Graph Query you can use to get the proposal by user*

```graphql
export const getAllProposalsByUser = (id: string): Promise<any> => {
  const query = `
      {
        proposals(where: {seller: "${id}", status: "Pending"}) {
          id
          rateAmount
          rateToken {
            address
            decimals
            name
            symbol
          }
          status
          cid
          createdAt
          seller {
            id
            handle
          }
          service {
            id
            cid
            createdAt
            buyer {
              id
              handle
            }
          }
          description {
            id
            about
            expectedHours
            startDate
            video_url
          }
          expirationDate
        }
      }
    `;
  return processRequest(query);
};gra
```

More proposal query [**here**](https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/queries/proposals.ts)&#x20;

## See the Full Code Implemented on Our Demo DAPP

* Form and submit: <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Form/ProposalForm.tsx>
* Proposal queries : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/queries/proposals.ts>
* Contract: <https://github.com/TalentLayer/talentlayer-contracts/blob/main/contracts/TalentLayerService.sol>
* processRequest utils function:

**GraphQL** :  <https://github.com/TalentLayer-Labs/indie-frontend/blob/00e882431f3ad820374841070b7f7fe1a8053c44/src/utils/graphql.ts>

**IPFS :** <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/utils/ipfs.ts>


# 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.&#x20;

## ① 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.

```tsx
<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`

```tsx
<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-`ValidateProposalModal`**&#x63;omponent : 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

```tsx
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

```tsx
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

```tsx
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&#x20;

* Check the ERC-20 token balance

```tsx
const balance = await ERC20Token.balanceOf(signer.getAddress());
  if (balance.lt(value)) {
    throw new Error('Insufficient balance');
}
```

* Check the token allowance

```tsx
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

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

## See the Full Code Implemented on Our Demo DAPP

* **ServiceDetail** : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/ServiceDetail.tsx>
* ProposalItem : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/ProposalItem.tsx>
* ValidateProposalModal : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Modal/ValidateProposalModal.tsx>
* ValidateProposal : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/contracts/acceptProposal.tsx>


# Delegation

## Intro to Delegation

Delegation enables platforms to take actions and cover costs of users interacting with their UI. This is possible thanks to meta-transactions - a standard way for a third-parties to send another user’s transactions on the original user's behalf.&#x20;

## Use Case of Delegation

The goal of the delegation system is to allow users to give the right to third party accounts (called **delegates**) to perform actions on the protocol on their behalf.

The main use cases can be:

* **covering user fees**: platforms can perform actions on behalf of the users, covering their fees
* **account abstraction:** platforms can abstract away the complexity of having to approve transactions for their users by taking those actions for them
* **plugins**: users can delegate actions to plugins for automation, scheduling, smoother experience, etc..

This system can also achieve a much better UX for users. Once they have a TalentLayer ID, they only need to call one function to link a new delegate to their profile.

The delegate can now call functions for the user, who won’t have to send transactions or sign messages anymore.

## Limits of Delegation

Delegates will only be able to perform actions that don’t touch user funds. Operations that involve user funds will have to be done directly by the user, both for security and technical reasons.

These operations are:

* accept proposal making a deposit in the escrow
* pay arbitration fee to raise dispute

The delegates are linked to a profile, so this means that they cannot be used to mint a TalentLayer id for a user.

## Delegation covering

You can, when the delegation is activated, cover fees for the actions below

* Mint a TalentLayerId for a user
* Update profile data
* Create a service
* Create and update a proposal
* Release and reimburse
* Create a review

## TalentLayer's Implementation

We have implemented a delegation system in contracts that the user interacts with:

* TalentLayerID
* ServiceRegistry
* TalentLayerEscrow
* TalentLayerReview

To support this, we've enabled:

* tracking the list of delegates for each TalentLayer ID
  * this state will be stored in `TalentLayerID` and the other contracts will read from it
* allowing users to add and remove a delegate
* for each function that supports delegation: enabling checking whether the user is either the owner or a delegate of the involved profile


# Meta Transaction

Here will provides consultation and recommends several vetted relayer solutions, including OpenGSN, Biconomy, and OZ Defender

## Support for Gasless Meta-transaction

Our contracts are EIP-2771 compatible so they can support meta-transactions. Our implementation follows the standard provided by `@opengsn/contracts`.

### Relayers

In order to enable gassless transactions, platforms building on TalentLayer must choose a relayer and integrate it into their frontend. Platforms are also responsible for managing whitelisting, since we advise only covering gas costs for a subset of trusted users.

{% hint style="warning" %}
Need help choosing a relayer? We're happy to chat about it! Reach out to us on the "contact" page of the documentation.
{% endhint %}

Any relayer solutions which are EIP-2771 compatible work with TalentLayer's contracts.

Our team has vetted a few relayer solutions that we recommend supporting:

* **OpenGSN (V2 or V3)**
  * Implementation:

    [Open GSN Implementation](https://www.notion.so/Open-GSN-Implementation-ba1cd34a59914ffcaba0537d48f3a130)
  * decentralized, network of relayers, not many but anyone can run their own
  * need to develop Paymaster contract (probably one for each platform which will have a whitelist of users that can be sponsored)
  * expensive, multiple extra things done on chain
* **Biconomy**
  * Implementation:

    [Biconomy Implementation](https://www.notion.so/Biconomy-Implementation-1d0084455fa64df5861b4b4c9be09a83)
  * semi-centralized, network of relayers, many are Biconomy’s but other people can join?
  * no need to develop any new contracts
* **OZ Defender**
  * Implementation:

    [OZ Defender Implementation](https://www.notion.so/OZ-Defender-Implementation-75bec36238184eedaf576aa0bd75cf54)
  * centralized, single relayer, they have the private key
  * no need to develop any new contracts
  * need to spin up server or use AutoTask


# Delegate System

In this section we will present how the delegation system works, how you can activate it and use it

Please don't hesitate to reach out to us if you require additional information.


# Setting

Here we will cover the delegation activation setting

## Set the .env delegation variable

```shell
# ========== DELEGATION ==============
## Active the delegate feature for service / proposal / release / review (the proposal validation won't be delegate)
NEXT_PUBLIC_ACTIVE_DELEGATE=true

## Active the delegate feature for minting ID - will call a backend api and call the smartcontract function mintForAddress
NEXT_PUBLIC_ACTIVE_DELEGATE_MINT=true

## This seed phrase is only used for delegate purpose
NEXT_PRIVATE_DELEGATE_SEED_PHRASE="add you seed phrase here only for delegate purpose"

## Public address
NEXT_PUBLIC_DELEGATE_ADDRESS="0xaddyouraddresshereonlyfordelegatepurpose"
```

```sh
NEXT_PUBLIC_ACTIVE_DELEGATE=true
```

The `NEXT_PUBLIC_ACTIVE_DELEGATE` variable will display the activation button in the settings dashboard, allowing users to activate and deactivate the delegation feature.

```sh
NEXT_PUBLIC_ACTIVE_DELEGATE_MINT=true
```

The `NEXT_PUBLIC_ACTIVE_DELEGATE_MINT` variable will enable users to mint their TalentLayerID without incurring any fees.

```sh
NEXT_PRIVATE_DELEGATE_SEED_PHRASE="add you seed phrase here only for delegate purpose"
```

The `NEXT_PRIVATE_DELEGATE_SEED_PHRASE` is required to pay the fees for the user's actions. The wallet should be used exclusively for delegation purposes.

```sh
NEXT_PUBLIC_DELEGATE_ADDRESS="0xaddyouraddresshereonlyfordelegatepurpose"
```

The NEXT\_PUBLIC\_DELEGATE\_ADDRESS is the public address used for transactions.


# User workflow

In this section, we will see how the user can activate or deactivate the delegation feature on their dashboard.

## 1 - The component

Feel free to add the activation wherever you want. Currently, it is added in a popup modal, but it will soon be moved to a dedicated setting section in the user dashboard.

```tsx
import { Provider } from '@wagmi/core';
import { ethers } from 'ethers';
import { createMultiStepsTransactionToast, showErrorTransactionToast } from '../utils/toast';

export const toggleDelegation = async (
  user: string,
  DelegateAddress: string,
  provider: Provider,
  validateState: boolean,
  contract: ethers.Contract,
): Promise<void> => {
  try {
    let tx: ethers.providers.TransactionResponse;
    let toastMessages;
    if (validateState === true) {
      tx = await contract.addDelegate(user, DelegateAddress);
      toastMessages = {
        pending: 'Submitting the delegation...',
        success: 'Congrats! the delegation is active',
        error: 'An error occurred while delegation process',
      };
    } else {
      tx = await contract.removeDelegate(user, DelegateAddress);
      toastMessages = {
        pending: 'Canceling the delegation...',
        success: 'The delegation has been canceled',
        error: 'An error occurred while canceling the delegation',
      };
    }

    await createMultiStepsTransactionToast(toastMessages, provider, tx, 'Delegation');
  } catch (error) {
    showErrorTransactionToast(error);
  }
};

```

As you can see, we have added the `addDelegate` and `removeDelegate` functions from the TalentLayerID contract. By triggering these functions, it will call the respective function with two parameters.

* `user` : is the TalentLayer user ID
* `DelegateAddress` : is the delegate address add by the platform in the .env file

## See the Full Code Implemented on Our Demo DAPP

* Delegate Modal : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Modal/DelegateModal.tsx>
* Toggle Delegation : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/contracts/toggleDelegation.tsx>


# Service creation example

In this section, we will cover in detail how delegation works for the service creation.

The process is essentially the same for all the actions we have covered (except for minting delegation, which will be discussed in a separate section). Now, let's focus on delegation for service creation.

## 1 – The front end

First, we need to modify our ServiceForm.tsx component. If you recall, the classic workflow was as follows:

1. Fill out the service creation form.
2. Click on submit.
3. It triggers the onSubmit function, which calls the `createService` function in the `TalentLayerService` contract.

Now, we will add a new step that checks if delegation is activated. If it is, we will call the relevant API.

```tsx
// ..............

const { isActiveDelegate } = useContext(TalentLayerContext);

// ..............
 if (isActiveDelegate) {
    const response = await delegateCreateService(user.id, user.address, cid);
    tx = response.data.transaction;
  } else {
    const contract = new ethers.Contract(
      config.contracts.serviceRegistry,
      ServiceRegistry.abi,
      signer,
    );

    tx = await contract.createService(
      user?.id,
      process.env.NEXT_PUBLIC_PLATFORM_ID,
      cid,
      signature,
    );
  }  
```

As you can see, we check if delegation is activated using the `isActiveDelegate` variable. If it is, we call the `delegateCreateService` function. Let's take a closer look at it.

## 2 - The Back end

The `delegateCreateService` is a part of the Next API management workflow. You can explore further details in the Next [Next API documentation](https://nextjs.org/docs/pages/building-your-application/routing/api-routes)

```tsx
export const delegateCreateService = async (
  userId: string,
  userAddress: string,
  cid: string,
): Promise<any> => {
  try {
    return await axios.post('/api/delegate/create-service', {
      userId,
      userAddress,
      cid,
    });
  } catch (err) {
    console.error(err);
    throw err;
  }
}
```

In the code above, we are calling the `/api/delegate/create-service` API&#x20;

{% hint style="info" %}
You can find all the delegate API in `pages > api > delegate`
{% endhint %}

```typescript
// pages/api/createService.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { Contract } from 'ethers';
import { config } from '../../../config';
import TalentLayerService from '../../../contracts/ABI/TalentLayerService.json';
import { getServiceSignature } from '../../../utils/signature';
import { getDelegationSigner, isPlatformAllowedToDelegate } from '../utils/delegate';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { userId, userAddress, cid } = req.body;

  // @dev : you can add here all the check you need to confirm the delagation for a user
  await isPlatformAllowedToDelegate(userAddress, res);

  try {
    const signer = await getDelegationSigner(res);
    if (!signer) {
      return;
    }

    const signature = await getServiceSignature({ profileId: Number(userId), cid });
    const serviceRegistryContract = new Contract(
      config.contracts.serviceRegistry,
      TalentLayerService.abi,
      signer,
    );

    const transaction = await serviceRegistryContract.createService(
      userId,
      process.env.NEXT_PUBLIC_PLATFORM_ID,
      cid,
      signature,
    );

    res.status(200).json({ transaction: transaction });
  } catch (error) {
    console.log('errorDebug', error);
    res.status(500).json('tx failed');
  }
}

```

An <mark style="color:orange;">important</mark> part of the code below is that we set a new signer (the platform) for the `createService` transaction. The new signer is based on and composed of the variables you have set up in the .env file:

* `NEXT_PRIVATE_DELEGATE_SEED_PHRASE`
* `NEXT_PUBLIC_DELEGATE_ADDRESS`

```tsx
 const signer = await getDelegationSigner(res);
```

```typescript
import { NextApiResponse } from 'next';
import { ethers, Wallet } from 'ethers';
import { config } from '../../../config';
import { getUserByAddress } from '../../../queries/users';

export async function isPlatformAllowedToDelegate(
  userAddress: string,
  res: NextApiResponse,
): Promise<boolean> {
  const getUser = await getUserByAddress(userAddress);
  const delegateAddresses = getUser.data?.data?.users[0].delegates;

  if (
    delegateAddresses.indexOf(
      (process.env.NEXT_PUBLIC_DELEGATE_ADDRESS as string).toLowerCase(),
    ) === -1
  ) {
     res.status(500).json('Delegation is not activated');
    return false;
  }

  return true;
}

export async function getDelegationSigner(res: NextApiResponse): Promise<Wallet | null> {
  const provider = new ethers.providers.JsonRpcProvider(process.env.NEXT_PUBLIC_BACKEND_RPC_URL);
  const delegateSeedPhrase = process.env.NEXT_PRIVATE_DELEGATE_SEED_PHRASE;

  if (!delegateSeedPhrase) {
    res.status(500).json('Delegate seed phrase is not set');
    return null;
  }

  const signer = Wallet.fromMnemonic(delegateSeedPhrase).connect(provider);

  return signer;
}

```

Here is the core of the delegation

* With `isPlatformAllowedToDelegate`, we check if the user has activated delegation by requesting the user graph and verifying if a delegation address has been set by the platform.
* With `getDelegationSigner`, we ensure that the platform has correctly configured all the necessary variables to instantiate the signer for delegation purposes.

## See the Full Code Implemented on Our Demo DAPP

* Service Form component: <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Form/ServiceForm.tsx>
* Request component : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/request.ts>
* Delegate API : <https://github.com/TalentLayer-Labs/indie-frontend/tree/main/src/pages/api/delegate>
* isPlatformAllowedToDelegate and getDelegationSigner : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/pages/api/utils/delegate.ts>


# How mintForAddress works

In this section, we will provide a detailed explanation of how the mintForAddress function works, which allows users to mint their TalentLayerID without paying any fees.

## 1 - The Front End

The workflow begins in the TalentLayerIdForm, where the user can select their handle and mint their TalentLayerID. As we have seen in the previous sections, if delegation is not activated, the workflow remains unchanged and follows the steps outlined below:

1. Select your handle.
2. Click on the mint button, triggering the onSubmit function.
3. It checks the handle price and then calls the mint function.

Now, we introduce an additional step in the workflow, specifically for delegation. As mentioned before, if delegation is activated, it triggers the `delegateMintID` function. Let's examine the code below.

```tsx
const onSubmit = async (
    submittedValues: IFormValues,
    { setSubmitting }: { setSubmitting: (isSubmitting: boolean) => void },
  ) => {
    if (account && account.address && account.isConnected && provider && signer) {
      try {
        const contract = new ethers.Contract(
          config.contracts.talentLayerId,
          TalentLayerID.abi,
          signer,
        );

        const handlePrice = await contract.getHandlePrice(submittedValues.handle);
        console.log(process.env.NEXT_PUBLIC_ACTIVE_DELEGATE_MINT);

        if (process.env.NEXT_PUBLIC_ACTIVE_DELEGATE_MINT === 'true') {
          const response = await delegateMintID(
            submittedValues.handle,
            handlePrice,
            account.address,
          );
          tx = response.data.transaction;
        } else {
          tx = await contract.mint(process.env.NEXT_PUBLIC_PLATFORM_ID, submittedValues.handle, {
            value: handlePrice,
          });
        }
        await createTalentLayerIdTransactionToast(
          {
            pending: 'Minting your Talent Layer Id...',
            success: 'Congrats! Your Talent Layer Id is minted',
            error: 'An error occurred while creating your Talent Layer Id',
          },
          provider,
          tx,
          account.address,
        );

        setSubmitting(false);
        // TODO: add a refresh function on TL context and call it here rather than hard refresh
        router.reload();
      } catch (error: any) {
        showErrorTransactionToast(error);
      }
    } else {
      openConnectModal();
    }
  };
```

The `delegateMintID` function is called only if the environment variable `process.env.NEXT_PUBLIC_ACTIVE_DELEGATE_MINT` is set to true. So, what happens in this function?&#x20;

Well, nothing new :) It first calls the appropriate API Next route, as shown below:

```typescript
export const delegateMintID = async (
  handle: string,
  handlePrice: any,
  userAddress: string,
): Promise<any> => {
  try {
    return await axios.post('/api/delegate/mint-id', {
      handle,
      handlePrice,
      userAddress,
    });
  } catch (err) {
    console.error(err);
    throw err;
  }
};
```

In the mint-id API, it follows the steps we discussed earlier by obtaining the new signer and triggering the `mintForAddress` function, which mints the TalentLayerID for the user.

```tsx
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { handle, handlePrice, userAddress } = req.body;

  // @dev : you can add here all the check you need to confirm the delagation for a user

  try {
    if (process.env.NEXT_PUBLIC_ACTIVE_DELEGATE_MINT !== 'true') {
      res.status(500).json('Delegation is not activated');
      return null;
    }

    const signer = await getDelegationSigner(res);

    if (!signer) {
      return;
    }

    const talentLayerID = new Contract(config.contracts.talentLayerId, TalentLayerID.abi, signer);
    const transaction = await talentLayerID.mintForAddress(
      userAddress,
      process.env.NEXT_PUBLIC_PLATFORM_ID,
      handle,
      {
        value: handlePrice,
      },
    );

    res.status(200).json({ transaction: transaction });
  } catch (error) {
    console.log('errorDebug', error);
    res.status(500).json({ error: error });
  }
}
```

The mintForAddress contract function requires four parameters:

1. `userAddress`: This is the address of the user who wishes to mint their TalentLayerID.
2. `process.env.NEXT_PUBLIC_PLATFORM_ID`: This is the platform ID set up in the .env file.
3. `handle`: This is the selected handle chosen by the user.
4. `handleprice`: This represents the price of the handle. For the exact cost of a handle, please refer to the TalentLayer website.

## See the Full Code Implemented on Our Demo DAPP

* TalentLayerIDForm : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/Form/TalentLayerIdForm.tsx>
* request.ts : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/components/request.ts>
* mint-id API : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/pages/api/delegate/mint-id.ts>
* getDelegationSigner : <https://github.com/TalentLayer-Labs/indie-frontend/blob/main/src/pages/api/utils/delegate.ts>


# Get a Platform ID

Here, we will explore the steps to follow to obtain a Platform ID on both the testnet (Mumbai) and mainnet (Polygon).

## Learn about Platform IDs

{% content-ref url="/pages/2KzyqiEeag9Ax7Z5ac68" %}
[PlatformID](/readme/basics/platformid)
{% endcontent-ref %}

## Create a Platform ID on Mumbai Testnet&#x20;

{% hint style="success" %}
**Platform IDs on Polygon Mumbai are self-service** - anyone can create a Platform ID today by following the guide below.&#x20;
{% endhint %}

Step 1: Please follow the mumbai  polygon scan link <https://mumbai.polygonscan.com/address/0xEFD8dbC421380Ee04BAdB69216a0FD97F64CbFD4#writeProxyContract>

Step 2: Please follow this tutorial to mint your TalentLayer Platform ID on Mumbai&#x20;

{% embed url="<https://www.loom.com/share/384aef3008e547cfbec6bf4765b74ec1?sid=bf543684-8038-42a0-ad3d-e909073e8a15>" %}

## &#x20;Create a Platform ID on Polygon Mainnet&#x20;

In order to mint a Platform ID on the Polygon mainnet, you must be whitelisted by the TalentLayer team. Please visit the "Contact The Team" page to request a mainnet Platform ID.&#x20;

{% hint style="warning" %}
**Mainnet Platform IDs will only be approved for working testnet platforms.** Please do not request a mainnet Platform ID unless you have a production-ready app on testnet.&#x20;
{% endhint %}


# Inspiration for Builders

We're always brainstorming ideas for cool things that can be built with TalentLayer's dev tools. Here's some ideas submitted by our community. This is a public doc - all ideas are free for the taking. Have an idea you want to share? [Tell us about it here](https://docs.talentlayer.org/quick-start-integration-guide). 😄

## Project Ideas

#### 📝 Project management & payments tool

* Imagine a tool for a Company / DAO
* Users create their different tasks in a similar way that Trello
* Each ticket is a service on TalentLayer - when completed, escrow is released to the person completing it
* Users control all their tasks from this frontend and enjoy user liquidity from other platforms to find a good profile for the tasks they are posting
* It can maybe be built as a Trello or Github plugin

#### 💬 Messaging app with in-line job creation and management

* Create an app to create a conversation with someone you want to work with and handle the whole workflow directly and simply from the conversation using TalentLayer as a backend.
* Create a new job, validate the proposal and send money directly with the freelance using chat command inside the conversation
* It can maybe in the future directly be build as a discord or telegram plugin if they integrate wallet connection

#### 🤝 Influencer marketing marketplace

* Create a platform where businesss can connect with social media influencers for paid sponsorship gigs
* Possibly focus on the Lens ecosystem!

#### 🛍️ Shopify for Gig Platforms

* Simple to have a marketplace on TL in few minutes
* use safe SDK to create wallet if needed
* or Create your wallet with social login
* Create your TLplatformId
* Configure your domain
* Configure the marketplace configurations
  * Euro onramp with monerium as an option
* Choose your template (2 sided marketplaces, 1 sided talent, 1 sided company)
* Start focusing on your real business

#### 👋 Freelance Work Auction Platform

As a worker:

* go on the dapp
* filled your skills and level
* filled your calendar available (start and end hour, days available)
* Filled auction type (dutch, english)
* Set your minimum hour rate (can be different the weekend, the night)
* Set your minimum service hour (for example don’t accept job less than 2 days)
* See in live your calendar organised with money planned for the week / month
* When a mission is starting chat with your employer ect
* Note: you can choose between auto-confirm or manual confirmation

As an employer:

* go on the dapp
* Find worker with the skills you want available when you want, now, this weekend, during the night
* Check the profile and reputation (see medium price, price evolution..)
* Detailed your job
* booked the worker

Names ideas: AuctionWork, noWWorking. AutoPlan. Freererlance, BetterWork, WorkBetter

## What Types of Things Can You Build on TalentLayer?

TalentLayer’s low-level infra creates a new base layer that many new sorts of platforms, services, and other infrastructures can build on top of. There’s so much that’s possible; check out some of the key demographics of products that you can launch to power the future of work.

<figure><img src="/files/lhrUqJJobg6TvKB8mFqg" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/W7kbkgxEUIPGLeKqW4EA" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/wv8H5BwgY3PsxJe0wAno" alt=""><figcaption></figcaption></figure>


# Contact The Team

{% embed url="<https://tally.so/r/wkNBLe>" %}


# Core Developer Guides

{% hint style="warning" %}
**ALERT:** The documentation included in this section is most applicable for TalentLayer Core developers. For people building platforms on TalentLayer, most of these guides will be non-applicable.
{% endhint %}


# Subgraph Local Setup

{% hint style="warning" %}
**ALERT:** In most cases, you don't need to set up a local subgraph - everything is ready to use on Testnet! If you have advanced needs, we recommend contacting our team.&#x20;
{% endhint %}

## Requirements

[The Graph CLI](https://thegraph.com/en/). Installation instructions found [here](https://thegraph.com/docs/en/deploying/deploying-a-subgraph-to-studio/#installing-graph-cli).

[Docker](https://www.docker.com/). Installation instructions found [here](https://docs.docker.com/get-docker/).

## Instructions

### 1. Clone Repository from GitHub

```bash
git clone https://github.com/TalentLayer/talentlayer-id-subgraph
```

### 2. Navigate to Folder

```bash
cd talentlayer-id-subgraph
```

### 3. Start Subgraph Node

{% hint style="info" %}
Make sure Docker is running.
{% endhint %}

```bash
sh run-graph-node.sh
```

### 4. Deploy Subgraph To Node

```bash
make regenerate
```


# Smart Contracts Local Setup

{% hint style="warning" %}
**ALERT:** In most cases, you don't need to set up local contracts - everything is ready to use on Testnet! If you have advanced needs, we recommend contacting our team.&#x20;
{% endhint %}

## Requirements

[Hardhat](https://hardhat.org/). Installation instructions found [here](https://hardhat.org/hardhat-runner/docs/getting-started#installation)

## Instructions

### 1. Clone Repository From GitHub

```bash
git clone https://github.com/TalentLayer/talentlayer-id-contracts.git
```

### 2. Navigate to Folder

```bash
cd talentlayer-id-contracts
```

### 3. Install Dependencies

```bash
npm install
```

### 4. Create Environment Files&#x20;

```bash
touch .env
```

{% hint style="info" %}
Setup .env according to [this example](https://github.com/TalentLayer/talentlayer-id-contracts/edit/main/.env.example).
{% endhint %}

### 5. Create A Local Hardhat Chain

```bash
npx hardhat node 
```

### 6. Deploy Contracts to Local Chain

{% hint style="info" %}
Please see ./talentlayer-id-contracts/Makefile for more details.
{% endhint %}

{% hint style="info" %}
Open up a new instance of the terminal (Mac/Linux) or command line (Windows).
{% endhint %}

```bash
make install
```

### 7. Run Tests

```bash
npx hardhat test
```


# Advanced Documentation

{% hint style="warning" %}
**ALERT:** The documentation included in this section is most applicable for TalentLayer Core developers. For people building platforms on TalentLayer, most of these guides will be non-applicable.
{% endhint %}


# Contract & Graph Deployment Guide

Reference for deploying TalentLayer and related dependencies to a new chain.

## Deploy Workflow

### To be prepared before deploy

* Setup local .env file to be able to execute all commands.
  * Be sure you have all variables in the .env.example file
  * Mandatory:
    * MNEMONIC
    * SNOWTRACE\_API\_KEY & POLYGONSCAN\_API\_KEY: used to validate the contracts
    * INFURA\_API\_KEY: used by hardhat to deploy
  * Optional
    * SUBGRAPH\_FOLDER: useful to easly copy config into subgraph repo
    * DEPLOY\_NETWORK: use by Makefile to define network for all commands
    * INFURA\_ID & INFURA\_SECRET: use by playground script to post json on IPFS
* Be sure that your address has enough fund
* Note: if you have any issue in the command bellow, check the troubleshooting.md
* Replace the network used in the command bellow by the one you want to deploy to. For this documentation we use polygon.

### Step 1: Contract deployment

* Deploy TL contracts: `npx hardhat deploy-full --network polygon --verify`

### Step 2: Setup initial data

* Double-check the networkConfig.ts, it contains all setups for the current network
  * multisigAddressList: list of multisig addresses used to receive fee and with ownership of upgradabiltiy
  * allowedTokenList: list of tokens allowed to be used as payment
  * platformList: list of platform name and address used to create our partners platformId
* Launch the setup command, it will automatically add the multisig addresses, the allowed tokens and the platformIds
  * `npx hardhat initial-setup --network polygon`

### Step 3: Update Subgraph

#### Update configuration

* Update the abis from the contract folder to the graph folder
* configure your env var `DEPLOY_NETWORK`
* Update network.json file with the new deployed addresses: `make update-graph-config`
* Update the start block in the network.json. Use the block number of the first contract deployed

#### Deploy your subgraph

* Update the abis in the subgraph repo
* Generate code from your GraphQL schema and operations.: `graph codegen`
* Copy configuration from network.json and buid graph code: `graph build --network polygon`
* Authenticate to the hosted service: `graph auth --network polygon --product hosted-service <your access token>`
* Deploy to the hosted service:
  * polygon: `graph deploy --product hosted-service talentlayer/talent-layer-polygon`
  * fuji: `graph deploy --product hosted-service talentlayer/talent-layer-fuji`

### Step 4: Update Indie Frontend

* Update the abis in the frontend repo `make update-frontend-config`
* Fill the network const with the right deployed addresses in the **src > config.ts** file

### Step 5: Defender

* Transfer ownership to the multisig for every contracts
  * for ownable contracts:
    * `npx hardhat transfer-ownership --contract-name "TalentLayerID" --address 0x0CFF3F17b62704A0fc76539dED9223a44CAf4825 --network polygon`
    * `npx hardhat transfer-ownership --contract-name "TalentLayerService" --address 0x0CFF3F17b62704A0fc76539dED9223a44CAf4825 --network polygon`
    * `npx hardhat transfer-ownership --contract-name "TalentLayerReview" --address 0x0CFF3F17b62704A0fc76539dED9223a44CAf4825 --network polygon`
    * `npx hardhat transfer-ownership --contract-name "TalentLayerEscrow" --address 0x0CFF3F17b62704A0fc76539dED9223a44CAf4825 --network polygon`
  * for access control contracts:
    * `npx hardhat grant-role --contract-name "TalentLayerPlatformID" --address 0x0CFF3F17b62704A0fc76539dED9223a44CAf4825 --network polygon`


# TalentLayer Improvement Proposals

Inspired by the EIP for Ethereum, TLIP stands for TalentLayer Improvement Proposal. A TLIP is a document describing a new feature, an architecture decision, a code improvement, or a security fix for the TalentLayer protocol. For TalentLayer implementers, TLIPs are a convenient way to track the progress of their implementation.

We intend TLIPs to be the primary mechanism for the core team and open-source contributors to propose new features, collect community input on a subject, and document the design decisions that have gone into the Talent Layer protocol.

Eventually, we will host this TLIP Index and the surrounding discussions in a proper governance forum, but for now, we will host each TLIP as a public Notion document where anyone can comment.

{% hint style="success" %}
⭐ **CALL FOR INPUT:** Do you have input on how we can improve TalentLayer? [Please get in touch](https://docs.talentlayer.org/quick-start-integration-guide) to post a new TLIP on the forum or, for feedback on existing TLIPs, post as comments directly in Notion!
{% endhint %}

{% embed url="<https://talentlayer.notion.site/TLIP-83015d793d9d48818914f8cac3c08231>" %}


# Audit Report

## Internal Audits

### TLIP 0002: Security Assessment

In January and February of 2023 TalentLayer's core team and open-source developer community conducted an internal security audit that resulted in a number of updates being made before proceeding with external audits.&#x20;

#### Audit Methodology and Results

{% embed url="<https://talentlayer.notion.site/TLIP-0002-Security-Assessment-782ab9e80d774749811adfc61cbb7622>" %}

## External Audits

### [Benoit Gantaume](https://www.linkedin.com/in/benoitgantaume/)

Benoit conducted an audit as a part of the TalentLayer Attackathon hosted in March 2023.&#x20;

#### Audit Results

{% embed url="<https://github.com/0xRomain/talenlayer-id-report/blob/main/findings.md>" %}

### [Ahmet Ahmedov](https://twitter.com/0xahmedov)

Ahmet conducted an audit as a part of the TalentLayer Attackathon hosted in March 2023.&#x20;

#### Audit Results

{% embed url="<https://github.com/ahmedovv123/audits/blob/main/audits/TalentLayer.md>" %}


