Smart Contracts (Berachain Mainnet)
Interact only with the proxy addresses below. Do not call internal implementation or WERC settlement contracts.
Network: Berachain Mainnet · Chain ID 80094 · Explorer: https://berascan.com
Share token
| Symbol | Decimals | Address |
|---|---|---|
| duPRT | 18 | 0xf5Bac52e31317dE901edc773fbef8f75c798f36f |
Vault proxies
Berascan has verified the contract ABI at each address above.
Access control
The contract ABI includes setKYCed(address controller, bool) and isKYCed(address). These functions appear in the repository ABIs but are not present in the deployed mainnet bytecode. Calling isKYCed on either the duPRT vault (0x1B61…) or the duPRT share token (0xf5Ba…) reverts as a nonexistent function, while other functions on the same contracts, such as isVaultActive() and name(), return correctly.
What this means for deposits: A wallet does not need to be whitelisted to call requestDeposit. A requestDeposit from a never-seen address reverts only on ERC20: transfer amount exceeds allowance, the standard token approval check, not on any KYC error. The claim step reverts with InsufficientClaimableAssets() when nothing has been fulfilled, again not a KYC error.
Upgradeability caveat: The vaults are UUPS-upgradeable. The current implementation does not enforce on-chain KYC for LP deposits. A future upgrade could change this. SukukFi will publish notice of any access control change before it takes effect.
Basis: This is empirical (eth_call) evidence against the live contracts on Berachain Mainnet, not source-read. The implementation source is not verified on Berascan. eth_call is reliable for determining whether a given call reverts and on what error, but it is not a substitute for a full source audit.
How TVL is calculated
totalAssets() is not a TVL figure. Per the vault source, totalAssets() deliberately excludes pending deposits, claimable redemptions, cancellations, and any capital already invested. It answers a narrower question: how much is available in this vault right now for a new investment or conversion calculation. Reading it as “how much has been deposited” undercounts the real figure, sometimes down to zero, even when real deposits are sitting in the vault as pending or invested capital.
The complete figure, per vault: take the vault’s raw asset balance (balanceOf of the underlying token at the vault address, equivalent to grossAssetBalance in getVaultMetrics()). This captures every state at once: idle, pending, claimable, and cancelled. It excludes only capital that has already moved elsewhere, which brings us to trUST.
duPRT and trUST do not double count. When the Investment Manager calls investAssets() on a duPRT vault, the underlying asset physically moves into the matching trUST vault for that same asset. duPRT’s own accounting stops counting it (per the exclusion above), and trUST’s totalAssets(), a plain balanceOf with no exclusions, now includes it. So the non-double-counting total across the protocol is: each duPRT vault’s raw balance, plus each trUST vault’s totalAssets(). Every dollar is counted exactly once, wherever it currently sits.
Basis: Confirmed by reading the vault source in sukukfi/security-audits and by direct eth_call against the live vaults, including tracing a duPRT vault’s investmentVault field to confirm it resolves to trUST’s vault for the same asset.