Architecture

Le protocole vit sur deux chaînes. Ethereum porte le launchpad, les pools et les vaults ; Robinhood Chain porte les actions tokenisées.

graph TD subgraph ETH[Ethereum] F[StockFunFactory] -->|déploie| TK[StockFunToken] F -->|déploie| V[TreasuryVault] F --> LL[LiquidityLock] TK -->|chaque transfert| HR[HoldingRecorder] LL -->|deux positions verrouillées| PM[Uniswap v4 PoolManager] PM --> H[StockFunHook] H -->|2 %| V H -->|2 %| CR[Créateur] H -->|0,5 %| TW[Wallet équipe] H -->|0,5 %| BB[BuybackBurner] BB -->|achat + burn| SF[$STOCKFUN] V -->|ETH → USDC → USDG| BH[BridgeHub] V -->|sendToAirdrop, rail local| AD[AirdropDistributor] AD -.lecture.-> HR AD -->|réclamations, au prorata| HO[Holders du token] L[StockFunLens] -.lecture.-> F O[TreasuryOracle] -.bornes.-> V end subgraph RH[Robinhood Chain] RH2[RemoteHub] --> MV[Vault miroir par marché] MV -->|pools secondaires| ST[Stock Tokens] end BH -->|LayerZero, OFT USDG| RH2 ST -->|sendToAirdrop, OFT des actions| AD K[Keeper offchain] -.déclenche.-> V K -.déclenche.-> BH

Les contrats

Ce tableau décrit le protocole tel qu'il a été décidé. La partie lancement est dans le code depuis le 2026-09-28, la distribution de l'airdrop depuis le 2026-10-04, et l'étape du keeper et l'écran de réclamation depuis le 2026-10-05, rien n'étant déployé ; les adaptateurs LayerZero des actions ne sont pas dans le dépôt. Les routeurs et adaptateurs autres que le routeur de swap officiel n'y figurent pas : UniswapV4StockRouter sur Ethereum, RobinhoodStockRouter sur Robinhood Chain et l'UsdgOftAdapter. Voir État d'avancement. Depuis le 2026-10-02, chaque contrat du tableau est upgradable, sauf les tokens, le lock de liquidité et les déployeurs : voir plus bas. Les pourcentages du diagramme sont les réglages par défaut de la taxe.

Contrat Rôle Cardinalité
StockFunFactory Crée les marchés, perçoit les frais de création, tient le registre et les réglages de l'owner pour les nouveaux marchés et pour les vaults ; l'autorité d'upgrade de chaque module Ethereum Un
MarketDeployer · VaultDeployer Contournent la limite EIP-170 en portant le creation code du token et celui du proxy du vault ; sans état, remplacés via la factory plutôt qu'upgradés Un chacun
StockFunToken ERC-20 simple, non upgradable, sans mint et sans burn ; aucune logique propre au-delà d'un setter, setRecorder, et, depuis le 2026-10-05, de ses rescues, tous à l'owner du protocole ; déclare chaque changement de solde à l'enregistreur de détention Un par marché
HoldingRecorder Enregistre, pour chaque token, la détention de chaque adresse dans le temps et la supply hors du PoolManager d'Uniswap ; un transfert échoue si son enregistrement échoue, la seule exception voulue à la règle d'isolation Un
TreasuryVault Détient ETH, USDC puis actions ; conversion, chaque jambe à part, remise de ses actions à l'airdrop sur le rail local (sendToAirdrop), chaque action à part, et récupération d'urgence immédiate Un proxy par marché
LiquidityLock Crée le pool et y dépose les deux positions, verrouillées, et garde la commission de LP et le tick spacing de chaque pool ; garde pour son destinataire une part de frais de LP refusée (payOwed) ; non upgradable ; sa seule sortie est le mode fin, 30 jours après son annonce Un
StockFunHook Prélève la taxe sur chaque swap, applique l'anti-snipe, garde les réglages de la taxe et la whitelist de chaque pool, refuse la liquidité de quiconque sauf le lock, tient les soldes créateur, équipe et buyback jusqu'à leur réclamation, et ce qu'il doit à un vault qui a refusé sa part (payTreasury) Un
StockFunSwapRouter Le routeur officiel des trades, le seul par lequel une adresse de la whitelist est exemptée de l'anti-snipe Un, remplaçable par l'owner
StockFunLens Lecture seule, agrège l'état d'un marché pour la dapp ; depuis le 2026-10-05, lit chaque vault à part, par un appel plafonné en gas (readVault), si bien qu'un vault qui ne répond pas ne fait plus échouer la page : il est marqué illisible (vaultReadable), garde son ETH, son basket et ses soldes lus sur les tokens, et les totaux disent combien de vaults ils laissent de côté (unreadableVaults). Depuis le 2026-10-06, sa page porte aussi le rail, la pause et l'USDC passé par le pont du vault, l'état du lock du pool, le module d'enregistrement du token, et ce que le hook et le lock doivent au vault : le service de données de l'app ne lit plus rien d'autre par marché, si bien qu'un vault qui brûle son gas ne fait échouer que ses propres chiffres. Depuis la septième boucle d'audit, ce service lit aussi chaque module upgradable (le hook, l'oracle, le contrat de l'airdrop, les deux hubs du pont) et le token du protocole chacun dans son groupe : un module qui brûle son gas, après un upgrade raté, ne fait échouer que ses propres chiffres Un
TreasuryOracle Feeds Chainlink, inscrits une fois ; leurs heartbeats sont des réglages, et depuis le 2026-10-06 ses deux gardes sur Robinhood Chain : le contrôle du séquenceur, éteint tant que Chainlink ne publie pas de feed de disponibilité pour cette chaîne, et la pause d'oracle de chaque action, qui retient le prix de cette action pendant une opération sur titres Un par chaîne
BuybackBurner Rachète $STOCKFUN et l'envoie au burn. Aucun autre chemin pour son ETH tant que le pool $STOCKFUN est verrouillé Un
BridgeHub · RemoteHub · RemoteTreasuryVault Le rail cross-chain, chaque marché d'un batch à part ; le hub distant est l'autorité d'upgrade sur Robinhood Chain et désigne la route de l'airdrop et l'adaptateur de chaque action ; le vault miroir envoie ses actions à l'airdrop (sendToAirdrop) Un, un, un proxy par marché
StockFunProtocolToken $STOCKFUN, non upgradable comme un token de marché ; toute sa supply part dans sa position verrouillée Un
AirdropDistributor Distribue, sur Ethereum, les actions achetées par chaque trésorerie aux holders de son token, par cycle quotidien ; ne crédite que les vaults du marché ; chaque holder réclame ; lié à la factory, qui le désigne Un

