Skip to main content
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. 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).
  • 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. 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 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