SoulBound Finance Protocol: Technical Overview
SoulBound Finance launches on Arbitrum mainnet on April 20, 2026 — the first privacy-preserving payment protocol that embeds regulatory compliance at the smart contract layer rather than bolting it on after the fact.
This post and the accompanying repository cover the on-chain smart contracts only, released under AGPL-3.0. The full platform — application frontend, backend OTU generation and redemption services, and operational infrastructure — is proprietary and not included in the open-source release. What you’re reading about here is the settlement layer: the contracts that custody funds, enforce access control, verify attestations, and process redemptions.
The protocol enables users to deposit crypto, generate anonymous one-time-use (OTU) transfer codes, and have those codes redeemed to ephemeral addresses — all while maintaining an immutable attestation of purpose on-chain. It is purpose-built for charitable giving, donations, and personal gifts where the sender’s identity should be verifiable by regulators but invisible to recipients.
The contracts are written in Solidity 0.8.19, built with Foundry, and carry zero external dependencies (no OpenZeppelin). Every line was written from scratch.
This post serves two purposes: to explain how the on-chain protocol works for anyone evaluating it, and to solicit security review from the community before mainnet deployment. We’re publishing the contract source code now because we want real scrutiny from people who break things for a living — not a rubber stamp.
The protocol repo: github.com/SoulboundSecurity/sbf-protocol
The System at a Glance
Three operational contracts, deployed and linked atomically by a fourth deployer contract:
SoulBoundToken Non-transferable identity token. Gates access to the protocol.
DepositPool Accepts multi-token deposits. Generates OTUs with EIP-712 attestations.
ClaimPool Processes anonymous OTU redemptions to ephemeral addresses.
SoulBoundDeployer Atomic deployment, linking, and ownership handoff of the three contracts above.
The flow is: Mint SBT → Deposit → Generate OTU (with attestation) → Redeem anonymously.
SoulBoundToken — Identity Without Addresses
The SBT is a non-transferable token that represents a user’s identity within the protocol. It is not an ERC-721. There is no transfer, no approve, no tokenId. One address, one SBT, forever.
What it stores
Each SBT contains:
encryptedAccountId — A deterministic hash of the user’s address, a protocol-specific salt, and the chain ID. The protocol never stores raw wallet addresses in its identity layer.
zkpCommitment — A slot for a zero-knowledge proof commitment (Privado ID). Set at mint or updated later. Enables privacy-preserving KYC without revealing identity.
nonce — A counter that increments every time the user generates an OTU. This is the backbone of replay protection for attestation signatures.
eulaHash — The keccak256 of the terms the user accepted at mint time. This is an immutable on-chain record of which version of the terms were signed. When terms update, old SBTs retain their version.
mintedAt — Timestamp of creation.
Minting
Minting requires the user to provide the current EULA hash. The transaction signature itself is the cryptographic proof of acceptance — no separate signing step. If the EULA hash doesn’t match what the controller has set, the mint reverts.
Once minted, the SBT cannot be transferred, burned, or re-minted. The identity is permanent.
Nonce Management
Only the DepositPool contract can increment a user’s nonce, and it does so exactly once per OTU generation. This prevents attestation signature replay: each attestation is bound to a specific nonce value, and once the nonce increments, that signature is permanently invalidated.
DepositPool — Multi-Token Deposits and OTU Generation
This is where funds live and where the attestation logic happens.
Deposits
Users with a valid SBT can deposit ETH (native) or any whitelisted ERC-20 token. Deposits have no protocol fees. Balances are tracked per-user, per-token in an internal mapping.
Supported tokens at launch: ETH, USDC, USDT, WBTC. The controller can add or remove tokens from the whitelist. ETH cannot be removed.
OTU Generation — The Core Mechanism
This is the most security-critical function in the protocol. When a user generates an OTU, they are converting deposited funds into an anonymous bearer instrument that can be redeemed by anyone who possesses the code.
The generation process requires an EIP-712 typed data signature from the user that attests to the purpose of the transfer. This attestation is:
Per-transaction — The fee tier (charitable vs. commercial) is not stored per-user. The same user can generate a charitable OTU for a church donation and a commercial OTU for a business payment, each with its own attestation.
Nonce-bound — The attestation includes the user’s current SBT nonce. After generation, the nonce increments, permanently invalidating that specific attestation signature.
Immutable on-chain — The user’s signature is a cryptographic commitment to their stated purpose. If they attest “charitable” but use the funds commercially, they have signed a false attestation on a permanent, public ledger. The legal exposure falls on the user, not the protocol.
Fee Structure
Fees are charged on OTU generation, not on deposits:
Charitable tier: 1.00% protocol fee + 0.25% gas reserve = 1.25% total
Commercial tier: 2.00% protocol fee + 0.25% gas reserve = 2.25% total (disabled at launch)
The gas fee (0.25%) is immutable — hardcoded at 25 basis points as a Solidity constant. Protocol fees are controller-adjustable but capped at 5% maximum (500 bps). Both caps are enforced in the contract.
Fees are calculated on top of the OTU amount: if you generate a 1 ETH OTU, you pay 1.0125 ETH from your balance (charitable tier). The recipient receives exactly 1 ETH.
Fee Distribution
Protocol fees go directly to the protocol treasury address. The OTU amount plus gas fee goes to the ClaimPool. These are separate transfers in the same transaction.
The protocol treasury address is mutable — the controller can update it (for multisig rotation, etc.). The ClaimPool link, by contrast, is set once and cannot be changed.
EIP-712 Attestation Verification
The signature verification implements full EIP-712 with:
Domain separator bound to contract address + chain ID (prevents cross-chain and cross-contract replay)
Struct hash includes: depositor address, token, amount, fee tier, nonce, and a purpose hash
ECDSA recovery with EIP-2 s-value enforcement (restricts s to lower half of the curve order to prevent signature malleability)
v-value validation (must be 27 or 28)
Recovered signer must equal
msg.sender
The purpose hash is a keccak256 of a human-readable string that appears in the user’s wallet signing prompt:
Charitable: “I attest this withdrawal is for charitable, donation, or personal gift purposes”
Commercial: “I attest this withdrawal is for commercial or business purposes”
Emergency Withdrawal
Any SBT holder can withdraw their full balance for any token at any time. No fees. No whitelist check (works even if the token has been delisted). This is the escape hatch — it always works.
ClaimPool — Anonymous Redemption
The ClaimPool receives funds from the DepositPool and processes redemptions. It maintains two separate balance pools:
Redemption balance — Funds available for OTU redemptions
Gas fund balance — The 0.25% gas fee reserve, used for operational costs (DeFi yield, gas subsidies)
Redemption Processing
An operator (backend service) processes redemptions. The key design property: recipient addresses are ephemeral. They are used exactly once in the redemption transaction and never stored on-chain beyond that transaction’s logs.
Each redemption requires a unique redemptionHash — a hash of the OTU code. The contract tracks which hashes have been processed, preventing double-spend. Once a hash is marked as processed, it can never be used again.
Batch Redemptions
The operator can process multiple redemptions in a single transaction. This is a privacy feature: by batching redemptions together, it becomes harder to correlate the timing of a deposit with a specific redemption. Individual timing analysis is defeated when multiple redemptions are processed atomically.
The batch function is fault-tolerant: if an individual redemption in the batch has already been processed, has a zero amount, or targets the zero address, it is skipped rather than reverting the entire batch.
Maximum batch size is 50 items.
Gas Fund
The gas fund receives the 0.25% fee from every OTU generation. The gas manager can use these funds for DeFi operations (e.g., depositing to Aave for yield) or operational costs. The useGasFund function accepts arbitrary calldata, enabling interaction with any DeFi protocol.
The gas manager role is separate from the operator role. The operator processes redemptions; the gas manager manages the operational treasury. A compromised gas manager key could drain the gas fund — the gas manager key should be held in an HSM or secure enclave with monitoring on gas fund balance.
SoulBoundDeployer — Atomic System Bootstrap
The deployer is deployed independently, then creates all three operational contracts, links them together, and transfers ownership — all in one transaction via deploySystem(). This eliminates the risk of partially-configured systems or ownership gaps.
Deployment Flow
Deploy SoulBoundToken (deployer contract as temporary controller)
Deploy DepositPool (deployer contract as temporary controller)
Deploy ClaimPool (deployer contract as temporary operator)
Link all contracts to each other and configure: SBT ↔ DepositPool, DepositPool → ClaimPool + Treasury, ClaimPool → DepositPool + Gas Manager, EULA hash on SBT
Whitelist initial tokens
Transfer all roles to
msg.sender(expected: multisig)
If any step fails, the entire transaction reverts. There is no state where one contract exists without the others being properly configured.
Post-Deployment Validation
The deployer includes a validateDeployment() function that returns a boolean and an array of issue strings. It checks every linkage, every configuration, and every role assignment. This is designed to be called by deployment scripts to verify the system is correctly configured before proceeding.
Operator Trust Model
The protocol has three privileged roles, each with a clearly scoped trust boundary:
Controller (SBT + DepositPool): Can update EULA, modify fee rates (capped at 5%), toggle commercial tier, add/remove tokens, update the treasury address. Cannot access user funds. Cannot generate OTUs on behalf of users. Cannot process redemptions.
Operator (ClaimPool): Can process redemptions and batch redemptions. This role is held by the backend application — the bridge between off-chain OTU validation and on-chain fund transfer. A compromised operator could drain the ClaimPool redemption balance. Mitigation: operator key should be held in a secure enclave or HSM, with monitoring on ClaimPool balance and redemption patterns.
Gas Manager (ClaimPool): Can deploy gas fund to external contracts (Aave, etc.) via arbitrary calldata. Cannot access redemption balance. Same key management recommendations as operator.
Testing
Our internal test suite covers 78 Forge unit tests and 24 live-chain smoke tests against local Anvil deployments. Every public function has happy path, revert case, and edge case coverage. The smoke tests run the full deposit-to-redemption cycle with real transactions, randomized deployer wallets, and gas metering — different contract addresses every run.
Test philosophy: every revert path gets its own test. If the contract has a custom error, there’s a test that triggers it. Fee calculations are verified to the wei. EIP-712 attestation tests cover valid signatures, replayed nonces, wrong signers, malformed signatures, and cross-tier mismatches. Batch redemption tests verify skip logic for already-processed hashes, zero amounts, and zero addresses within a single batch.
Full test coverage documentation is available at TESTING.md in the repo.
We do not publish our internal test suite, but we welcome community-contributed tests. If you write tests that break something — or prove something holds — we want them. Contributions should follow the existing contract conventions: Forge-std, custom errors for all revert cases, and the same happy path / revert / edge case structure documented in TESTING.md. See CLA.md for contribution terms.
Security Properties We Believe Hold
These are the properties we’re asking reviewers to verify or challenge:
No double-spend. Each
redemptionHashcan only be processed once. TheprocessedRedemptionsmapping prevents replay.No attestation replay. Each attestation signature is bound to a specific nonce. The nonce increments after each OTU generation. A used attestation signature can never produce a valid OTU again.
No signature malleability. EIP-2 s-value range check ensures each signature has exactly one valid form.
No cross-chain replay. The EIP-712 domain separator includes
address(this)andblock.chainid, binding signatures to a specific contract on a specific chain.No fund extraction beyond balance. Users can only generate OTUs up to their deposited balance minus fees. The balance check is performed before any state mutation.
No unauthorized fund movement. Only the DepositPool can send funds to the ClaimPool (via
receiveFunds/receiveFundsETH). Only the operator can process redemptions. Only the gas manager can use the gas fund.No reconfiguration of critical links. The DepositPool ↔ ClaimPool link and the SBT ↔ DepositPool link are set once and cannot be changed.
Emergency withdrawal always works. Bypasses the token whitelist, charges no fees, and returns the user’s full balance. This is the unconditional escape hatch.
Checks-effects-interactions pattern maintained. All balance updates occur before external calls in every function that transfers funds. Note:
generateOTUupdates balances, then callsincrementNonceon the SBT contract (an external call), then distributes funds. The intermediate SBT call occurs after balance deduction but before fund transfers — reentrancy through the SBT cannot double-spend because the balance is already zeroed.No
delegatecall, noselfdestruct, notx.origin. The protocol uses none of these patterns.
What We’re Asking For
We’re asking the security community to:
Read the contracts. They’re compact — SoulBoundToken is ~220 lines, DepositPool is ~500, ClaimPool is ~330. No inheritance trees, no abstract base classes, no OpenZeppelin.
Challenge the properties above. If any of them don’t hold, we want to know.
Look for edge cases in the fee calculation, the EIP-712 verification, the batch redemption logic, and the gas fund operations.
Consider the interaction patterns. The contracts are designed to be called in a specific order. Are there orderings that produce unexpected states?
Mainnet deployment is April 20, 2026. The application will be live at soulbound.finance on launch day.
Responsible Disclosure
If you identify a vulnerability, please report it to security@soulboundsecurity.io before public disclosure. We take every report seriously and will acknowledge receipt within 24 hours.
Links
Protocol repo github.com/SoulboundSecurity/sbf-protocol
Website soulboundsecurity.io
App (live April 20) soulbound.finance
Security contact security@soulboundsecurity.io
License AGPL-3.0
Owner SoulBound Security Ltd (NI735885)


