> ## Documentation Index
> Fetch the complete documentation index at: https://docs.echo.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Contracts

> Build your own sale contract with Sonar purchase permit validation.

## Overview

For most sales, Sonar deploys and manages the [SettlementSale](/sonar/reference/contracts/settlement-sale) contract for you. However, if you have specific requirements not covered by the standard contract, you can build a custom contract that integrates with Sonar's compliance infrastructure.

### When to Build a Custom Contract

* You need custom onchain logic beyond what Sonar provides
* You want to integrate token sale logic with other contract functionality
* You have specific requirements for how commitments are structured
* You want to extend the standard contract with additional features

### Deployment Models

Sonar supports different levels of customization:

| Model            | Contract                     | Settlement                                                           |
| ---------------- | ---------------------------- | -------------------------------------------------------------------- |
| **Standard**     | Sonar deploys SettlementSale | You determine allocations via the dashboard; Sonar syncs to contract |
| **Fully Custom** | You deploy custom contract   | You determine and write allocations directly to contract             |

### The Core Integration

Your custom contract must validate **purchase permits**—signed authorizations from Sonar that prove a wallet is eligible to participate. Sonar handles all compliance (KYC/KYB, accreditation, jurisdiction checks) and issues permits for eligible participants.

The permit contains:

* Entity identity and authorization proof
* Purchase limits (min/max amounts)
* Price limits (for auctions)
* Expiration timestamp

## Purchase Permit

