HalalFi Technical Documentation

On-Chain Guarantor Layer & Exposure Module

Complete product specification mapped to deployed smart contracts, test suite, and Next.js frontend on BNB Chain mainnet.

Last updated: 2026-05-28 · Chain ID 56

1. Overview & Core Philosophy

HalalFi operates with a strict off-chain guarantee baseline — no campaign is published without verified backing from the investee (property titles, corporate checks, liquid collateral, etc.). The Guarantor Module adds an optional on-chain guarantee layer where verified legal entities deposit USDT into a master vault and underwrite trade cycles with leveraged credit lines.

Binary campaign model

Every project is strictly binary at launch — there are no per-investor toggles or split risk tiers within a single contract:

  • Off-chain Guaranteed — backed by HalalFi's existing off-chain collateral process. On default, investors rely on off-chain legal asset liquidation managed by HalalFi Ops.
  • On-chain Guaranteed — a verified guarantor locks coverage in GuaranteeVault.sol. 100% of investor principal in that campaign is protected by the vault safety net.

Two parallel on-chain systems

SystemContractPurpose
Crowdfund ProjectsCrowdfundProject.solCap-based fundraising with optional on-chain guarantor lock after dual admin + guarantor approval
Guaranteed CampaignsGuaranteedCampaign.solTarget-based insured campaigns with full state machine, 3%/40%/57% profit split, disputes, and vault default payout
Credit lines are not ERC-20 tokens. All guarantor credit is internal accounting state inside GuaranteeVault.sol, mapped to wallet addresses — bypassing secondary-market regulatory risk.

2. System Architecture

High-level contract graph
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────────┐
│ CrowdfundAdmin  │────▶│  GuaranteeVault  │◀────│ GuaranteedCampaign  │
│ (platform admin)│     │  (master vault)  │     │ Factory             │
└────────┬────────┘     └────────┬─────────┘     └──────────┬──────────┘
         │                       │                          │
         │ approve/reject        │ lock/release/default     │ deploy
         ▼                       ▼                          ▼
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────────┐
│ CrowdfundFactory│────▶│ CrowdfundProject │     │ GuaranteedCampaign  │
│                 │     │ (per project)    │     │ (per campaign)      │
└─────────────────┘     └──────────────────┘     └─────────────────────┘

Factory pattern

  • CrowdfundFactory.createProject() — deploys a new CrowdfundProject clone with immutable config
  • GuaranteedCampaignFactory.createCampaign() — deploys GuaranteedCampaign and optionally locks vault credit at creation
  • CrowdfundAdmin is the vault admin — all KYB, freeze, and margin operations proxy through it

Registry & access control

The vault maintains campaignRegistry[address] — only registered campaign/project contracts may call releaseGuarantee() or executeDefaultPayout(). Factories register addresses at deploy time via registerCampaign() / registerProject().

3. Main Actors

ActorPlatform RoleOn-Chain IdentityStatus
GuarantorDeposits USDT, receives 3× credit, underwrites projects, earns 40% profit shareWallet in GuaranteeVault profilesImplemented
Admin / HalalFi OpsKYB approval, project approval, dispute verdicts, fee withdrawalCrowdfundAdmin.isAdmin mappingImplemented
Investee / CreatorCreates campaigns, repays principal + profit, files disputesmsg.sender at createProject/createCampaignImplemented
InvestorDeploys USDT capital; protection rules set by project binary configPer-project investment mappingImplemented
Jury / Review TeamEvaluates dispute evidence, issues majority verdictHybrid off-chain panel; admin passes verdict on-chainPartial
Jury panel (5 or 7 arbitrators) operates off-chain. Only the admin's on-chain resolveDispute() call encodes the verdict. There is no on-chain jury voting contract yet.

4. Guarantor Module — Onboarding & Lifecycle

