# Agreement

### Overview

The **Agreement** contract is a core component of the Permission Protocol that manages consent agreements between parties. It uses EIP-712 typed data signatures for secure, gasless agreement creation and prevents replay attacks through digest tracking.

### Contract Details

| Property         | Value                          |
| ---------------- | ------------------------------ |
| Solidity Version | ^0.8.28                        |
| License          | MIT                            |
| Inheritance      | EIP712, Context (OpenZeppelin) |
| Implements       | IAgreement                     |

***

### Purpose

The Agreement contract serves as the foundation for creating legally-binding consent agreements on-chain. It enables:

* **Typed Data Signing**: Counterparties sign agreements off-chain using EIP-712
* **Replay Protection**: Each unique agreement can only be created once
* **Flexible Terms**: Supports multiple purpose keys and conditions
* **Revocation Control**: Configurable revocation eligibility with optional grace periods
* **Human-Readable Data**: Provides parsed agreement data with string conversions
* **Batch Operations**: Create multiple agreements in a single transaction

***

### Data Structures

#### RevokeEligibility Enum

Defines the revocation rules for an agreement.

```solidity
enum RevokeEligibility {
    UnRevokable,              // Cannot be revoked
    InstantlyRevokable,       // Can be revoked immediately
    RevokableAfterGracePeriod // Can only be revoked after grace period passes
}
```

| Value                       | Description                                              |
| --------------------------- | -------------------------------------------------------- |
| `UnRevokable`               | Consent records cannot be revoked                        |
| `InstantlyRevokable`        | Consent records can be revoked at any time               |
| `RevokableAfterGracePeriod` | Consent records can only be revoked after a grace period |

#### AgreementData (IAgreement Interface)

The primary structure for storing agreement details, defined in the `IAgreement` interface.

```solidity
struct AgreementData {
    bytes32 kind;                      // Type of agreement (encoded as bytes32)
    bytes32[] purpose;                 // List of purpose keys
    bytes32 termsHash;                 // Hash of the terms content
    bytes32 conditions;                // Hash of specific conditions or limitations
    address counterParty;              // Address of the other party
    uint64 revokeGracePeriodSeconds;   // Waiting period before revocation is allowed
    RevokeEligibility revokeEligibility; // Revocation rules
    string termsRef;                   // Reference URL to full terms
}
```

| Field                      | Type              | Description                                                       |
| -------------------------- | ----------------- | ----------------------------------------------------------------- |
| `kind`                     | bytes32           | The type/category of the agreement                                |
| `purpose`                  | bytes32\[]        | Array of purpose keys defining data usage                         |
| `termsHash`                | bytes32           | Keccak256 hash of the full terms document                         |
| `conditions`               | bytes32           | Hash of additional conditions/limitations                         |
| `counterParty`             | address           | The party consenting to the agreement                             |
| `revokeGracePeriodSeconds` | uint64            | Seconds to wait before revocation (for RevokableAfterGracePeriod) |
| `revokeEligibility`        | RevokeEligibility | Rules for when/if the agreement can be revoked                    |
| `termsRef`                 | string            | URL or IPFS hash pointing to full terms                           |

#### ParsedAgreementData

Human-readable version with strings instead of bytes32.

```solidity
struct ParsedAgreementData {
    string kind;                       // Type as readable string
    string[] purpose;                  // Purpose keys as strings
    bytes32 termsHash;                 // Hash of terms (unchanged)
    bytes32 conditions;                // Conditions hash (unchanged)
    address counterParty;              // Counterparty address
    uint64 revokeGracePeriodSeconds;   // Grace period in seconds
    RevokeEligibility revokeEligibility; // Revocation rules
    string termsRef;                   // Reference URL
}
```

#### AgreementInput

Input structure for batch agreement creation.

```solidity
struct AgreementInput {
    bytes32 kind;                      // The agreement kind
    bytes32[] purpose;                 // Array of purpose keys
    bytes32 termsHash;                 // Hash of the terms
    bytes32 conditions;                // The conditions hash
    address counterParty;              // The counterparty address
    uint64 revokeGracePeriodSeconds;   // Grace period for revocation
    RevokeEligibility revokeEligibility; // Revocation eligibility
    string termsRef;                   // Reference to the terms
    bytes signature;                   // The EIP-712 signature from the counterparty
}
```

***

### State Variables