Sonar generates permits with this structure (see also the [sonar contract repo](https://github.com/sunrisedotdev/sonar/blob/main/contracts/src/permits/PurchasePermitV3.sol)):

```solidity theme={null}
struct PurchasePermitV3 {
    bytes16 saleSpecificEntityID;  // Sale-specific entity identifier (same entity has different IDs per sale)
    bytes16 saleUUID;              // Your Sale UUID
    address wallet;                // Authorized wallet address
    uint64 expiresAt;              // Unix timestamp expiration
    uint256 minAmount;             // Minimum commitment amount
    uint256 maxAmount;             // Maximum commitment amount
    uint64 minPrice;               // Minimum bid price (for auctions)
    uint64 maxPrice;               // Maximum bid price (for auctions)
    uint64 opensAt;                // Unix timestamp when permit becomes valid (inclusive)
    uint64 closesAt;               // Unix timestamp when permit stops being valid (exclusive)
    bytes payload;                 // Additional data (e.g., forcedLockup)
}
```

### Validation

To ensure the validity of a purchase permit, you need to verify the following aspects (see also the [example implementation](#example-validation) below).
Please make sure to **implement all of these checks** to ensure correct enforcement of the requirements of your sale.

1. The ECDSA signature was issued by the Sonar signer
2. The permit's sale UUID matches yours
3. The permit is not expired
4. The current time is within the permit's time window (`opensAt` \<= now \< `closesAt`)
5. The sender matches the wallet address in the permit
6. The commitment amount is within the minimum and maximum amount limits
7. The bid price is within the minimum and maximum price limits (for auctions)

### Example Validation

These snippets are based on the [example sale contract](https://github.com/sunrisedotdev/sonar/blob/main/contracts/src/ExampleSale.sol).

#### 1. Signature Verification

You must verify that permits are signed by Sonar's authorized signer:

```solidity theme={null}
// Grant this role to the signer address provided by Sonar
bytes32 public constant PURCHASE_PERMIT_SIGNER_ROLE = keccak256("PURCHASE_PERMIT_SIGNER_ROLE");

// Verify signature in your purchase function
address recoveredSigner = PurchasePermitV3Lib.recoverSigner(permit, signature);
if (!hasRole(PURCHASE_PERMIT_SIGNER_ROLE, recoveredSigner)) {
    revert PurchasePermitUnauthorizedSigner(recoveredSigner);
}
```

#### 2. Sale UUID Validation

Prevent permit reuse across different sales:

```solidity theme={null}
if (permit.saleUUID != saleUUID) {
    revert PurchasePermitSaleUUIDMismatch(permit.saleUUID, saleUUID);
}
```

#### 3. Expiration Checks

Permits have short expiration times:

```solidity theme={null}
if (permit.expiresAt <= block.timestamp) {
    revert PurchasePermitExpired();
}
```

#### 4. Time Window Validation

Permits include a time window during which they are valid for commitments:

```solidity theme={null}
if (block.timestamp < permit.opensAt) {
    revert PurchasePermitNotYetValid();
}
if (block.timestamp >= permit.closesAt) {
    revert PurchasePermitWindowClosed();
}
```

#### 5. Wallet Authorization

Ensure the permit is used by the authorized wallet:

```solidity theme={null}
if (permit.wallet != msg.sender) {
    revert PurchasePermitSenderMismatch(msg.sender, permit.wallet);
}
```

## Integration Patterns

### Wallet-Level Tracking (Basic)

The example contract tracks purchases by wallet address:

```solidity theme={null}
// Track amount purchased by each wallet
mapping(address => uint256) public amountByAddress;

function purchase(uint256 amount, /* permit params */) external {
    _validatePurchasePermit(purchasePermit, purchasePermitSignature);
    
    uint256 newTotalAmount = amountByAddress[msg.sender] + amount;
    
    // Validate purchase limits
    if (newTotalAmount < purchasePermit.minAmount) {
        revert AmountBelowMinimum(newTotalAmount, purchasePermit.minAmount);
    }
    if (newTotalAmount > purchasePermit.maxAmount) {
        revert AmountExceedsMaximum(newTotalAmount, purchasePermit.maxAmount);
    }
    
    amountByAddress[msg.sender] = newTotalAmount;
}
```

### Entity-Level Tracking

One might want to additionally enforce limits by entity, across all wallets they can use:

```solidity theme={null}
// Track amount purchased by each entity
mapping(bytes16 => uint256) public amountByEntity;

function purchase(uint256 amount, /* permit params */) external {
    _validatePurchasePermit(purchasePermit, purchasePermitSignature);

    bytes16 entityID = purchasePermit.saleSpecificEntityID;
    uint256 newTotalAmount = amountByEntity[entityID] + amount;

    if (newTotalAmount > purchasePermit.maxAmount) {
        revert AmountExceedsMaximum(newTotalAmount, purchasePermit.maxAmount);
    }

    amountByEntity[entityID] = newTotalAmount;
}
```

### Auction bid price validation

If you are running a sale with a dynamic pricing model where the user can commit with a requested price (e.g. auctions),
you should validate the bid price is within the minimum and maximum price limits.

```solidity theme={null}
struct Bid {
    uint64 price;
    uint256 amount;
}

mapping(address => Bid) internal bidByAddress;

function processBid(uint64 price, uint256 amount, /* permit params */) external {
    _validatePurchasePermit(purchasePermit, purchasePermitSignature);

    if (price > purchasePermit.maxPrice) {
        revert BidPriceExceedsMaxPrice(price, purchasePermit.maxPrice);
    }

    if (price < purchasePermit.minPrice) {
        revert BidPriceBelowMinPrice(price, purchasePermit.minPrice);
    }

    if (amount < purchasePermit.minAmount) {
        revert BidBelowMinAmount(amount, purchasePermit.minAmount);
    }

    if (amount > purchasePermit.maxAmount) {
        revert BidExceedsMaxAmount(amount, purchasePermit.maxAmount);
    }
    
    // Replace the existing bid
    bidByAddress[msg.sender] = Bid(price, amount);
}
```

## Getting the Signer Address

Sonar signs all purchase permits using a global infrastructure key. When deploying your contract, grant the `PURCHASE_PERMIT_SIGNER_ROLE` to this address.

**For EVM chains:** `0x9a1964e04d10ccfe1a9ce300d943f71c6e3ae24c`

For other chains, contact [support@echo.xyz](mailto:support@echo.xyz) to get the correct signer address.

<Note>
  The permit signer role is always held by Sonar's infrastructure. This ensures all purchase authorizations flow through Sonar's compliance verification, maintaining the integrity of the permit system.
</Note>

## See Also

<CardGroup cols={2}>
  <Card title="SettlementSale Reference" href="/sonar/reference/contracts/settlement-sale" icon="file-contract">
    Reference for the standard Sonar sale contract
  </Card>

  <Card title="Frontend Integration" href="/sonar/integration-guides/frontend-overview" icon="browser">
    Generate and use purchase permits in your frontend
  </Card>

  <Card title="Purchase Permits" href="/sonar/core-features/purchase-permits" icon="shield-check">
    Deep dive into permit structure and validation
  </Card>

  <Card title="Example Contract" href="https://github.com/sunrisedotdev/sonar/blob/main/contracts/src/ExampleSale.sol" icon="github">
    View the example sale contract on GitHub
  </Card>
</CardGroup>