Step 1: Request & Admin KYB Review

  1. Prospective guarantor calls GuaranteeVault.requestGuarantorship(entityName) from their wallet
  2. Only wallet address and entity name are stored on-chain (corporate docs, contact details, asset proof remain off-chain per spec)
  3. Admin reviews pending requests via getPendingGuarantorSummaries()
  4. Admin accepts via CrowdfundAdmin.approveGuarantorRequest(guarantor) or rejects via rejectGuarantorRequest(guarantor)
  5. Duplicate requests blocked: require(!hasPendingRequest[msg.sender]) and require(!profile.isApproved)
GuaranteeVault.sol — request flow
function requestGuarantorship(string calldata entityName) external {
    require(bytes(entityName).length > 0, "Empty entity");
    require(!profile.isApproved, "Already approved");
    require(!hasPendingRequest[msg.sender], "Request pending");
    hasPendingRequest[msg.sender] = true;
    _entityNames[msg.sender] = entityName;
    _pendingGuarantorRequests.push(msg.sender);
    emit GuarantorRequested(msg.sender, entityName);
}

Step 2: Deposit & credit activation

Approved guarantors deposit USDT via depositUSDT(amount). Capacity rebuilds automatically:

Capacity formula
totalCreditCapacity = (depositedUSDT × 3) + adminAllocatedMargin
availableCredit     = totalCreditCapacity - usedCredit

Admin guarantor management

ActionFunctionCondition
Accept requestCrowdfundAdmin.approveGuarantorRequestPending request exists
Reject requestCrowdfundAdmin.rejectGuarantorRequestPending request exists
FreezeCrowdfundAdmin.setGuarantorFrozen(guarantor, true)Hidden from creator picker; capacity = 0
UnfreezeCrowdfundAdmin.setGuarantorFrozen(guarantor, false)Rebuilds capacity
DeleteCrowdfundAdmin.deleteGuarantoractiveProjects = 0 AND depositedUSDT = 0
Admin margin boostCrowdfundAdmin.configureGuarantorMarginAdds adminAllocatedMargin to capacity

Frontend: /guarantor (request, deposit, approve projects) · /admin (requests, freeze, delete)

5. The 3× Exposure Credit Engine

Rule 1: 3× Multiplier

CAPACITY_MULTIPLIER = 3 in GuaranteeVault._rebuildCapacity(). Admin can add physical collateral margin via configureAdminMargin() without requiring additional USDT deposit.

Rule 2: First-Project Ceiling

When activeProjects == 0, lock amount cannot exceed raw depositedUSDT:

GuaranteeVault._lockGuarantee()
if (profile.activeProjects == 0) {
    require(amount <= profile.depositedUSDT, "First project limit");
}

Rule 3: 50% Investee Concentration Limit

Per-guarantor, per-investee exposure tracked in guarantorToInvesteeExposure[guarantor][investee]:

Concentration check
uint256 maxSingleInvestee = profile.totalCreditCapacity / 2;
uint256 nextExposure = guarantorToInvesteeExposure[guarantor][investee] + amount;
require(nextExposure <= maxSingleInvestee, "Investee concentration");

Rule 4: Liquidity Withdrawal Lock

On guarantee lock, liquidityLocked[guarantor] += amount. Withdrawals blocked while remaining < liquidityLocked. Guarantor cannot pull core collateral while credit is actively backing live projects.

Rule 5: Failed campaign reset

When fundraising fails (Guaranteed) or refund phase triggers (Crowdfund), releaseGuarantee() decrements usedCredit, clears exposure mapping, and unlocks liquidity — restoring available credit with no fees charged.

Tests in test/GuaranteeSystem.test.js verify: 100k deposit → 300k capacity, 150k first campaign fails, 80k second campaign fails at 180k exposure, liquidity lock/unlock on cancel.

6. Crowdfund System (Cap-Based)

Unified project creation flow

  1. Creator submits via CrowdfundFactory.createProject(config, guarantor, useOnChainGuarantor, guaranteeAmount)
  2. Project starts with adminApproved = false, isApproved = false
  3. Admin loads project in /admin, sets platform fee 1–50%, calls approveProject(project, feePercent) — no reason field
  4. If on-chain guarantor selected: guarantor must call guarantorApproveProject() → vault locks guarantee → project goes live
  5. If no on-chain guarantor: live immediately after admin approval
