Coldtill Get Coldtill

Internal security audit: fee contracts and fee shares, October 2026

Paste into Claude Code, Codex, Cursor or your favourite coding agent. View Markdown
Scope Fee-edition contracts for Ethereum-style chains and Tron, the 100 fee-share NFTs on each chain family, the Solana deposit program's fee path, the Solana fee-shares program and mint tool, and the Go code that deploys and drives them
Type Internal review: automated tools, fuzzing, mutation testing and three independent AI-assisted manual reviews
Result No critical or high findings. 6 medium, 13 low and 24 informational findings across the three reviews (some overlap). Every medium is fixed. Lows and infos are fixed or accepted below

This is not an independent audit. The reviewers were AI agents briefed by the people who wrote the code. Nothing here is locked yet: the Ethereum-style and Tron FeeConfig contracts, the Solana deposit program, its fee config and the Solana fee-shares program all stay upgradeable or owned until an outside firm has reviewed them. See How to get Coldtill audited.

The source lives in Fee Contracts/ at the repository root. The full reports are kept next to it:

  • Fee Contracts/audit/review-evm.md: Ethereum, Base, Polygon and BNB Chain contracts.
  • Fee Contracts/audit/review-tron.md: the Tron versions and the Go deploy and sweep code.
  • Fee Contracts/audit/review-solana.md: the deposit program's fee path, the fee-shares program and the mint tool.

What was reviewed

Component Files
Fee shares (Ethereum-style) evm/src/ColdtillFeeShares.sol
Fee shares (Tron) evm/src/tron/TronColdtillFeeShares.sol
Fee override evm/src/FeeConfig.sol, evm/src/tron/TronFeeConfig.sol
Fee factories and deposit wallets evm/src/DepositFactoryFee.sol, DepositWalletFee.sol and their Tron versions
Solana fee shares solana/programs/coldtill-fee-shares, solana/mint
Solana deposit program (fee path) app/solana/programs/keyhold-deposit
Go app/internal/chains/evm, app/internal/chains/tron, app/internal/chains/sol, app/cmd/coldtill-fees

Method

  1. Static analysis: Slither and Aderyn on all Solidity, plus clippy and cargo-audit on both Solana programs. Raw output is in Fee Contracts/audit/static/. Every finding is either fixed or explained under Tool findings.
  2. Tests:
    • Unit, fuzz and invariant tests in Foundry, covering the full path: payment, sweep, fee withdrawal, shares, release and claim.
    • LiteSVM tests against the real Metaplex Core program.
    • Go tests on all three editions.
    • Local-validator tests of the deposit program.
    • Fork tests against real USDC and USDT on Ethereum and Base.
  3. Deep campaign: 100,000 fuzz runs per test and 2,000 invariant runs of depth 128 on the shares contract. Four invariants are checked:
    • every share can still be paid;
    • no share gets more than 1%;
    • recorded receipts match funding;
    • releases never exceed receipts.
  4. Mutation testing: Gambit mutants of every fee contract, run in a scratch copy against the test suite. Report in Fee Contracts/audit/mutation-report.txt.
  5. Three independent manual reviews, each by a separate model with a written brief and no access to the others' findings. Each review wrote its own proof-of-concept tests:
    • Ethereum-style chains: Claude Opus. An earlier pass by GPT was superseded.
    • Tron: Claude Fable.
    • Solana: Claude Fable.
  6. Reproducible bytecode: two clean builds give identical bytecode. Hashes are in Fee Contracts/audit/bytecode-hashes.txt. The .bin files embedded in the Go binary match a fresh build byte for byte and are pinned by TestBytecodePinned.

Severity follows the October 2026 audit.

Findings and resolutions

Ethereum-style chains (review-evm.md)

