# Credentials

### Overview

Locale uses Soulbound Tokens (SBTs) to represent verified credentials on-chain. These non-transferable NFTs prove that a user has completed verification without exposing personal information.

### What Are Soulbound Tokens?

Soulbound Tokens are NFTs that:

* **Cannot be transferred** - Permanently bound to the minting address
* **Cannot be sold** - No marketplace trading possible
* **Represent identity** - Prove verification status
* **Can be revoked** - For fraud or policy violations

```mermaid
graph LR
    subgraph "Regular NFT"
        A[Owner A] -->|transfer| B[Owner B]
        B -->|sell| C[Owner C]
    end

    subgraph "Soulbound Token"
        D[Owner D] -->|bound| D
        D -.->|cannot transfer| E[Owner E]
    end
```

### Credential Types

#### BorrowerCredential

Issued to borrowers who complete KYC verification.

```solidity
struct BorrowerCredential {
    uint256 tokenId;
    address holder;
    KYCLevel kycLevel;      // BASIC or ENHANCED
    uint256 issuedAt;
    uint256 expiresAt;
    bool revoked;
}
```

| Field       | Description                  |
| ----------- | ---------------------------- |
| `tokenId`   | Unique credential identifier |
| `holder`    | Wallet address (immutable)   |
| `kycLevel`  | Verification tier            |
| `issuedAt`  | Timestamp of issuance        |
| `expiresAt` | Expiration timestamp         |
| `revoked`   | Revocation status            |

#### InvestorCredential

Issued to investors who complete accreditation verification.

```solidity
struct InvestorCredential {
    uint256 tokenId;
    address holder;
    AccreditationLevel level;  // RETAIL, ACCREDITED, QUALIFIED
    uint256 issuedAt;
    uint256 expiresAt;
    bool revoked;
}
```

| Level          | Description         | Requirements                  |
| -------------- | ------------------- | ----------------------------- |
| **RETAIL**     | Standard investor   | Basic KYC                     |
| **ACCREDITED** | Accredited investor | Income/net worth verification |
| **QUALIFIED**  | Qualified purchaser | Enhanced verification         |

### KYC Levels

#### Basic KYC

Required for all borrowers:

| Check    | Method         | Data Verified |
| -------- | -------------- | ------------- |
| Identity | Plaid IDV      | Name, DOB     |
| Document | Government ID  | ID validity   |
| Selfie   | Liveness check | Photo match   |

#### Enhanced KYC

Required for loans > $100K:

| Check     | Method          | Data Verified       |
| --------- | --------------- | ------------------- |
| All Basic | -               | -                   |
| Address   | Utility bill    | Proof of residence  |
| Income    | Bank statements | Income verification |
| Business  | Registration    | Business legitimacy |

### Verification Flow

#### Borrower Credential Issuance

```mermaid
sequenceDiagram
    participant B as Borrower
    participant W as Web App
    participant P as Plaid IDV
    participant API as Backend
    participant SC as BorrowerCredential

    B->>W: Click "Verify Identity"
    W->>P: Initialize IDV session
    P->>B: Document upload prompt
    B->>P: Upload ID + selfie
    P->>P: Verify documents
    P-->>API: Webhook: verification_complete
    API->>API: Validate webhook signature
    API->>SC: mintCredential(borrower, KYC_BASIC)
    SC-->>B: BorrowerCredential (SBT)
```

#### Investor Credential Issuance

```mermaid
sequenceDiagram
    participant I as Investor
    participant W as Web App
    participant P as Plaid
    participant API as Backend
    participant SC as InvestorCredential

    I->>W: Click "Verify Accreditation"
    W->>P: Initialize verification
    alt Self-Certification
        I->>W: Sign attestation
    else Document Verification
        I->>P: Upload financial docs
        P-->>API: Verification result
    end
    API->>SC: mintCredential(investor, ACCREDITED)
    SC-->>I: InvestorCredential (SBT)
```

### Smart Contract Implementation