Approval state machine
Pending admin → [if useOnChainGuarantor] Awaiting guarantor → Live
                ↓ admin reject                    ↓ guarantor reject
              Declined                          Declined

Investment & cap logic

  • invest(amount) — one investment per wallet, min/max bounds, USDT only
  • cap = minimum success threshold; raiseAmount = hard upper bound
  • After endDate: if totalRaised >= cap → creator may withdrawFunds() (platform fee deducted from raised amount)
  • If cap not met → investors claimRefund() for 100% principal; guarantee released

Success settlement

  • Creator returnFunds(amount) with principal + profit
  • Investors withdrawReturns() — pro-rata principal + profit (pull-payment)
  • Guarantee released from vault on withdraw or refund
Crowdfund projects do not trigger vault default payout to investors on creator default. The on-chain guarantor lock is credit underwriting only — not principal insurance for crowdfund. Full vault default protection exists in Guaranteed Campaigns only.

7. Guaranteed Campaigns (Target-Based)

Creation

GuaranteedCampaignFactory.createCampaign()
createCampaign(
    address guarantor,
    bool requestOnChainProtection,  // binary: on-chain or off-chain
    uint8 fallbackMode,             // 0 = revert, 1 = launch off-chain
    uint256 targetAmount,
    uint256 expectedProfitRateBps,  // e.g. 10000 = 100% target yield
    uint256 fundraisingDeadline,
    uint256 maturityDate
)

State machine

GuaranteedCampaign.CampaignState
Fundraising → Active (target hit)
Active → Settled (repayAndSettle)
Active → GracePeriod (after maturity)
GracePeriod → Disputed (fileDispute + $500 fee)
GracePeriod → Defaulted (silent 5-day expiry)
Disputed → Defaulted | Active | PendingOffChainLiquidation
Fundraising → Canceled (under target)

Investment records

Per-user ledger: mapping(address => InvestmentRecord[]) userInvestments with principalAmount and claimed flag. Global totalProtectedPrincipal tracks vault-covered exposure.

8. Investor Flows

ActionCrowdfundGuaranteed Campaign
Invest during windowCrowdfundProject.invest()GuaranteedCampaign.invest()
One wallet limitYesYes
Refund if failedclaimRefund() — cap not metclaimCanceledRefund() — under target
Claim returns + profitwithdrawReturns() — pro-rataclaimInvestorSettlement() — 57% profit share + principal
Default principal claimNot availableclaimDefaultPrincipal() — 1:1 from vault payout pool
UI route/campaigns/[address]/guaranteed/[address]

Portfolio view: /investments via getUserInvestmentsByWalletAddress()

9. Settlement & Pull-Payment Engine

Gross profit pool (Guaranteed Campaigns only)

Profit calculation
grossProfitPool = totalReturnedFunds - totalProtectedPrincipal

Platform share  = grossProfitPool × 3%   (300 BPS)
Guarantor share = grossProfitPool × 40%  (4000 BPS)  [if onChainProtected]
Investor share  = grossProfitPool × 57%  (5700 BPS)  [pro-rata across investors]
GuaranteedCampaign.sol — fee constants
uint256 private constant PLATFORM_SHARE_BPS  = 300;   // 3%
uint256 private constant GUARANTOR_SHARE_BPS = 4000;  // 40%
uint256 private constant INVESTOR_SHARE_BPS  = 5700;  // 57%

Pull-payment rule

Every actor must execute their own claim transaction and pay gas:

  • claimInvestorSettlement() — investors
  • claimGuarantorFee() — guarantor
  • claimPlatformFee() — admin

On settlement, vault releaseGuarantee() restores guarantor credit lines.

Crowdfund platform fee (different model)

Crowdfund uses platformFeePercent set at admin approval — range 1% to 50% of total raised amount, deducted at creator withdrawal. This is separate from the guaranteed campaign 3% profit share.