| Variable           | Type                              | Visibility | Description                                               |
| ------------------ | --------------------------------- | ---------- | --------------------------------------------------------- |
| `agreementCounter` | uint256                           | public     | Counter for generating unique agreement IDs (starts at 1) |
| `agreements`       | mapping(uint256 => AgreementData) | public     | Storage mapping of agreement ID to data                   |
| `usedDigests`      | mapping(bytes32 => uint256)       | public     | Tracks used EIP-712 digests to agreement IDs              |

***

### Functions

#### Constructor

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

Initializes the contract with EIP-712 domain separator parameters.

**Parameters:**

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

***

#### createAgreement

```solidity
function createAgreement(
    bytes32 kind,
    bytes32[] calldata purpose,
    bytes32 termsHash,
    bytes32 conditions,
    address counterParty,
    uint64 revokeGracePeriodSeconds,
    RevokeEligibility revokeEligibility,
    string calldata termsRef,
    bytes calldata signature
) external returns (uint256 agreementId)
```

Creates a new agreement with EIP-712 signature verification.

**Parameters:**

| Name                       | Type              | Description                            |
| -------------------------- | ----------------- | -------------------------------------- |
| `kind`                     | bytes32           | The agreement type                     |
| `purpose`                  | bytes32\[]        | Array of purpose keys                  |
| `termsHash`                | bytes32           | Hash of terms content                  |
| `conditions`               | bytes32           | Conditions hash                        |
| `counterParty`             | address           | Address that must sign (or msg.sender) |
| `revokeGracePeriodSeconds` | uint64            | Grace period before revocation allowed |
| `revokeEligibility`        | RevokeEligibility | Revocation rules                       |
| `termsRef`                 | string            | Reference to full terms                |
| `signature`                | bytes             | EIP-712 signature from counterParty    |

**Returns:**

| Name          | Type    | Description                            |
| ------------- | ------- | -------------------------------------- |
| `agreementId` | uint256 | The unique ID of the created agreement |

**Requirements:**

* `kind` cannot be empty (bytes32(0))
* If `counterParty` != `msg.sender`, signature must be valid
* Agreement with same parameters cannot already exist

**Emits:** `AgreementCreated`

***

#### batchCreateAgreements

```solidity
function batchCreateAgreements(
    AgreementInput[] calldata inputs
) external returns (uint256[] memory agreementIds)
```

Creates multiple agreements in a single transaction.

**Parameters:**

| Name     | Type              | Description               |
| -------- | ----------------- | ------------------------- |
| `inputs` | AgreementInput\[] | Array of agreement inputs |

**Returns:**

| Name           | Type       | Description                    |
| -------------- | ---------- | ------------------------------ |
| `agreementIds` | uint256\[] | Array of created agreement IDs |

**Requirements:**

* Input array must not be empty

**Emits:** `AgreementCreated` for each agreement

***

#### isRevokable

```solidity
function isRevokable(
    uint256 agreementId,
    uint64 createdAt
) public view returns (bool)
```

Checks if a consent record for this agreement can be revoked based on the agreement's revocation rules.

**Parameters:**

| Name          | Type    | Description                                   |
| ------------- | ------- | --------------------------------------------- |
| `agreementId` | uint256 | The agreement identifier                      |
| `createdAt`   | uint64  | Timestamp when the consent record was created |

**Returns:**

| Name   | Type | Description                                        |
| ------ | ---- | -------------------------------------------------- |
| (bool) | bool | `true` if revocation is allowed, `false` otherwise |

**Logic:**

* `UnRevokable`: Always returns `false`
* `InstantlyRevokable`: Always returns `true`
* `RevokableAfterGracePeriod`: Returns `true` only if `(block.timestamp - createdAt) >= revokeGracePeriodSeconds`

**Reverts:** `AgreementNotFound` if agreement doesn't exist

***

#### getAgreementData

```solidity
function getAgreementData(uint256 agreementId) public view returns (AgreementData memory)
```

Retrieves raw agreement data by ID.

**Parameters:**

| Name          | Type    | Description              |
| ------------- | ------- | ------------------------ |
| `agreementId` | uint256 | The agreement identifier |

**Returns:** `AgreementData` struct

**Reverts:** `AgreementNotFound` if agreement doesn't exist

***

#### getParsedAgreementData

```solidity
function getParsedAgreementData(uint256 agreementId) public view returns (ParsedAgreementData memory)
```

Returns agreement data with human-readable strings.

**Parameters:**

