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
| System | Contract | Purpose |
|---|---|---|
| Crowdfund Projects | CrowdfundProject.sol | Cap-based fundraising with optional on-chain guarantor lock after dual admin + guarantor approval |
| Guaranteed Campaigns | GuaranteedCampaign.sol | Target-based insured campaigns with full state machine, 3%/40%/57% profit split, disputes, and vault default payout |
GuaranteeVault.sol, mapped to wallet addresses — bypassing secondary-market regulatory risk.2. System Architecture
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ 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 newCrowdfundProjectclone with immutable configGuaranteedCampaignFactory.createCampaign()— deploysGuaranteedCampaignand optionally locks vault credit at creationCrowdfundAdminis 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
| Actor | Platform Role | On-Chain Identity | Status |
|---|---|---|---|
| Guarantor | Deposits USDT, receives 3× credit, underwrites projects, earns 40% profit share | Wallet in GuaranteeVault profiles | Implemented |
| Admin / HalalFi Ops | KYB approval, project approval, dispute verdicts, fee withdrawal | CrowdfundAdmin.isAdmin mapping | Implemented |
| Investee / Creator | Creates campaigns, repays principal + profit, files disputes | msg.sender at createProject/createCampaign | Implemented |
| Investor | Deploys USDT capital; protection rules set by project binary config | Per-project investment mapping | Implemented |
| Jury / Review Team | Evaluates dispute evidence, issues majority verdict | Hybrid off-chain panel; admin passes verdict on-chain | Partial |
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
- Prospective guarantor calls
GuaranteeVault.requestGuarantorship(entityName)from their wallet - Only
wallet addressandentity nameare stored on-chain (corporate docs, contact details, asset proof remain off-chain per spec) - Admin reviews pending requests via
getPendingGuarantorSummaries() - Admin accepts via
CrowdfundAdmin.approveGuarantorRequest(guarantor)or rejects viarejectGuarantorRequest(guarantor) - Duplicate requests blocked:
require(!hasPendingRequest[msg.sender])andrequire(!profile.isApproved)
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:
totalCreditCapacity = (depositedUSDT × 3) + adminAllocatedMargin availableCredit = totalCreditCapacity - usedCredit
Admin guarantor management
| Action | Function | Condition |
|---|---|---|
| Accept request | CrowdfundAdmin.approveGuarantorRequest | Pending request exists |
| Reject request | CrowdfundAdmin.rejectGuarantorRequest | Pending request exists |
| Freeze | CrowdfundAdmin.setGuarantorFrozen(guarantor, true) | Hidden from creator picker; capacity = 0 |
| Unfreeze | CrowdfundAdmin.setGuarantorFrozen(guarantor, false) | Rebuilds capacity |
| Delete | CrowdfundAdmin.deleteGuarantor | activeProjects = 0 AND depositedUSDT = 0 |
| Admin margin boost | CrowdfundAdmin.configureGuarantorMargin | Adds 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:
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]:
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.
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
- Creator submits via
CrowdfundFactory.createProject(config, guarantor, useOnChainGuarantor, guaranteeAmount) - Project starts with
adminApproved = false,isApproved = false - Admin loads project in
/admin, sets platform fee 1–50%, callsapproveProject(project, feePercent)— no reason field - If on-chain guarantor selected: guarantor must call
guarantorApproveProject()→ vault locks guarantee → project goes live - If no on-chain guarantor: live immediately after admin approval
Pending admin → [if useOnChainGuarantor] Awaiting guarantor → Live
↓ admin reject ↓ guarantor reject
Declined DeclinedInvestment & cap logic
invest(amount)— one investment per wallet, min/max bounds, USDT onlycap= minimum success threshold;raiseAmount= hard upper bound- After
endDate: iftotalRaised >= cap→ creator maywithdrawFunds()(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
7. Guaranteed Campaigns (Target-Based)
Creation
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
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
| Action | Crowdfund | Guaranteed Campaign |
|---|---|---|
| Invest during window | CrowdfundProject.invest() | GuaranteedCampaign.invest() |
| One wallet limit | Yes | Yes |
| Refund if failed | claimRefund() — cap not met | claimCanceledRefund() — under target |
| Claim returns + profit | withdrawReturns() — pro-rata | claimInvestorSettlement() — 57% profit share + principal |
| Default principal claim | Not available | claimDefaultPrincipal() — 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)
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]
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()— investorsclaimGuarantorFee()— guarantorclaimPlatformFee()— 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:
| Scenario | Trigger | Outcome |
|---|---|---|
| A: Delayed repayment | Investee repays within 5 days | Standard settlement pull routing |
| B: Dispute logged | Investee pays disputeFeeUSDT (default 500 USDT) via fileDispute() | State → Disputed; grace paused; admin resolves |
| C: Silent default | 5 days pass with zero repayment and zero dispute | triggerSilentDefault() → vault payout → Defaulted |
Dispute verdicts (admin-enforced)
enum DisputeVerdict {
Rejected, // → full default payout sequence
PartiallyAccepted, // → partial vault payout + timeline extension
Accepted // → no payout; maturity extended
}Default payout & nuclear credit burn
- Campaign calls
GuaranteeVault.executeDefaultPayout(guarantor, totalProtectedPrincipal) - Vault pays min(deposit, principal) to campaign contract
_applyNuclearBurn(guarantor)— freezes profile, zeroes all credit capacity permanently- Investors independently
claimDefaultPrincipal()at their own gas cost - Guarantor fees voided on default — fees never deducted from investor principal compensation
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
| Contract | Path | Responsibility |
|---|---|---|
| GuaranteeVault | contracts/guarantee/GuaranteeVault.sol | Master vault, credit engine, default payout, guarantor lifecycle |
| GuaranteedCampaign | contracts/guarantee/GuaranteedCampaign.sol | Insured campaign state machine, settlement, disputes |
| GuaranteedCampaignFactory | contracts/guarantee/GuaranteedCampaignFactory.sol | Campaign deployment, fallback modes, dispute fee config |
| CrowdfundProject | contracts/CrowdfundProject.sol | Per-project cap fundraising, dual approval, invest/refund/returns |
| CrowdfundFactory | contracts/CrowdfundFactory.sol | Project deployment, guarantor approval orchestration, query APIs |
| CrowdfundAdmin | contracts/CrowdfundAdmin.sol | Multi-admin registry, project approval, vault proxy functions |
| CrowdfundLens | contracts/CrowdfundLens.sol | Read-only aggregation lens |
| CrowdfundStructs | contracts/libraries/CrowdfundStructs.sol | Shared view structs for factory queries |
Key factory query functions
getAllProjectsDetails()— all crowdfund summariesgetProjectDetails(address)— single project + investor listgetProjectsPendingGuarantorApproval(guarantor)— guarantor inboxgetUserInvestmentsByWalletAddress(wallet)— portfoliogetActiveGuarantorSummaries()— creator picker (excludes frozen)getPendingGuarantorSummaries()— admin KYB queue
12. Data Structures
struct GuarantorProfile {
bool isApproved;
bool isFrozen;
uint256 depositedUSDT;
uint256 usedCredit;
uint256 totalCreditCapacity; // (depositedUSDT × 3) + adminAllocatedMargin
uint256 adminAllocatedMargin;
uint256 activeProjects;
}struct InvestmentRecord {
uint256 principalAmount;
bool claimed; // prevents double-claim
}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 Type | Spec | Implementation | Status |
|---|---|---|---|
| Guaranteed — platform profit share | 3% | PLATFORM_SHARE_BPS = 300 | Implemented |
| Guaranteed — guarantor profit share | 40% | GUARANTOR_SHARE_BPS = 4000 | Implemented |
| Guaranteed — investor profit share | 57% | INVESTOR_SHARE_BPS = 5700 | Implemented |
| Dispute arbitration fee | $500 USDT | disputeFeeUSDT (configurable by admin) | Implemented |
| Crowdfund platform fee | Admin sets 1–50% | approveProject(project, feePercent) | Implemented |
| Default fee nullification | No fees on default | Guarantor fee claim blocked in Defaulted state | Implemented |
14. Security & Test Coverage
Security patterns
ReentrancyGuardon 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
| Route | Purpose |
|---|---|
| / | Dashboard — stats, contract addresses |
| /campaigns | Browse crowdfund projects |
| /campaigns/[address] | Invest, refund, returns, creator actions |
| /guaranteed | Browse guaranteed campaigns |
| /guaranteed/[address] | Invest, repay, dispute, default claims |
| /create | Create crowdfund project (optional on-chain guarantor) |
| /guarantor | Request guarantor status, deposit, approve projects |
| /admin | Review projects, guarantor requests, fees, freeze/delete |
| /investments | User portfolio across all projects |
| /docs | This 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)
| Contract | Address |
|---|---|
| CrowdfundAdmin | 0x318Fb9Cd39a56E50Fc76ADF6059f8A7f30ab151C |
| CrowdfundFactory | 0x6cB55a24C8811346F14ff9e8B109cEe752186fb5 |
| CrowdfundLens | 0xFF1aDF87940D31d0A3a90F2d6a7671C6254466D5 |
| GuaranteeVault | 0xeF0a7F1162F26E66460DF73B4c11753867bAE80B |
| GuaranteedCampaignFactory | 0x30480e566306749dcC4Cb8E19C12ead542f7c9ee |
| USDT | 0x55d398326f99059fF775485246999027B3197955 |
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:
| Requirement | Status | Location |
|---|---|---|
| Off-chain guarantee baseline (process) | Off-chain / Ops | Not on-chain by design |
| Binary on-chain vs off-chain at launch | Implemented | useOnChainGuarantor / requestOnChainProtection flags |
| Guarantor KYB request flow | Implemented | requestGuarantorship + admin approve/reject |
| Wallet + entity name on-chain only | Implemented | GuaranteeVault._entityNames |
| 3× credit multiplier | Implemented | CAPACITY_MULTIPLIER = 3 |
| Admin physical collateral margin | Implemented | configureAdminMargin |
| First-project ≤ deposit ceiling | Implemented | _lockGuarantee first-project check |
| 50% investee concentration | Implemented | guarantorToInvesteeExposure mapping |
| Liquidity withdrawal lock | Implemented | liquidityLocked mapping |
| Guarantor freeze (hidden from picker) | Implemented | setGuarantorFrozen + getActiveGuarantors |
| Guarantor delete (zero stake, no projects) | Implemented | deleteGuarantor |
| Investee selects guarantor from marketplace | Implemented | /create page + getActiveGuarantorSummaries |
| Guarantor accept/reject project | Implemented | guarantorApproveProject / guarantorRejectProject |
| Fallback if guarantor rejects (guaranteed) | Implemented | FallbackMode.LaunchOffChain in factory |
| Expected profit rate at deploy | Implemented | expectedProfitRateBps in GuaranteedCampaign |
| Pull-payment settlement (all actors) | Implemented | claim* functions on GuaranteedCampaign |
| 3%/40%/57% parallel profit split | Implemented | PLATFORM/GUARANTOR/INVESTOR_SHARE_BPS |
| 5-day grace period | Implemented | GRACE_PERIOD = 5 days |
| Silent default (no jury) | Implemented | triggerSilentDefault |
| Dispute filing with arbitration fee | Implemented | fileDispute + disputeFeeUSDT |
| Jury panel on-chain voting | Not Implemented | Hybrid: off-chain jury, admin passes verdict |
| Dispute partially accepted verdict | Partial | resolveDispute(PartiallyAccepted) — coded, not tested |
| Dispute accepted (timeline extension) | Partial | resolveDispute(Accepted) — coded, not tested |
| Nuclear credit burn on default | Implemented | _applyNuclearBurn |
| Vault insolvency → off-chain queue | Implemented | PendingOffChainLiquidation + 180-day window |
| Fee nullification on default | Implemented | No guarantor fee in Defaulted state |
| Crowdfund cap-based fundraising | Implemented | CrowdfundProject full lifecycle |
| Crowdfund dual approval (admin + guarantor) | Implemented | adminApproveProject + guarantorApproveProject |
| Crowdfund vault default to investors | Not Implemented | By design — guarantee is credit lock only |
| Guaranteed campaign create UI | Not Implemented | Factory deployed; /guaranteed/create redirects to /create |
| Creator withdrawal deadline (7 days) | Partial | Config in networkConfig; not enforced in withdrawFunds |
| returnNotifyDate enforcement | Partial | Stored in config; not used in on-chain logic |
| Performance tests | Partial | Multi-investor invariant test; no gas benchmarks |
| Security tests (access control) | Implemented | GuaranteeSystem access control test |