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.
Deployed addresses differ per environment and are not listed here.
Vocabulary
The contracts predate the product’s current names: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.
- 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
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:- A click id can be moved once (
AlreadyMoved) and converted once (AlreadyConverted). recordUserMovementrefuses 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.registerSettlementrequires a conversion, must match the stored campaign and seller, and is refused after a return. It setsisSettledand emitsConversionSettled. Settled means vested, not paid.registerPartialReturnrecords 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
recordUserMovementsays “first-click attribution” and the function returns a value namedisFirstClick. The platform is last-click: the backend picks the winning click before calling, and the function records one movement per click id and always returnstrue. - The campaign
returnWindowonly 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.
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.
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 blocksclaim, 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.
stakechecks the contract received exactlyamount, so fee-on-transfer tokens are rejected.
getPosition(account), isActive(account), isClaimable(account), totalStaked() and lockDuration(period). Events: Staked, Restaked, Claimed, Slashed, TreasuryUpdated, MinStakeAmountUpdated.
The subgraph
A subgraph (insubgraph/ 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.