Proxies et upgrades

Depuis le 2026-10-02, chaque module est un proxy ERC-1967 placé devant une implémentation, upgradé en UUPS. Le proxy détient l'adresse et l'état ; l'implémentation détient le code, et un upgrade la remplace. Une implémentation ne peut jamais être initialisée : chaque proxy est initialisé dans son propre constructeur.

Chaque module demande à un contrat qui peut l'upgrader, son autorité d'upgrade, fixée dans son implémentation : la factory sur Ethereum, qui répond par son owner, et le hub distant sur Robinhood Chain, qui répond par son admin d'urgence, l'owner Ethereum tel que l'a porté le dernier batch du pont. Un transfert de la propriété de la factory déplace donc d'un coup le pouvoir d'upgrade de chaque module Ethereum, et celui des modules de Robinhood Chain avec le batch suivant. Un upgrade est refusé si la nouvelle implémentation désigne une autre autorité ; celles du hook et de l'enregistreur de détention doivent aussi garder le même PoolManager.

Une implémentation n'est jamais utilisée directement. Depuis la cinquième boucle d'audit du 2026-10-05, celles de la factory et du hub distant, qui lisent leur admin dans le stockage du proxy, où personne ne le détient sur l'implémentation (l'owner de celle de la factory est l'adresse morte que pose son constructeur, l'admin de celle du hub vaut zéro), ont pour levier leur déployeur, pour un actif envoyé par erreur à leur propre adresse.

Non upgradables : les tokens, le lock de liquidité et, sur Robinhood Chain, le déployeur des vaults miroirs, de l'adresse duquel est dérivée celle de chaque vault miroir. MarketDeployer et VaultDeployer ne détiennent aucun état : la factory les remplace (setDeployers) plutôt que de les upgrader.

Le stockage s'allonge, il n'est jamais réordonné : la disposition de chaque module est enregistrée dans contracts/storage-layouts/, et contracts/script/check-storage-layouts.sh la compare avant chaque upgrade. Depuis le 2026-10-05, il compare chaque niveau de chaque structure, taille comprise, et tient pour immuable la structure d'un élément de tableau : seule une structure valeur d'un mapping, ou la dernière variable, peut grandir à sa fin.

Qui peut upgrader, et à quelle vitesse : voir Modèle de confiance.

Le contrat de l'airdrop

Depuis le 2026-10-04, AirdropDistributor distribue les actions de chaque trésorerie sur Ethereum. C'est un module upgradable comme les autres, un proxy dont l'autorité d'upgrade est la factory. La factory le désigne (setAirdropDistributor) et les vaults le lisent en direct ; un contrat remplacé garde chacun de ses cycles réclamable chez lui. Il lit la détention dans l'enregistreur de détention, et ses règles sont dans L'airdrop.

Deux chemins y mènent, et rien d'autre ne crédite un cycle :

  • Le rail du pont. sendToAirdrop sur le vault miroir envoie chaque action listée par l'adaptateur LayerZero de cette action, que le hub distant désigne (setStockAdapter), vers le contrat que le hub distant désigne (setAirdrop), avec l'identifiant du marché en charge utile. Sur Ethereum, l'OFT de l'action frappe l'action wrappée au contrat, et l'endpoint de LayerZero l'appelle. Il ne crédite la livraison que si elle vient d'un OFT d'action enregistré par l'owner, de Robinhood Chain, envoyée par le vault miroir que le hub de pont dérive pour ce marché.
  • Le rail local. sendToAirdrop sur le vault Ethereum approuve les montants exacts, le contrat les tire, depuis le seul vault du marché, et crédite ce qu'il a reçu ; les approbations sont refermées. Un vault câblé au hub de pont refuse cet appel.