10. Disputes, Grace Periods & Default Payout

5-day silent grace window

GRACE_PERIOD = 5 days after maturity. Three scenarios:

ScenarioTriggerOutcome
A: Delayed repaymentInvestee repays within 5 daysStandard settlement pull routing
B: Dispute loggedInvestee pays disputeFeeUSDT (default 500 USDT) via fileDispute()State → Disputed; grace paused; admin resolves
C: Silent default5 days pass with zero repayment and zero disputetriggerSilentDefault() → vault payout → Defaulted

Dispute verdicts (admin-enforced)

GuaranteedCampaign.resolveDispute()
enum DisputeVerdict {
    Rejected,           // → full default payout sequence
    PartiallyAccepted,  // → partial vault payout + timeline extension
    Accepted            // → no payout; maturity extended
}

Default payout & nuclear credit burn

  1. Campaign calls GuaranteeVault.executeDefaultPayout(guarantor, totalProtectedPrincipal)
  2. Vault pays min(deposit, principal) to campaign contract
  3. _applyNuclearBurn(guarantor) — freezes profile, zeroes all credit capacity permanently
  4. Investors independently claimDefaultPrincipal() at their own gas cost
  5. Guarantor fees voided on default — fees never deducted from investor principal compensation
GuaranteeVault._applyNuclearBurn()
profile.isFrozen = true;
profile.usedCredit = 0;
profile.activeProjects = 0;
profile.totalCreditCapacity = 0;
profile.adminAllocatedMargin = 0;
liquidityLocked[guarantor] = 0;
emit GuarantorNuclearBurn(guarantor);

Vault insolvency shortfall

If guarantor deposit < protected principal: pro-rata liquidation of available USDT, campaign state → PendingOffChainLiquidation with expectedLiquidationWindow = 180 days. HalalFi Ops enforces physical collateral off-chain.

11. Smart Contract Reference

ContractPathResponsibility
GuaranteeVaultcontracts/guarantee/GuaranteeVault.solMaster vault, credit engine, default payout, guarantor lifecycle
GuaranteedCampaigncontracts/guarantee/GuaranteedCampaign.solInsured campaign state machine, settlement, disputes
GuaranteedCampaignFactorycontracts/guarantee/GuaranteedCampaignFactory.solCampaign deployment, fallback modes, dispute fee config
CrowdfundProjectcontracts/CrowdfundProject.solPer-project cap fundraising, dual approval, invest/refund/returns
CrowdfundFactorycontracts/CrowdfundFactory.solProject deployment, guarantor approval orchestration, query APIs
CrowdfundAdmincontracts/CrowdfundAdmin.solMulti-admin registry, project approval, vault proxy functions
CrowdfundLenscontracts/CrowdfundLens.solRead-only aggregation lens
CrowdfundStructscontracts/libraries/CrowdfundStructs.solShared view structs for factory queries

Key factory query functions

  • getAllProjectsDetails() — all crowdfund summaries
  • getProjectDetails(address) — single project + investor list
  • getProjectsPendingGuarantorApproval(guarantor) — guarantor inbox
  • getUserInvestmentsByWalletAddress(wallet) — portfolio
  • getActiveGuarantorSummaries() — creator picker (excludes frozen)
  • getPendingGuarantorSummaries() — admin KYB queue

12. Data Structures

IGuaranteeVault.GuarantorProfile
struct GuarantorProfile {
    bool isApproved;
    bool isFrozen;
    uint256 depositedUSDT;
    uint256 usedCredit;
    uint256 totalCreditCapacity;  // (depositedUSDT × 3) + adminAllocatedMargin
    uint256 adminAllocatedMargin;
    uint256 activeProjects;
}
GuaranteedCampaign.InvestmentRecord
struct InvestmentRecord {
    uint256 principalAmount;
    bool claimed;                 // prevents double-claim
}
CrowdfundStructs.ProjectSummary (excerpt)
struct ProjectSummary {
    address projectAddress;
    address creator;
    uint256 raiseAmount;
    uint256 cap;
    uint256 totalRaised;
    address guarantor;
    bool useOnChainGuarantor;
    uint256 guaranteeAmount;
    bool adminApproved;
    bool guarantorApproved;
    bool isApproved;
    bool isDeclined;
}