| Name          | Type    | Description              |
| ------------- | ------- | ------------------------ |
| `agreementId` | uint256 | The agreement identifier |

**Returns:** `ParsedAgreementData` struct with kind and purpose as strings

**Reverts:** `AgreementNotFound` if agreement doesn't exist

***

#### hashAgreementData

```solidity
function hashAgreementData(
    bytes32 kind,
    bytes32[] calldata purpose,
    bytes32 termsHash,
    bytes32 conditions,
    address counterParty,
    uint64 revokeGracePeriodSeconds,
    RevokeEligibility revokeEligibility,
    string calldata termsRef
) public pure returns (bytes32)
```

Computes the EIP-712 struct hash for agreement data.

**Returns:** The keccak256 hash of the encoded struct

***

#### getTypedDataHash

```solidity
function getTypedDataHash(
    bytes32 kind,
    bytes32[] calldata purpose,
    bytes32 termsHash,
    bytes32 conditions,
    address counterParty,
    uint64 revokeGracePeriodSeconds,
    RevokeEligibility revokeEligibility,
    string calldata termsRef
) public view returns (bytes32)
```

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

**Returns:** The hash that should be signed by the counterparty

***

### Events

#### AgreementCreated

```solidity
event AgreementCreated(
    uint256 indexed agreementId,
    bytes32 kind,
    bytes32[] purpose,
    bytes32 termsHash,
    bytes32 conditions,
    address counterParty,
    uint64 revokeGracePeriodSeconds,
    RevokeEligibility revokeEligibility,
    string termsRef
);
```

Emitted when a new agreement is successfully created.

***

### Errors

| Error                             | Description                                            |
| --------------------------------- | ------------------------------------------------------ |
| `AgreementNotFound()`             | The requested agreement ID does not exist (IAgreement) |
| `Unauthorized()`                  | Caller is not authorized for the action                |
| `InvalidSignature()`              | The provided signature is invalid or from wrong signer |
| `AgreementAlreadyExists(uint256)` | An agreement with the same parameters already exists   |
| `EmptyBatchInput()`               | The batch input array is empty                         |

***

### EIP-712 Type Definition

The contract uses the following EIP-712 type for signing:

```javascript
const types = {
  AgreementData: [
    { name: "kind", type: "bytes32" },
    { name: "purpose", type: "bytes32[]" },
    { name: "termsHash", type: "bytes32" },
    { name: "conditions", type: "bytes32" },
    { name: "counterParty", type: "address" },
    { name: "revokeGracePeriodSeconds", type: "uint64" },
    { name: "revokeEligibility", type: "uint8" },
    { name: "termsRef", type: "string" },
  ],
};
```

***

### Usage Examples

#### Creating an Agreement (TypeScript/Viem)

```typescript
import { stringToHex, keccak256 } from "viem";

// RevokeEligibility enum values
const RevokeEligibility = {
  UnRevokable: 0,
  InstantlyRevokable: 1,
  RevokableAfterGracePeriod: 2,
};

// Prepare agreement data
const kind = stringToHex("data-sharing", { size: 32 });
const purpose = [stringToHex("analytics", { size: 32 }), stringToHex("marketing", { size: 32 })];
const termsHash = keccak256(stringToHex("Full terms content here"));
const conditions = stringToHex("30-day-retention", { size: 32 });
const counterParty = "0x..."; // Counterparty address
const revokeGracePeriodSeconds = 86400n; // 24 hours (only used if RevokableAfterGracePeriod)
const revokeEligibility = RevokeEligibility.InstantlyRevokable;
const termsRef = "https://example.com/terms/v1";

// Create EIP-712 typed data
const typedData = {
  domain: {
    name: "Agreement",
    version: "1",
    chainId: 1n,
    verifyingContract: agreementContractAddress,
  },
  types: {
    AgreementData: [
      { name: "kind", type: "bytes32" },
      { name: "purpose", type: "bytes32[]" },
      { name: "termsHash", type: "bytes32" },
      { name: "conditions", type: "bytes32" },
      { name: "counterParty", type: "address" },
      { name: "revokeGracePeriodSeconds", type: "uint64" },
      { name: "revokeEligibility", type: "uint8" },
      { name: "termsRef", type: "string" },
    ],
  },
  primaryType: "AgreementData",
  message: {
    kind,
    purpose,
    termsHash,
    conditions,
    counterParty,
    revokeGracePeriodSeconds,
    revokeEligibility,
    termsRef,
  },
};

// Counterparty signs
const signature = await counterPartyWallet.signTypedData(typedData);

// Create the agreement on-chain
const agreementId = await agreementContract.write.createAgreement([
  kind,
  purpose,
  termsHash,
  conditions,
  counterParty,
  revokeGracePeriodSeconds,
  revokeEligibility,
  termsRef,
  signature,
]);
```