Le keeper déclenche les deux, et ne décide que du moment ; sur le rail du pont, depuis le 2026-10-06, aussi du gas que chaque livraison reçoit sur Ethereum, que le hub distant tient dans les bornes que l'owner de StockFun y fixe.

Les propriétés structurelles

Une panne ne bloque jamais le reste. C'est la règle de conception du 2026-10-05. Une fonction, un marché, une action, un cycle, un enregistrement ou un destinataire qui échoue est sauté, gardé comme dû ou différé, avec un événement, et le reste passe : le batch de pont emporte les autres marchés, l'achat les autres jambes, l'envoi à l'airdrop les autres actions, la réclamation les autres actifs ; le hook doit à un vault qui refuse sa part, et le lock garde pour lui la part qu'il refuse. Chaque contrat qui peut détenir de l'ETH ou des tokens a un levier pour en sortir ce qui est coincé — le mode urgence sur les vaults, les hubs et le contrat de l'airdrop, rescue sur les autres —, et chaque module peut recevoir une correction. Une seule exception, voulue : la déclaration d'un transfert de token à l'enregistreur de détention reste bloquante, sans quoi un holder pourrait la faire sauter en privant l'appel de gas, sur son propre transfert, pour grossir sa part de l'airdrop ; son levier, setRecorder(0) ou un upgrade en place de l'enregistreur, est immédiat. Voir Modèle de confiance et Le mode urgence.

Le hook est unique. Un seul contrat sert tous les pools, ce qui évite de miner une adresse par marché — l'adresse d'un hook v4 encode ses permissions dans ses bits de poids faible, et la trouver coûte du calcul. Depuis le 2026-10-02, c'est un proxy dont l'adresse porte les 14 permissions v4 : un upgrade garde cette adresse, et une implémentation future peut utiliser n'importe quel callback.

La liquidité n'est pas un NFT. Elle est détenue directement dans le PoolManager, indexée par l'adresse du lock. Il n'y a donc pas de position à transférer, pas d'approve à révoquer, pas de tokenId à perdre. Les seules opérations de liquidité que le contrat sache faire sont un modifyLiquidity de delta exactement nul, pour encaisser les frais, et, par le mode fin, le retrait de toutes les positions d'un pool, 30 jours après l'annonce de la fin.

Seul le lock ajoute de la liquidité. Depuis le 2026-10-01, le hook refuse toute autre position dans un pool StockFun : chaque trade est un swap contre les positions verrouillées, et paie la taxe.

Les vaults n'ont pas de retrait. Aucune fonction ne permet à quiconque d'envoyer les actifs d'un vault vers une adresse de son choix. Les deux sorties sont l'airdrop, dont la seule destination est le contrat de l'airdrop que désigne le protocole, qui paie les holders du token au prorata selon une règle que personne ne choisit, et le mode urgence, par lequel l'owner de StockFun déplace un actif vers n'importe quelle adresse, aussitôt. Ce sont les règles de l'implémentation actuelle : l'owner de StockFun peut upgrader un vault, avec effet immédiat.

Les bornes sont des réglages, les feeds non. Les bornes de prix des vaults, 50 et 200 points de base par défaut, sont des réglages de l'owner de StockFun, lus en direct par chaque vault ; le keeper ne peut que les resserrer. Le registre de feeds inscrit chaque feed une fois ; les heartbeats des feeds sont des réglages, et depuis le 2026-10-06 ses deux gardes aussi, qui ne peuvent que retenir un prix, jamais en changer un : le contrôle du séquenceur (éteint sur Robinhood Chain tant que Chainlink n'y publie pas de feed de disponibilité) et la pause d'oracle de chaque action (allumée pour chaque action de Robinhood Chain ; voir Le rail Robinhood). Le mode urgence ne touche ni les uns ni les autres : il déplace des actifs, il ne change pas les règles d'exécution. Le registre, comme les vaults, peut être upgradé par l'owner de StockFun.

Les packages hors chaîne

Package Rôle
shared/ ABIs générées, constantes du protocole, formatage. Source unique partagée par tout le reste
backend/ Service de prix. Isole la seule dépendance externe de la dapp
keeper/ Conversion, batches de pont, routage distant, surveillance des livraisons, l'étape de l'airdrop, les dettes du hook et du lock, les frais de LP

L'app vit à côté de projet/, à la racine du dépôt, depuis le 2026-10-02 : cairn-app/, le front Next.js, et cairn-worker/, le Worker qui lit les contrats une fois pour tous et pousse les changements aux pages ouvertes. Elle remplace la dapp React + Vite provisoire de projet/dapp/, retirée ce jour-là. Voir La dapp.