13. Fee Structures

Fee TypeSpecImplementationStatus
Guaranteed — platform profit share3%PLATFORM_SHARE_BPS = 300Implemented
Guaranteed — guarantor profit share40%GUARANTOR_SHARE_BPS = 4000Implemented
Guaranteed — investor profit share57%INVESTOR_SHARE_BPS = 5700Implemented
Dispute arbitration fee$500 USDTdisputeFeeUSDT (configurable by admin)Implemented
Crowdfund platform feeAdmin sets 1–50%approveProject(project, feePercent)Implemented
Default fee nullificationNo fees on defaultGuarantor fee claim blocked in Defaulted stateImplemented

14. Security & Test Coverage

Security patterns

  • ReentrancyGuard on all withdrawal, settlement, and vault liquidation paths
  • State updates before external transfers (checks-effects-interactions)
  • Fixed-point math via BPS (10_000 basis points) for all fee calculations
  • Campaign registry prevents unauthorized vault calls
  • No ERC-20 credit tokens — internal accounting only

Test suite — GuaranteeSystem.test.js (9 tests)

  • Guarantor onboarding, 3× capacity, duplicate rejection
  • First-project ceiling & 50% concentration enforcement
  • Liquidity lock during active credit; unlock on cancel
  • Successful settlement with exact 3%/40%/57% split + pull claims
  • Silent default after 5-day grace + nuclear burn + investor principal claim
  • Dispute rejected → default payout path
  • Insolvency shortfall → PendingOffChainLiquidation
  • Access control on vault admin/factory functions
  • Multi-investor accounting invariants (performance-style)

Test suite — CrowdfundV2.test.js (17 tests)

  • Deployment wiring, cap-based config, admin migration
  • Cap success + creator withdraw with platform fee
  • Refund when cap not met
  • Platform fee bounds (1–50%)
  • Pro-rata returns distribution, no double-claim
  • Dual approval flow (admin → guarantor → live)
  • Admin-only approval when no on-chain guarantor
  • Factory query APIs, user action state, edge cases + fuzz invariant

Test gaps (not yet covered)

  • Dispute partially accepted & accepted verdict paths
  • Guaranteed campaign off-chain fallback mode (LaunchOffChain)
  • Guarantor freeze/delete admin actions
  • Crowdfund creator withdrawal deadline enforcement (config exists, not enforced on-chain)

Run tests: npx hardhat test — 26 passing

15. Frontend & Data Layer

Stack

  • Next.js 15 App Router · shadcn/ui · Tailwind CSS
  • Wagmi v2 + viem + RainbowKit (BNB Chain mainnet)
  • Alchemy RPC with batched multicall (web/src/lib/alchemy-client.ts)
  • TanStack Query with 30s stale time for chain reads

Routes

RoutePurpose
/Dashboard — stats, contract addresses
/campaignsBrowse crowdfund projects
/campaigns/[address]Invest, refund, returns, creator actions
/guaranteedBrowse guaranteed campaigns
/guaranteed/[address]Invest, repay, dispute, default claims
/createCreate crowdfund project (optional on-chain guarantor)
/guarantorRequest guarantor status, deposit, approve projects
/adminReview projects, guarantor requests, fees, freeze/delete
/investmentsUser portfolio across all projects
/docsThis documentation

Data fetching pattern

All bulk reads use a dedicated Alchemy PublicClient with multicall batching. Guarantor lists use single-call getActiveGuarantorSummaries() instead of N×3 individual reads. Payment proofs indexed from on-chain event logs via getLogs().

16. Deployment (BNB Mainnet)