#### BorrowerCredential Contract

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";

contract BorrowerCredential is ERC721Upgradeable, AccessControlUpgradeable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant REVOKER_ROLE = keccak256("REVOKER_ROLE");

    enum KYCLevel { NONE, BASIC, ENHANCED }

    struct Credential {
        KYCLevel kycLevel;
        uint256 issuedAt;
        uint256 expiresAt;
        bool revoked;
    }

    mapping(uint256 => Credential) public credentials;
    mapping(address => uint256) public holderToToken;

    // Soulbound: Override transfer functions
    function _update(
        address to,
        uint256 tokenId,
        address auth
    ) internal virtual override returns (address) {
        address from = _ownerOf(tokenId);

        // Only allow minting (from = address(0)) and burning
        require(
            from == address(0) || to == address(0),
            "Soulbound: Transfer not allowed"
        );

        return super._update(to, tokenId, auth);
    }

    function mintCredential(
        address borrower,
        KYCLevel level,
        uint256 validityPeriod
    ) external onlyRole(MINTER_ROLE) returns (uint256) {
        require(holderToToken[borrower] == 0, "Already has credential");

        uint256 tokenId = _nextTokenId();
        _safeMint(borrower, tokenId);

        credentials[tokenId] = Credential({
            kycLevel: level,
            issuedAt: block.timestamp,
            expiresAt: block.timestamp + validityPeriod,
            revoked: false
        });

        holderToToken[borrower] = tokenId;

        emit CredentialIssued(borrower, tokenId, level);
        return tokenId;
    }

    function revokeCredential(
        uint256 tokenId,
        string calldata reason
    ) external onlyRole(REVOKER_ROLE) {
        credentials[tokenId].revoked = true;
        emit CredentialRevoked(tokenId, reason);
    }

    function isValid(address holder) external view returns (bool) {
        uint256 tokenId = holderToToken[holder];
        if (tokenId == 0) return false;

        Credential memory cred = credentials[tokenId];
        return !cred.revoked && block.timestamp < cred.expiresAt;
    }

    event CredentialIssued(address indexed holder, uint256 tokenId, KYCLevel level);
    event CredentialRevoked(uint256 indexed tokenId, string reason);
}
```

#### InvestorCredential Contract

```solidity
contract InvestorCredential is ERC721Upgradeable, AccessControlUpgradeable {
    enum AccreditationLevel { NONE, RETAIL, ACCREDITED, QUALIFIED }

    struct Credential {
        AccreditationLevel level;
        uint256 issuedAt;
        uint256 expiresAt;
        bool revoked;
    }

    // Similar structure to BorrowerCredential
    // with accreditation-specific logic
}
```

### Credential Verification

#### On-Chain Verification

Smart contracts verify credentials before operations:

```solidity
// In SimpleLoanPool
function createLoan(bytes32 loanId, address borrower, ...) external {
    require(
        borrowerCredential.isValid(borrower),
        "Invalid borrower credential"
    );
    // ... loan creation logic
}

