Technical

Technical

The Loam diamond, described in the terms a reviewer checks it in: which facets are actually deployed, what each one can call, who can call it, and how the pieces are wired together. The facet map below is read live from the diamond's own loupe, never copied from a deployment record โ€” an upgrade changes what is live, not what a doc says.

Facets

Every facet currently cut into the diamond, read live via IDiamondLoupe.facets(). A deployment record captures a moment; this call captures now.

Facet map โ€” facets()โ€”

Functions

The read surface any integrator or reviewer needs, with real signatures โ€” call these directly against the diamond address in the panel below with cast call or any ABI-aware client. previewMint and previewRedeem change no state: they can be called before signing anything, to see the exact result a mint or redeem would produce.

previewMint(uint256 pintoAmount6)view โ€” returns (surcoOut, fee6): the LOAM a given PINTO deposit would mint, and the fee taken, without sending a transaction
previewRedeem(uint256 surcoAmount)view โ€” returns (payout6, fee): the PINTO a given LOAM redemption would pay out, and the redemption fee in LOAM that shrinks the payout basis. The second output is named burned in the ABI, which the mint-on-demand cut left unchanged โ€” it is the FEE, not the amount burned; a redemption burns the full surcoAmount you pass in. No transaction is sent
navPintoPerSurco()view โ€” returns the current net-asset-value peg, PINTO per LOAM
circulatingSupply()view โ€” returns totalSupply() โˆ’ protocolLpSurco(), the LOAM actually in circulation
reserveBox()view โ€” returns unissued capacity: the cap minus what has been issued. A counter the contract keeps, not a balance it holds
maxSupply()view โ€” returns totalSupply() + reserveBox(), the cap โ€” written once at genesis, never raised
protocolLpSurco()view โ€” returns the LOAM side of the protocol-owned liquidity position
mintFeeBps()view โ€” returns the mint fee in basis points
redeemFeeBps()view โ€” returns the redeem fee in basis points
perfFeeBps()view โ€” returns the performance fee on realised Silo yield in basis points (1000 = 10%), which is paid out rather than retained
devAddr()view โ€” returns the address the performance fee is paid to, in PINTO
totalSupply()view โ€” returns what has actually been issued, not the cap
balanceOf(address)view โ€” standard ERC-20 balance read

Access control

Every upgrade to the diamond runs through one path, verified on chain rather than assumed from convention. DiamondCutFacet.diamondCut calls LibDiamond.enforceIsContractOwner() before it does anything else (src/diamond/facets/DiamondCutFacet.sol:23), and the diamond's own owner() returns the TimelockController address below โ€” not a wallet, not the Safe directly.

Holds every proposal โ€” TimelockController (is owner() of the diamond)0x6Cf2879291A343514F62c55448306a3f5E45EF9f
Delay โ€” getMinDelay()172800 (48 hours)
Canceller โ€” holds CANCELLER_ROLE, can pull a queued proposal, cannot execute one early0x7d81AeE0AB78D7a149420BfFFA6074E85DE735a3
Guardian โ€” holds GUARDIAN_ROLE, can halt redemptions and minting instantly, with no timelock0xc11D0a80F2f3ff6eba46cdB4f6CD3b0ff2D53beF

The Safe holds PROPOSER_ROLE on the timelock, and it holds EXECUTOR_ROLE too โ€” it is the only executor, and the zero address does not hold that role, so execution is not open to the public. Holding it shortens nothing: the timelock refuses to execute a queued proposal until its 48-hour delay has elapsed, so the Safe cannot execute a change before the delay runs. When the delay has run, the account that calls diamondCut is the timelock itself โ€” it is the diamond's owner(), and therefore msg.sender to the cut. There is no direct-call path from the Safe, the canceller, the guardian, or anywhere else to diamondCut that bypasses the timelock.

The canceller and the guardian are not independent parties. getOwners() on the Safe returns three signers with a threshold of 2, and two of those signers are the canceller and guardian addresses published above. These are role separations inside one signer set, not separate organisations. One call exposes it, so it is stated here rather than left to be found.

Two classes of privileged action do not run through the timelock at all. AdminFacet.latchRedemption(string) and AdminFacet.latchMint(string) are onlyRole(GUARDIAN_ROLE): the guardian can halt every redemption, or every mint, immediately and with no delay. Each requires a non-empty reason string, which is emitted on-chain with the latch, and the Safe can lift a latch โ€” also immediately. Separately, AdminFacet carries a set of parameter setters gated on DEFAULT_ADMIN_ROLE, which the Safe holds, and those take effect the moment they are called: the cushion floor and ceiling, the performance fee, the referrer, the Field exposure cap, the earmark ceiling, the max swap slippage, the stem-scan budget, and disableVenueDivergenceGate (that last one is callable by the guardian as well). They change parameters, not code. What the 48-hour delay covers is diamondCut itself, role grants, the mint and redeem fees, the venue, and devAddr.

Separately, the vLOAM staking contract has no equivalent chain to trace, because there is nothing upgradeable in it to trace:

grantBps()immutable, fixed at deployment โ€” no setter exists
ramp()immutable, fixed at deployment โ€” no setter exists

There is no owner, no pause function, no setter, and no proxy on the vLOAM contract. Nothing can change the staking curve after deployment, and nothing can drain custody through an admin function, because no such function was written.

Naming

The deployed contracts still say "Surco" โ€” the interfaces are named ISurco, and the current, live NAV function is navPintoPerSurco(), not a renamed equivalent. This is not evidence of a fork of someone else's work; it is the same codebase carried forward under a new brand.

The rename to Loam was deliberately not applied at the contract level. An external function's name is compiled into its 4-byte selector โ€” renaming navPintoPerSurco() to, say, navPintoPerLoam() would produce a different selector, which every integrator, indexer, and script currently calling the old one would silently stop matching. Relabeling the brand at the expense of breaking every existing integration at once was rejected in favor of leaving the ABI alone. The rename lives in the product name, the token symbol where applicable, and the docs โ€” not in function selectors.

Proxy pattern

The token's page on Basescan does not resolve facet reads or show a "Read as Proxy" tab the way a typical upgradeable token does. This is a detector gap, not a hidden contract: Etherscan's (and Basescan's) automatic proxy detector is built for EIP-1967, the single-implementation-slot proxy standard. The Loam diamond is an EIP-2535 diamond, which routes different function selectors to different facet contracts through IDiamondLoupe rather than one implementation slot โ€” a pattern the EIP-1967 detector was never written to recognize.

It would be possible to point Basescan's proxy detector at a single facet to force it to resolve โ€” but doing so would publish that one facet's ABI as if it were the whole token's, misrepresenting a multi-facet contract as a single-implementation one. That is deliberately not done. The correct way to read the diamond's real function surface is IDiamondLoupe.facets(), shown live at the top of this page, or the individual facet addresses it returns.

vLOAM

vLOAM is non-transferable by design, and a non-transferable balance emits no Transfer events โ€” staking and unstaking never call the ERC-20 Transfer event because there is no transfer to log. Any indexer or portfolio tool that builds balances purely from Transfer logs will show zero vLOAM for every holder. That absence is the correct, expected behavior of the contract, not a bug or a sign of a broken integration.

To read vLOAM correctly: call balanceOf(address) directly against the contract, or index the events it actually emits โ€” Staked, Withdrawn, and Settled โ€” rather than Transfer.