ID Severity Finding Resolution
M-01 Medium The Go binary embedded the factory compiled before the feeRecipient() fixes Fixed. make contracts rerun and pins updated. The staleness test now covers FeeConfig and the shares contract too. The v1 factory bytecode is unchanged, so no live deposit address moves
M-02 Medium One failing token reverted the whole sweep of a deposit address. An issuer freezing the factory would have locked merchant principal in that token Fixed. Each token is flushed in its own self-call: it either fully succeeds or stays put with a FlushFailed event, which the sweeper already retries. If the factory can't take the fee, the fee goes straight to the shares contract. Native coin still reverts the sweep, because otherwise destroy() would send the whole balance to a cold wallet that refuses ETH, with no fee
L-01 Low A token with two contract addresses sharing one balance (e.g. TUSD's legacy address) is counted twice Accepted. An allow-list would need an owner, and the shares contract deliberately has none. Fees only arrive in the tokens the gateway sweeps (native coin, USDC, USDT). Documented in fee shares
L-02 Low The FeeConfig owner could set a broken recipient and lock it. Ownership transfer was one-step Fixed. lock() now requires the override to be cleared, so locking always makes the shares contract the permanent destination. Ownership is two-step (acceptOwnership)
L-03 Low Anyone could push a share's payout into an escrow or vault holding the NFT Fixed. release skips shares held by contracts unless that contract calls. Wallets with no code and EIP-7702 delegated wallets are paid as before
I-01 Info A negative rebase made releasable revert Fixed. Saturates at zero. Rebasing tokens are still unsupported
I-02 Info Override changes can be front-run by a permissionless withdrawFees Accepted. Documented. The override is for emergencies only
I-03 Info The fee rounds to zero on very small sweeps Accepted. Under 50 base units at 2%. Worth less than the gas
I-04 Info No deterministic deploy for the shares contract, yet one address is used on every chain Fixed. coldtill-fees predict-shares <holder> deploys through the same CREATE2 deployer as FeeConfig. Checked on a local chain. The factory constructor now also refuses a shares or config address with no code, so a chain where shares aren't deployed can't get a factory
I-05 Info Polygon's native-coin ERC-20 shim Checked safe. No change
I-06 Info A seller can claim accrued fees just before a sale settles Accepted. Documented for buyers

Tron (review-tron.md)

ID Severity Finding Resolution
M-01 Medium Embedded Tron factory bytecode predated the fixes Fixed (same as Ethereum-style M-01)
M-02 Medium Deploy budget sized for the v1 factory. The fee factory needs about 1.6M energy, so a hot wallet funded to the advertised 150 TRX would burn it and fail Fixed. tron.DeployBudget(version): v1 keeps 400 TRX limit / 150 TRX minimum, the fee factory uses 800 TRX limit / 400 TRX minimum. The dashboard shows the right minimum per edition
L-01 Low All-or-nothing flush Fixed (same as Ethereum-style M-02). The Tron wallet also refuses a partial first fee transfer before falling back, so the fee can't be sent twice
L-02 Low No code check on the feeShares / feeConfig addresses Fixed. Both factory constructors revert with FeeConfigNotContract / FeeSharesNotContract, which also catches a Nile address baked into a mainnet build
I-01 Info releasable reverted after Tether's destroyBlackFunds Fixed (saturating, as above)
I-02 Info TRC10 tokens are stuck in the contracts, and selfdestruct forwards them to the cold wallet with no fee Accepted. TRC10 is unsupported. Documented
I-03 Info Energy cost of large release batches and of the shares deploy Accepted. Documented: release in batches of 20 or fewer, deploy with fee_limit of at least 1,500 TRX

Solana (review-solana.md)

ID Severity Finding Resolution
KD-01 Medium The mainnet deposit program is the pre-fee build, and the upgrade changes the Config layout from 73 to 75 bytes Deployment step. No merchant Config exists on mainnet yet. Before the upgrade, getProgramAccounts must return none. Then upgrade and run init_fee_config in the same session, before any merchant onboards
KD-02 Low u64 overflow in the fee maths made huge balances unsweepable Fixed. fee_of computes in u128
KD-03 Low Small sweeps fail rent checks before the fee vault, treasury or distributor exist Deployment step. Deploy order is: shares program initialize, then deposit upgrade, then init_fee_config. Merchants fund their treasury at onboarding
KD-04 Low Token-2022 hooks, transfer fees and permanent delegates Accepted. The gateway only offers plain USDC and USDT on Solana. Documented
KD-05 Info One key controls upgrades and fee routing Accepted for now. Moves to a multisig before locking. Nothing is locked before the outside audit
KD-06 Info Hygiene: unused fee_vault account, pre-existing vault ATA The server creates the vault's token account idempotently before every sweep. The unused account stays to keep the instruction layout stable
KD-07 Info Rent for pre-funded accounts comes out of early fees Accepted. initialize runs before fees are enabled, so no fees are affected
FS-01 Low A burned share's 1% stays in the pool forever Accepted. Documented: burning forfeits the share. The program stays upgradeable until the outside audit, which covers a future Core layout change
FS-02 Low Permissionless release pays escrow or program addresses Fixed. release_sol / release_token require an on-curve owner (OwnerNotWallet). Program-held shares keep their fees until a wallet owns them or the owner claims
FS-03 Low A permanent delegate draining the vault made shares fail with Math Fixed. Saturating subtraction
FS-04 Info The mint tool aborted if the distributor address was pre-funded Fixed. A system-owned empty account counts as uninitialised, with length checks before indexing. Tested on a local validator
FS-05 Info initialize didn't check the collection Fixed. The collection is passed as an account and must be a Core CollectionV1 (NotCoreCollection)
FS-06 Info Minor code quality, stale rent constant in tests The test now reads rent from the runtime. The rest is noted

Tool findings

Slither (static/slither-fee-contracts.txt). Nothing exploitable:

  • arbitrary-send-eth: payouts go to the NFT owner, the cold wallet, the factory or the shares contract. These addresses are immutables or ownerOf, not caller input.
  • reentrancy-eth / reentrancy-events:
    • release and claim are nonReentrant, and the reentrancy tests confirm it.
    • flushOne is callable only by the wallet itself, and the wallet only by its factory.
    • The events after calls are informational.
  • incorrect-equality: == 0 checks on balances and amounts, used only to skip work.
  • uninitialized-local: total starts at zero on purpose.
  • missing-zero-check:
    • Zero is a valid FeeConfig recipient: it means no override.
    • The wallet's immutables come from a factory that already checks them.
  • calls-loop: batches are caller-sized. A failing token in flush is isolated.
  • assembly:
    • CREATE2 in sweep;
    • extcodesize / extcodecopy in _isWallet.
  • low-level-calls: needed for tokens that return nothing (USDT) or misreport (Tron USDT).

Aderyn (static/aderyn-fee-contracts.md):

  • H-1 encodePacked collision: the input is fixed-length clone bytecode around one address, so no collision is possible.
  • H-2 selfdestruct: used in the same transaction as the clone's creation, which still deletes the account under EIP-6780 (and on the TVM, per the Tron review).
  • L-7 unchecked return: _requireOwned reverts and its return value isn't needed.
  • L-9 _mint: minting to the cold wallet at deploy is intended.
  • Other lows: centralisation of FeeConfig (see L-02), literals, loops. Style only.

Clippy: clean with -D warnings on both Solana programs.

cargo-audit: no vulnerabilities, only 5 unmaintained-crate warnings in Solana SDK dependencies.

Test results

Suite Result
Foundry, all fee contracts (excluding forks) 60 tests pass, including 4 invariants at the default 256 runs
Foundry fork tests (real USDC and USDT, Ethereum and Base) Pass
Deep fuzz and invariant campaign See Fee Contracts/audit/deep-campaign.log
Mutation testing See Fee Contracts/audit/mutation-report.txt
Solana fee-shares (LiteSVM with real Core) 8 tests pass
Solana deposit program on a local validator All pass, including fee withdraw, override and clear
Go, free / standard / pro editions All pass
Mint tool on a local validator, with a pre-funded distributor 100 assets minted, registered, finalised, collection authority given up

Before mainnet

  1. Deploy the shares contract with predict-shares and confirm the same address on every chain. Then deploy FeeConfig with no override. Then bake both addresses into the Free and Standard builds.
  2. On Tron, deploy shares and TronFeeConfig with a fee_limit of at least 1,500 TRX.
  3. On Solana:
    • deploy the fee-shares program;
    • run the mint tool, which initialises, registers, finalises and gives up the collection authority;
    • confirm no deposit Config accounts exist;
    • upgrade the deposit program and run init_fee_config.
  4. Verify every contract on Etherscan, Tronscan and with solana-verify.
  5. Do not lock anything (FeeConfig.lock, lock_fee_config, or either Solana program's upgrade authority) until an outside firm has reviewed this code and the keys sit behind a multisig.