// In StakingPool
function stake(bytes32 poolId, uint256 amount) external {
    require(
        investorCredential.isValid(msg.sender),
        "Invalid investor credential"
    );
    // ... staking logic
}
```

#### API Verification

Backend validates credentials before API operations:

```typescript
async function validateBorrowerCredential(address: string): Promise<boolean> {
  const credential = await borrowerCredentialContract.read.isValid([address]);

  if (!credential) {
    throw new Error('Invalid or expired borrower credential');
  }

  return true;
}
```

### Credential Metadata

#### On-Chain Data

Stored directly in the contract:

* Token ID
* Holder address
* KYC/Accreditation level
* Timestamps
* Revocation status

#### Off-Chain Data (IPFS)

Optional metadata stored off-chain:

* Verification provider
* Document hashes (not documents themselves)
* Audit trail

```json
{
  "name": "Locale Borrower Credential",
  "description": "Verified borrower credential for Locale lending",
  "image": "ipfs://...",
  "attributes": [
    { "trait_type": "KYC Level", "value": "ENHANCED" },
    { "trait_type": "Verified By", "value": "Plaid" },
    { "trait_type": "Verification Date", "value": "2024-01-15" }
  ]
}
```

### Expiration and Renewal

#### Credential Validity

| Credential Type                 | Validity Period | Renewal Process    |
| ------------------------------- | --------------- | ------------------ |
| BorrowerCredential (Basic)      | 1 year          | Re-verify identity |
| BorrowerCredential (Enhanced)   | 2 years         | Document re-upload |
| InvestorCredential (Retail)     | 1 year          | Self-attestation   |
| InvestorCredential (Accredited) | 1 year          | Re-verify status   |

#### Renewal Flow

```mermaid
graph TB
    A[Credential Expires Soon] --> B[Notification Sent]
    B --> C{User Action}
    C -->|Renew| D[Re-verification]
    D --> E[New Credential Minted]
    E --> F[Old Credential Burned]
    C -->|Ignore| G[Credential Expires]
    G --> H[Access Revoked]
```

### Revocation

#### Revocation Reasons

| Reason           | Trigger                    | Effect               |
| ---------------- | -------------------------- | -------------------- |
| **Fraud**        | Detected false information | Immediate revocation |
| **Dispute**      | Legal/regulatory action    | Temporary suspension |
| **Expiration**   | Time elapsed               | Natural expiration   |
| **User Request** | Account closure            | Self-revocation      |

#### Revocation Process

```mermaid
sequenceDiagram
    participant Admin as Admin
    participant API as Backend
    participant SC as Credential Contract
    participant User as User

    Admin->>API: Initiate revocation
    API->>API: Log reason + evidence
    API->>SC: revokeCredential(tokenId, reason)
    SC->>SC: Set revoked = true
    SC-->>User: Credential revoked event
    API->>User: Email notification
```

### Privacy Considerations

#### What's On-Chain

* ✅ Credential exists
* ✅ Verification level
* ✅ Valid/revoked status
* ✅ Expiration date

#### What's NOT On-Chain

* ❌ Personal information (name, DOB)
* ❌ Document copies
* ❌ Address details
* ❌ Financial data

#### Zero-Knowledge Integration

Credentials work with ZK proofs:

```mermaid
graph LR
    A[Credential On-Chain] --> B{Verifier Asks}
    B --> C[Generate ZK Proof]
    C --> D[Prove: Has Valid Credential]
    D --> E[No Personal Data Revealed]
```

### API Reference

#### Check Credential Status

```bash
GET /api/credentials/{address}/status

Response:
{
  "hasBorrowerCredential": true,
  "borrowerCredential": {
    "tokenId": "123",
    "kycLevel": "ENHANCED",
    "issuedAt": "2024-01-15T00:00:00Z",
    "expiresAt": "2026-01-15T00:00:00Z",
    "isValid": true
  },
  "hasInvestorCredential": false
}
```

#### Initiate Verification

```bash
POST /api/credentials/verify

Request:
{
  "type": "borrower",
  "level": "BASIC"
}

Response:
{
  "sessionId": "...",
  "redirectUrl": "https://plaid.com/identity/...",
  "expiresIn": 3600
}
```

### Integration Guide

#### Check Before Operations

```typescript
import { useBorrowerCredential } from '@/hooks/useCredentials';

function LoanApplicationForm() {
  const { data: credential, isValid } = useBorrowerCredential();

  if (!isValid) {
    return <VerifyIdentityPrompt />;
  }

  return <LoanForm />;
}
```

#### Listen for Events

```typescript
borrowerCredential.on('CredentialIssued', (holder, tokenId, level) => {
  console.log(`New credential: ${holder} - Level ${level}`);
});

borrowerCredential.on('CredentialRevoked', (tokenId, reason) => {
  console.log(`Credential ${tokenId} revoked: ${reason}`);
});
```
