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

# Smart contracts

> The platform keeps a public, tamper-evident record of attribution on an EVM-compatible chain.

The platform keeps a public, tamper-evident record of attribution on an EVM-compatible chain. This page describes what that record contains, who can write to it, and, just as important, what is deliberately **not** on-chain.

Contract sources live under `contracts/` in the backend repository (Solidity 0.8.28, built and tested with Hardhat). There are three top-level contracts; everything else under `contracts/` is abstracts, interfaces, libraries and test mocks that compose into them.

| Contract | Purpose | Upgradeable |
| - | - | - |
| `UAMRegistryClean` | Campaigns, member creators and the attribution lifecycle | Yes (UUPS proxy) |
| `UAMAccessControl` | Roles and the global pause switch | No |
| `FreeqStaking` | Time-locked FREEQ staking | No |

Deployed addresses differ per environment and are not listed here.

## Vocabulary

The contracts predate the product's current names:

| Contract | API and database | Product UI |
| - | - | - |
| buyer | advertiser | brand |
| seller | publisher | creator |
| `addSellers()` | `addPublishers()` | invite or add creators |
| party | party (`Party`) | account |

## What is on-chain, and what is not

**On-chain:** campaign records (owner, timing, return window), which creators belong to which campaign, and for each click that led to a sale: the movement, the conversion, any return, partial return or chargeback, and the settlement.

**Not on-chain, by design:**

* **Money.** The registry has no funding, fee, escrow, withdrawal or payout function, and rejects direct ETH. Funds move through Stripe and the platform's ledger. See [Money path](https://docs.higherfrequency.io/developers/money-path.md).
* **Every click.** Clicks are recorded off-chain. Only the click that wins an order is written, and only after the order is attributed.
* **Shopper identity.** The "user" on each record is a pseudo-address derived by hashing the click id. It is not a wallet, nobody holds a key for it, and it carries no personal data.
* **Order contents and prices.** The chain records that a conversion happened, not what was bought.

## UAMRegistryClean

### Writes

| Function | Who may call | What it records |
| - | - | - |
| `createCampaign` | A registered buyer | A campaign (attribution only, no funding) |
| `setCampaignReturnWindow`, `extendCampaign` | The campaign's buyer, while active | Return window, longer end time |
| `endActiveCampaign` | The buyer or an admin | End now; completion after the grace period |
| `addSellers`, `removeSellers` | The buyer or an admin | Campaign membership (sellers must already be registered parties) |
| `recordUserMovement` | Admin | The winning click for an order |
| `registerConversion` | Admin | That the click converted |
| `registerSettlement` | Admin | That the commission vested |
| `registerReturn`, `registerPartialReturn` | Admin | A full or partial refund |
| `registerChargeBack` | Admin | A chargeback |
| `addParty`, `addParties`, `addTag`, `setMaxCampaignDuration`, `setGracePeriod` | Admin | Registry configuration |

Every write is blocked while the system is paused. Do not assume every write is admin-only: check the modifier on the specific function.

In practice the platform's backend submits all of these transactions. Users never sign anything; brands and creators get platform-managed addresses when they register.

### The attribution lifecycle

Each attribution is keyed by the **click id** string:

```
recordUserMovement ──► registerConversion ──► registerSettlement
                               │
                               ├──► registerReturn / registerPartialReturn
                               └──► registerChargeBack
```

* A click id can be moved once (`AlreadyMoved`) and converted once (`AlreadyConverted`).
* `recordUserMovement` refuses a creator who is not a campaign member and a campaign that is not active, except on the grace paths for a click that happened while both were still valid. This is how late conversions after a campaign ends are honoured.
* `registerSettlement` requires a conversion, must match the stored campaign and seller, and is refused after a return. It sets `isSettled` and emits `ConversionSettled`. **Settled means vested, not paid.**
* `registerPartialReturn` records the refunded share in basis points.

### Events

`CampaignCreated`, `CampaignStateChanged`, `CampaignTimesUpdated`, `CampaignReturnWindowSet`, `SellerAdded`, `SellerRemoved`, `PartyAdded`, `TagCreated`, `UserMoved`, `UserConverted`, `ConversionSettled`, `ReturnRegistered`, `PartialReturnRegistered`, `ChargeBackRegistered`, plus configuration events. These are what the subgraph indexes.

### Traps

* The NatSpec on `recordUserMovement` says "first-click attribution" and the function returns a value named `isFirstClick`. The platform is **last-click**: the backend picks the winning click before calling, and the function records one movement per click id and always returns `true`.
* The campaign `returnWindow` only gates the on-chain return call. It is **not** the escrow hold that protects money.
* Upgrades are authorised only for the deployer role and only while the system is paused.

## UAMAccessControl

An OpenZeppelin-style role contract shared by the registry and the staking contract.

| Role | Holder | Can |
| - | - | - |
| `DEFAULT_ADMIN_ROLE` | Platform admin | Grant and revoke roles |
| `ADMIN_ROLE` | The platform's backend signer | All `onlyAdmin` operations |
| `DEPLOYER_ROLE` | The deployer | `pause()`, `unpause()`, registry upgrades |

The pause flag lives here and is read by both other contracts. This contract is not a proxy and is not upgradeable.

## FreeqStaking

Time-locked staking of the FREEQ token. Staking gives brands an alternative to a paid plan for platform access, and lets creators claim reserve balances early. See [Staking FREEQ](https://docs.higherfrequency.io/creators/staking-freeq.md).

| Function | Who | Effect |
| - | - | - |
| `stake(amount, period)` | Any wallet without an active position | Locks `amount` for `Quarterly` (90 days) or `Annual` (365 days) |
| `restake(period)` | The staker, after unlock | Starts a new lock with the same amount |
| `claim()` | The staker, after unlock | Returns the full principal |
| `slash(account)` | Admin | Sends the position's principal to the treasury |
| `setTreasury`, `setMinStakeAmount` | Admin | Configuration |

Rules:

* **One active position per wallet.** No top-ups, no partial withdrawals, no early exit.
* **No rewards.** The contract returns principal only.
* **Pause blocks new locks only** (`stake`, `restake`). It never blocks `claim`, so funds cannot be trapped by a pause.
* **Not upgradeable.** It holds user funds, so its rules are fixed at deploy. The token and access-control addresses are immutable.
* `stake` checks the contract received exactly `amount`, so fee-on-transfer tokens are rejected.

Read state with `getPosition(account)`, `isActive(account)`, `isClaimable(account)`, `totalStaked()` and `lockDuration(period)`. Events: `Staked`, `Restaked`, `Claimed`, `Slashed`, `TreasuryUpdated`, `MinStakeAmountUpdated`.

## The subgraph

A subgraph (in `subgraph/` in the repository) indexes the registry's events into entities such as `Campaign`, `Seller`, `CampaignSeller` and `Attribution`, with per-campaign and per-creator counters (users moved, converted, returned, charged back). After sending a transaction, the backend can poll the subgraph to read derived state. The subgraph is a convenience read model; the database and ledger remain the source of truth for anything involving money.

## Working with the contracts

```bash theme={null}
pnpm hardhat compile    # from the repository root
pnpm hardhat test       # contract test suite
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.