# Consent

### Overview

The **Consent** contract manages individual user consents linked to agreements in the Permission Protocol. It enables users (data suppliers) to create, revoke, and extend consent records through cryptographically signed actions.

### Contract Details

| Property         | Value                 |
| ---------------- | --------------------- |
| Solidity Version | ^0.8.28               |
| License          | MIT                   |
| Inheritance      | EIP712 (OpenZeppelin) |
| Dependencies     | IAgreement interface  |

***

### Purpose

The Consent contract provides a complete lifecycle management system for user consents:

* **Create Consent**: Users grant consent to an existing agreement
* **Revoke Consent**: Users can revoke their consent (based on agreement's revocation rules)
* **Extend Validity**: Users can extend the expiration of their consent
* **Batch Operations**: Create multiple consent records in a single transaction

All operations require EIP-712 signatures from the data supplier, enabling gasless (meta-transaction) patterns.

***

### Data Structures

#### ConsentRecord

The primary structure for storing consent details.

```solidity
struct ConsentRecord {
    uint256 agreementId;     // Reference to the parent Agreement ID
    IAgreement agreement;    // Reference to the Agreement contract
    uint64 createdAt;        // Timestamp when consent was created
    address supplier;        // Data supplier (user giving consent)
    uint64 validityEnd;      // Unix timestamp when consent expires (0 = never)
    uint16 nonce;            // Replay protection for modifications
    bool disclosed;          // Whether data is publicly disclosed
    string dataRef;          // Reference to the data (IPFS/URL)
    string revocationRef;    // Reason/reference if revoked (empty = active)
}
```

| Field           | Type       | Description                                            |
| --------------- | ---------- | ------------------------------------------------------ |
| `agreementId`   | uint256    | ID of the associated Agreement                         |
| `agreement`     | IAgreement | Reference to the Agreement contract                    |
| `createdAt`     | uint64     | Timestamp when consent was created (for revoke checks) |
| `supplier`      | address    | Address of the data supplier                           |
| `validityEnd`   | uint64     | Expiration timestamp (0 = never expires)               |
| `nonce`         | uint16     | Counter for replay protection on updates               |
| `disclosed`     | bool       | If `true`, data is publicly accessible                 |
| `dataRef`       | string     | IPFS hash or URL pointing to the data                  |
| `revocationRef` | string     | Revocation reason (empty if not revoked)               |

#### ConsentRecordInput

Input structure for batch consent record creation.

```solidity
struct ConsentRecordInput {
    uint256 agreementId;     // The associated agreement ID
    IAgreement agreement;    // The agreement contract
    address supplier;        // The supplier address (signer)
    uint64 validityEnd;      // The validity expiration timestamp
    bool disclosed;          // Whether the data is disclosed
    string dataRef;          // Reference to the data
    bytes32 r;               // The 'r' component of the signature
    bytes32 vs;              // The 'vs' component of the signature
}
```

***

### State Variables

| Variable               | Type                              | Visibility | Description                                  |
| ---------------------- | --------------------------------- | ---------- | -------------------------------------------- |
| `consentRecordCounter` | uint256                           | public     | Counter for unique consent IDs (starts at 1) |
| `usedDigests`          | mapping(bytes32 => uint256)       | public     | Tracks used EIP-712 digests                  |
| `consentRecords`       | mapping(uint256 => ConsentRecord) | public     | Storage of consent records                   |

***

### Functions

#### Constructor

```solidity
constructor(
    string memory name,
    string memory version
)
```

Initializes the contract with EIP-712 domain parameters.

**Parameters:**

| Name      | Type   | Description                   |
| --------- | ------ | ----------------------------- |
| `name`    | string | Domain name (e.g., "Consent") |
| `version` | string | Domain version (e.g., "1")    |

***

#### createConsentRecord

```solidity
function createConsentRecord(
    uint256 agreementId,
    IAgreement agreement,
    address supplier,
    uint64 validityEnd,
    bool disclosed,
    string calldata dataRef,
    bytes32 r,
    bytes32 vs
) external returns (uint256 consentRecordId)
```

Creates a new consent record with supplier signature verification.

**Parameters:**

| Name          | Type       | Description                               |
| ------------- | ---------- | ----------------------------------------- |
| `agreementId` | uint256    | ID of the parent agreement                |
| `agreement`   | IAgreement | The Agreement contract reference          |
| `supplier`    | address    | Address of the data supplier (signer)     |
| `validityEnd` | uint64     | Expiration timestamp (0 = never expires)  |
| `disclosed`   | bool       | Public disclosure flag                    |
| `dataRef`     | string     | Reference to the data                     |
| `r`           | bytes32    | ECDSA signature component r               |
| `vs`          | bytes32    | ECDSA compact signature component (v + s) |

**Returns:**

| Name              | Type    | Description                          |
| ----------------- | ------- | ------------------------------------ |
| `consentRecordId` | uint256 | The unique ID of the created consent |

**Requirements:**

* Referenced agreement must exist
* Signature must be from the supplier
* Consent with same parameters cannot already exist

**Effects:**

* Sets `createdAt` to the current block timestamp

**Emits:** `ConsentRecordCreated`

***

#### batchCreateConsentRecords

```solidity
function batchCreateConsentRecords(
    ConsentRecordInput[] calldata inputs
) external returns (uint256[] memory consentRecordIds)
```

Creates multiple consent records in a single transaction.

**Parameters:**

| Name     | Type                  | Description                    |
| -------- | --------------------- | ------------------------------ |
| `inputs` | ConsentRecordInput\[] | Array of consent record inputs |

**Returns:**

| Name               | Type       | Description                         |
| ------------------ | ---------- | ----------------------------------- |
| `consentRecordIds` | uint256\[] | Array of created consent record IDs |

**Requirements:**

* Input array must not be empty

**Emits:** `ConsentRecordCreated` for each consent record

***

#### revokeConsentRecord

```solidity
function revokeConsentRecord(
    uint256 consentRecordId,
    string calldata revocationRef,
    uint16 nonce,
    bytes32 r,
    bytes32 vs
) external
```

Revokes an existing consent record.

**Parameters:**

| Name              | Type    | Description                         |
| ----------------- | ------- | ----------------------------------- |
| `consentRecordId` | uint256 | ID of the consent to revoke         |
| `revocationRef`   | string  | Reason or reference for revocation  |
| `nonce`           | uint16  | Current nonce of the consent record |
| `r`               | bytes32 | ECDSA signature component r         |
| `vs`              | bytes32 | ECDSA compact signature component   |

**Requirements:**

* Agreement must allow revocation (`agreement.isRevokable()` returns true)
* Nonce must match current consent record nonce
* Signature must be from the original supplier
* Revocation reference cannot be empty
* Consent must not already be revoked

**Emits:** `ConsentRecordRevoked`

**Effects:**

* Sets `revocationRef` to the provided value
* Increments `nonce` by 1

***

#### extendValidity

```solidity
function extendValidity(
    uint256 consentRecordId,
    uint64 newValidityEnd,
    uint16 nonce,
    bytes32 r,
    bytes32 vs
) external
```

Extends the validity period of a consent record.

**Parameters:**

| Name              | Type    | Description                         |
| ----------------- | ------- | ----------------------------------- |
| `consentRecordId` | uint256 | ID of the consent to extend         |
| `newValidityEnd`  | uint64  | New expiration timestamp            |
| `nonce`           | uint16  | Current nonce of the consent record |
| `r`               | bytes32 | ECDSA signature component r         |
| `vs`              | bytes32 | ECDSA compact signature component   |

**Requirements:**

* Nonce must match current consent record nonce
* New validity must be greater than current validity
* Current validity cannot be 0 (consents with no expiry cannot be extended)
* Consent must not be revoked
* Signature must be from the original supplier

**Emits:** `ConsentRecordValidityExtended`

**Effects:**

* Updates `validityEnd` to new value
* Increments `nonce` by 1

***

#### getConsentRecord

```solidity
function getConsentRecord(uint256 consentRecordId) public view returns (ConsentRecord memory)
```

Retrieves a consent record by ID.

**Parameters:**

| Name              | Type    | Description                   |
| ----------------- | ------- | ----------------------------- |
| `consentRecordId` | uint256 | The consent record identifier |

**Returns:** `ConsentRecord` struct

**Reverts:** `ConsentRecordNotFound` if record doesn't exist

***

#### Hash Functions

**hashConsentRecord**

```solidity
function hashConsentRecord(
    uint256 agreementId,
    address agreement,
    address supplier,
    uint64 validityEnd,
    bool disclosed,
    string calldata dataRef
) public pure returns (bytes32)
```

Computes the EIP-712 struct hash for consent record creation.

**getTypedDataHash**

```solidity
function getTypedDataHash(
    uint256 agreementId,
    address agreement,
    address supplier,
    uint64 validityEnd,
    bool disclosed,
    string calldata dataRef
) public view returns (bytes32)
```

Returns the complete EIP-712 typed data hash for signing.

***

### Events

#### ConsentRecordCreated

```solidity
event ConsentRecordCreated(
    uint256 consentRecordId,
    uint256 indexed agreementId,
    IAgreement indexed agreement,
    address indexed supplier,
    uint64 validityEnd,
    bool disclosed,
    string dataRef,
    bytes32 r,
    bytes32 vs
);
```

Emitted when a new consent record is created. Includes the agreement contract reference and signature components for verification.

#### ConsentRecordRevoked

```solidity
event ConsentRecordRevoked(
    uint256 indexed consentRecordId,
    string revocationRef,
    uint16 nonce
);
```

Emitted when a consent record is revoked.

#### ConsentRecordValidityExtended

```solidity
event ConsentRecordValidityExtended(
    uint256 indexed consentRecordId,
    uint64 newValidityEnd,
    uint16 nonce
);
```

Emitted when consent validity is extended.

***

### Errors

| Error                                 | Description                                       |
| ------------------------------------- | ------------------------------------------------- |
| `ConsentRecordAlreadyExists(uint256)` | A consent with the same parameters already exists |
| `InvalidSignature()`                  | The provided signature is invalid                 |
| `InvalidRevocationRef()`              | The revocation reference is empty                 |
| `InvalidNonce()`                      | The provided nonce doesn't match                  |
| `InvalidNewValidityEnd()`             | New validity is not greater than current          |
| `ConsentRecordNotFound()`             | The requested consent ID does not exist           |
| `EmptyBatchInput()`                   | The batch input array is empty                    |
| `ConsentRecordAlreadyRevoked()`       | The consent record has already been revoked       |
| `RevokeFailed()`                      | Revocation not allowed by agreement's rules       |

***

### EIP-712 Type Definitions

The contract uses multiple EIP-712 types for different operations:

#### ConsentRecord (for creation)

```javascript
const types = {
  ConsentRecord: [
    { name: "agreementId", type: "uint256" },
    { name: "agreement", type: "address" },
    { name: "supplier", type: "address" },
    { name: "validityEnd", type: "uint64" },
    { name: "disclosed", type: "bool" },
    { name: "dataRef", type: "string" },
  ],
};
```

#### RevokeRecord (for revocation)

```javascript
const types = {
  RevokeRecord: [
    { name: "consentRecordId", type: "uint256" },
    { name: "revocationRef", type: "string" },
    { name: "nonce", type: "uint16" },
  ],
};
```

#### ExtendValidityRecord (for extension)

```javascript
const types = {
  ExtendValidityRecord: [
    { name: "consentRecordId", type: "uint256" },
    { name: "newValidityEnd", type: "uint64" },
    { name: "nonce", type: "uint16" },
  ],
};
```

***

### Usage Examples

#### Creating a Consent Record (TypeScript/Viem)

```typescript
import { parseSignature, signatureToCompactSignature } from "viem";

// Prepare consent data
const agreementContract = "0x..."; // Agreement contract address
const agreementId = 1n;
const supplier = supplierWallet.account.address;
const dataRef = "ipfs://QmXxx...";
const validityEnd = BigInt(Math.floor(Date.now() / 1000) + 30 * 24 * 3600); // 30 days
const disclosed = true;

// Create EIP-712 typed data
const typedData = {
  domain: {
    name: "Consent",
    version: "1",
    chainId: 1n,
    verifyingContract: consentContractAddress,
  },
  types: {
    ConsentRecord: [
      { name: "agreementId", type: "uint256" },
      { name: "agreement", type: "address" },
      { name: "supplier", type: "address" },
      { name: "validityEnd", type: "uint64" },
      { name: "disclosed", type: "bool" },
      { name: "dataRef", type: "string" },
    ],
  },
  primaryType: "ConsentRecord",
  message: {
    agreementId,
    agreement: agreementContract,
    supplier,
    validityEnd,
    disclosed,
    dataRef,
  },
};

// Sign with supplier wallet
const signature = await supplierWallet.signTypedData(typedData);
const parsedSig = parseSignature(signature);
const compactSig = signatureToCompactSignature(parsedSig);

// Create consent record on-chain
const consentId = await consentContract.write.createConsentRecord([
  agreementId,
  agreementContract,
  supplier,
  validityEnd,
  disclosed,
  dataRef,
  compactSig.r,
  compactSig.yParityAndS,
]);
```

#### Batch Creating Consent Records

```typescript
// Prepare multiple consent record inputs
const consentInputs = [
  {
    agreementId: 1n,
    agreement: agreementContractAddress,
    supplier: supplier1Address,
    validityEnd: validityEnd1,
    disclosed: true,
    dataRef: "ipfs://QmAbc...",
    r: compactSig1.r,
    vs: compactSig1.yParityAndS,
  },
  {
    agreementId: 2n,
    agreement: agreementContractAddress,
    supplier: supplier2Address,
    validityEnd: validityEnd2,
    disclosed: false,
    dataRef: "ipfs://QmDef...",
    r: compactSig2.r,
    vs: compactSig2.yParityAndS,
  },
];

// Create all consent records in one transaction
const consentIds = await consentContract.write.batchCreateConsentRecords([consentInputs]);
```

#### Revoking a Consent Record

```typescript
const consentRecordId = 1n;
const revocationRef = "User requested data deletion";
const nonce = 0; // Current nonce from getConsentRecord

const revokeTypedData = {
  domain: {
    name: "Consent",
    version: "1",
    chainId: 1n,
    verifyingContract: consentContractAddress,
  },
  types: {
    RevokeRecord: [
      { name: "consentRecordId", type: "uint256" },
      { name: "revocationRef", type: "string" },
      { name: "nonce", type: "uint16" },
    ],
  },
  primaryType: "RevokeRecord",
  message: {
    consentRecordId,
    revocationRef,
    nonce,
  },
};

const signature = await supplierWallet.signTypedData(revokeTypedData);
const parsedSig = parseSignature(signature);
const compactSig = signatureToCompactSignature(parsedSig);

// Note: This will fail if the agreement doesn't allow revocation
await consentContract.write.revokeConsentRecord([
  consentRecordId,
  revocationRef,
  nonce,
  compactSig.r,
  compactSig.yParityAndS,
]);
```

#### Extending Validity

```typescript
const consentRecordId = 1n;
const newValidityEnd = BigInt(Math.floor(Date.now() / 1000) + 60 * 24 * 3600); // 60 days
const nonce = 0; // Current nonce

const extendTypedData = {
  domain: {
    name: "Consent",
    version: "1",
    chainId: 1n,
    verifyingContract: consentContractAddress,
  },
  types: {
    ExtendValidityRecord: [
      { name: "consentRecordId", type: "uint256" },
      { name: "newValidityEnd", type: "uint64" },
      { name: "nonce", type: "uint16" },
    ],
  },
  primaryType: "ExtendValidityRecord",
  message: {
    consentRecordId,
    newValidityEnd,
    nonce,
  },
};

const signature = await supplierWallet.signTypedData(extendTypedData);
const parsedSig = parseSignature(signature);
const compactSig = signatureToCompactSignature(parsedSig);

await consentContract.write.extendValidity([
  consentRecordId,
  newValidityEnd,
  nonce,
  compactSig.r,
  compactSig.yParityAndS,
]);
```

#### Checking Consent Status

```typescript
const record = await consentContract.read.getConsentRecord([1n]);

// Check if revoked
const isRevoked = record.revocationRef !== "";

// Check if expired (validityEnd of 0 means never expires)
const now = BigInt(Math.floor(Date.now() / 1000));
const isExpired = record.validityEnd > 0n && now > record.validityEnd;

// Check if active
const isActive = !isRevoked && !isExpired;

// Access consent information
const agreementAddress = record.agreement;
const agreementId = record.agreementId;
const createdAt = record.createdAt;
```

#### Checking if Revocation is Allowed

```typescript
// Before attempting to revoke, check if the agreement allows it
const record = await consentContract.read.getConsentRecord([consentRecordId]);
const canRevoke = await agreementContract.read.isRevokable([
  record.agreementId,
  record.createdAt,
]);

if (!canRevoke) {
  // Check the agreement's revokeEligibility to understand why
  const agreementData = await agreementContract.read.getAgreementData([record.agreementId]);
  
  if (agreementData.revokeEligibility === 0) { // UnRevokable
    console.log("This consent cannot be revoked");
  } else if (agreementData.revokeEligibility === 2) { // RevokableAfterGracePeriod
    const gracePeriodEnd = record.createdAt + agreementData.revokeGracePeriodSeconds;
    console.log(`Revocation allowed after: ${new Date(Number(gracePeriodEnd) * 1000)}`);
  }
}
```

***

### Consent Lifecycle

```
┌─────────────┐
│   Created   │ ──── nonce: 0, createdAt: block.timestamp
└──────┬──────┘
       │
       ▼
┌─────────────┐     ┌─────────────┐
│   Active    │◄────│  Extended   │ ──── nonce: n+1
└──────┬──────┘     └─────────────┘
       │
       ▼ (if agreement allows)
┌─────────────┐
│   Revoked   │ ──── nonce: n+1 (permanent)
└─────────────┘
```

**Note:** Unlike previous versions, revocation is now permanent. Once a consent is revoked, it cannot be restored (undoRevocation has been removed).

***

### Revocation Rules

The ability to revoke a consent record depends on the parent agreement's `revokeEligibility` setting:

| RevokeEligibility               | Can Revoke?                                       |
| ------------------------------- | ------------------------------------------------- |
| `UnRevokable` (0)               | Never - `revokeConsentRecord` will always fail    |
| `InstantlyRevokable` (1)        | Always - can revoke immediately after creation    |
| `RevokableAfterGracePeriod` (2) | Only after `createdAt + revokeGracePeriodSeconds` |

The Consent contract calls `agreement.isRevokable(agreementId, createdAt)` to verify revocation eligibility before allowing the revoke operation.

***

### Security Considerations

1. **Compact Signatures**: Uses EIP-2098 compact signatures (r, vs) for gas efficiency
2. **Nonce Protection**: Each modification increments the nonce, preventing replay attacks
3. **Supplier Authorization**: Only the original supplier can modify their consent
4. **Agreement Validation**: Consent can only be created for existing agreements (verified by calling `getAgreementData`)
5. **Revocation Control**: Revocation eligibility is enforced by the parent Agreement contract
6. **Digest Tracking**: Prevents duplicate consent records with identical parameters
7. **Flexible Agreement Reference**: Each consent stores a reference to its Agreement contract, allowing consents to reference agreements from different Agreement contract deployments
8. **Timestamp Recording**: `createdAt` is recorded for grace period calculations

***

### Related Contracts

* **Agreement**: Parent contract that defines consent terms and revocation rules
* **IAgreement**: Interface for Agreement contract
* **DIDRegistry**: Identity management for suppliers

***

### Deployment

The contract is deployed using Hardhat Ignition:

```typescript
// ignition/modules/Consent.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import Agreement from "./Agreement.js";

const DOMAIN_NAME = "Consent";
const DOMAIN_VERSION = "1";

export default buildModule("ConsentModule", (m) => {
  const { agreement } = m.useModule(Agreement);
  const consent = m.contract("Consent", [DOMAIN_NAME, DOMAIN_VERSION], { after: [agreement] });

  return { consent };
});
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.permission.ai/permission-protocol/v1-protocol/contracts/consent.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