#### Creating an UnRevokable Agreement

```typescript
// For agreements that should never be revoked
const agreementId = await agreementContract.write.createAgreement([
  kind,
  purpose,
  termsHash,
  conditions,
  counterParty,
  0n, // revokeGracePeriodSeconds not used for UnRevokable
  RevokeEligibility.UnRevokable,
  termsRef,
  signature,
]);
```

#### Creating an Agreement with Grace Period

```typescript
// For agreements that can only be revoked after a waiting period
const sevenDaysInSeconds = 7n * 24n * 60n * 60n; // 604800 seconds

const agreementId = await agreementContract.write.createAgreement([
  kind,
  purpose,
  termsHash,
  conditions,
  counterParty,
  sevenDaysInSeconds, // Must wait 7 days before consent can be revoked
  RevokeEligibility.RevokableAfterGracePeriod,
  termsRef,
  signature,
]);
```

#### Checking Revocation Eligibility

```typescript
// Get the consent record's createdAt timestamp
const consentRecord = await consentContract.read.getConsentRecord([consentRecordId]);
const createdAt = consentRecord.createdAt;

// Check if revocation is allowed
const canRevoke = await agreementContract.read.isRevokable([agreementId, createdAt]);

if (canRevoke) {
  // Proceed with revocation
} else {
  // Revocation not allowed yet (or never allowed)
}
```

#### Batch Creating Agreements

```typescript
// Prepare multiple agreement inputs
const agreementInputs = [
  {
    kind: stringToHex("data-sharing", { size: 32 }),
    purpose: [stringToHex("analytics", { size: 32 })],
    termsHash: keccak256(stringToHex("Terms 1")),
    conditions: stringToHex("condition-1", { size: 32 }),
    counterParty: counterParty1Address,
    revokeGracePeriodSeconds: 0n,
    revokeEligibility: RevokeEligibility.InstantlyRevokable,
    termsRef: "https://example.com/terms/1",
    signature: signature1,
  },
  {
    kind: stringToHex("licensing", { size: 32 }),
    purpose: [stringToHex("commercial-use", { size: 32 })],
    termsHash: keccak256(stringToHex("Terms 2")),
    conditions: stringToHex("condition-2", { size: 32 }),
    counterParty: counterParty2Address,
    revokeGracePeriodSeconds: 604800n, // 7 days
    revokeEligibility: RevokeEligibility.RevokableAfterGracePeriod,
    termsRef: "https://example.com/terms/2",
    signature: signature2,
  },
];

// Create all agreements in one transaction
const agreementIds = await agreementContract.write.batchCreateAgreements([agreementInputs]);
```

#### Reading Agreement Data

```typescript
// Get raw data
const agreementData = await agreementContract.read.getAgreementData([1n]);
console.log(agreementData.revokeEligibility); // 0, 1, or 2
console.log(agreementData.revokeGracePeriodSeconds); // Grace period in seconds

// Get human-readable data
const parsed = await agreementContract.read.getParsedAgreementData([1n]);
console.log(parsed.kind); // "data-sharing"
console.log(parsed.purpose); // ["analytics", "marketing"]
```

***

### Security Considerations

1. **Replay Protection**: Each unique agreement can only be created once via digest tracking
2. **Signature Verification**: Counterparty signatures are verified using EIP-712 ECDSA recovery
3. **Self-Signing**: If `counterParty` equals `msg.sender`, no signature verification is needed
4. **Immutability**: Once created, agreements cannot be modified or deleted
5. **Revocation Control**: The `revokeEligibility` setting is immutable and enforced by the Consent contract

***

### Related Contracts

* **IAgreement**: Interface defining AgreementData struct and core functions
* **Consent**: Creates consent records referencing agreements
* **DIDRegistry**: Manages decentralized identities for parties

***

### Deployment

The contract is deployed using Hardhat Ignition:

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

export default buildModule("AgreementModule", (m) => {
  const agreement = m.contract("Agreement", ["Agreement", "1"]);
  return { agreement };
});
```


---

# 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/agreement.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.