ContractAddress
CrowdfundAdmin0x318Fb9Cd39a56E50Fc76ADF6059f8A7f30ab151C
CrowdfundFactory0x6cB55a24C8811346F14ff9e8B109cEe752186fb5
CrowdfundLens0xFF1aDF87940D31d0A3a90F2d6a7671C6254466D5
GuaranteeVault0xeF0a7F1162F26E66460DF73B4c11753867bAE80B
GuaranteedCampaignFactory0x30480e566306749dcC4Cb8E19C12ead542f7c9ee
USDT0x55d398326f99059fF775485246999027B3197955

Deploy script: scripts/deploy.js · Deploy order: CrowdfundAdmin → GuaranteeVault(admin=CrowdfundAdmin) → CrowdfundFactory → CrowdfundLens → GuaranteedCampaignFactory → link vault factories via configureVaultFactories()

Server deploy: web/deploy/nginx-setup.sh · Ubuntu + Node.js 20 + PM2 + nginx reverse proxy

17. Full Implementation Matrix

Product specification requirement vs. current codebase status:

RequirementStatusLocation
Off-chain guarantee baseline (process)Off-chain / OpsNot on-chain by design
Binary on-chain vs off-chain at launchImplementeduseOnChainGuarantor / requestOnChainProtection flags
Guarantor KYB request flowImplementedrequestGuarantorship + admin approve/reject
Wallet + entity name on-chain onlyImplementedGuaranteeVault._entityNames
3× credit multiplierImplementedCAPACITY_MULTIPLIER = 3
Admin physical collateral marginImplementedconfigureAdminMargin
First-project ≤ deposit ceilingImplemented_lockGuarantee first-project check
50% investee concentrationImplementedguarantorToInvesteeExposure mapping
Liquidity withdrawal lockImplementedliquidityLocked mapping
Guarantor freeze (hidden from picker)ImplementedsetGuarantorFrozen + getActiveGuarantors
Guarantor delete (zero stake, no projects)ImplementeddeleteGuarantor
Investee selects guarantor from marketplaceImplemented/create page + getActiveGuarantorSummaries
Guarantor accept/reject projectImplementedguarantorApproveProject / guarantorRejectProject
Fallback if guarantor rejects (guaranteed)ImplementedFallbackMode.LaunchOffChain in factory
Expected profit rate at deployImplementedexpectedProfitRateBps in GuaranteedCampaign
Pull-payment settlement (all actors)Implementedclaim* functions on GuaranteedCampaign
3%/40%/57% parallel profit splitImplementedPLATFORM/GUARANTOR/INVESTOR_SHARE_BPS
5-day grace periodImplementedGRACE_PERIOD = 5 days
Silent default (no jury)ImplementedtriggerSilentDefault
Dispute filing with arbitration feeImplementedfileDispute + disputeFeeUSDT
Jury panel on-chain votingNot ImplementedHybrid: off-chain jury, admin passes verdict
Dispute partially accepted verdictPartialresolveDispute(PartiallyAccepted) — coded, not tested
Dispute accepted (timeline extension)PartialresolveDispute(Accepted) — coded, not tested
Nuclear credit burn on defaultImplemented_applyNuclearBurn
Vault insolvency → off-chain queueImplementedPendingOffChainLiquidation + 180-day window
Fee nullification on defaultImplementedNo guarantor fee in Defaulted state
Crowdfund cap-based fundraisingImplementedCrowdfundProject full lifecycle
Crowdfund dual approval (admin + guarantor)ImplementedadminApproveProject + guarantorApproveProject
Crowdfund vault default to investorsNot ImplementedBy design — guarantee is credit lock only
Guaranteed campaign create UINot ImplementedFactory deployed; /guaranteed/create redirects to /create
Creator withdrawal deadline (7 days)PartialConfig in networkConfig; not enforced in withdrawFunds
returnNotifyDate enforcementPartialStored in config; not used in on-chain logic
Performance testsPartialMulti-investor invariant test; no gas benchmarks
Security tests (access control)ImplementedGuaranteeSystem access control test

HalalFi Crowdfunding · BNB Chain Mainnet · Solidity 0.8.34+ · 26 automated tests passing