Pawnable eliminates price-based liquidations entirely.
Collateral is locked on-chain, and liquidation is determined purely by time.
WorldLand Grants Program
This project is supported by the WorldLand Foundation Grants Program. Originally built on Base Sepolia, PAWNABLE has migrated to WorldLand Mainnet (Chain ID: 103) to leverage WorldLand's low-cost, high-performance EVM environment.
👆 Click to watch the full demo on YouTube
PAWNABLE is a decentralized P2P lending platform built on WorldLand blockchain. Borrowers create on-chain loan requests with locked collateral, and lenders fund them directly. No price oracle, no LTV liquidation — only time-based liquidation.
Migration Note: PAWNABLE was originally developed on Base Sepolia (Chain ID: 84532) and has been migrated to WorldLand Mainnet (Chain ID: 103). The native collateral token has changed from ETH to WLC.
Pawnable enhances capital efficiency for tokens launched on RobinPump.fun. Instead of forcing holders to sell volatile bonding-curve tokens, Pawnable enables time-based collateralized borrowing, reducing sell pressure and increasing ecosystem liquidity.
| Feature | Traditional DeFi (AAVE) | PAWNABLE |
|---|---|---|
| Interest Rate | Algorithm-based | Borrower-defined |
| Structure | Liquidity Pool | 1:1 P2P Matching |
| Liquidation | Price-based (oracle) | Time-based (deadline) |
| Collateral | Stays in pool | Locked per request |
| MEV Risk | Liquidation MEV | None |
| Oracle Dependency | Required | None |
┌─────────────┐ ┌──────────────┐
│ BORROWER │ │ LENDER │
└──────┬──────┘ └──────┬───────┘
│ │
│ 1. createLoanRequest() │
│ Collateral locked on-chain │
│ (status: OPEN) │
│ │
│ │ 2. Browse requests
│ │ via API
│ │
│ │ 3. fundLoan()
│ │ Principal → Borrower
├───────────────────────────────────────┤
│ │
│ 4. Loan created (ONGOING) │
│ dueTimestamp set │
│ │
│ 5a. repayLoan() before deadline │
│ → Collateral returned │
│ │
│ 5b. claimCollateral()
│ after deadline
│ → Collateral to Lender
┌────────────────────────────────────────────────────────┐
│ Frontend (Next.js) │
│ - createLoanRequest / cancelLoanRequest │
│ - fundLoan / repayLoan │
│ - Marketplace & Dashboard │
└──────────────────┬─────────────────────────────────────┘
│ REST API (read)
│
┌──────────────────▼─────────────────────────────────────┐
│ Backend (Express + Prisma) │
│ - Pure indexer (no judgment, no validation) │
│ - Loan request & loan listing API │
│ - Token registry │
└──────────────────┬─────────────────────────────────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌──────────────┐ ┌─────────────────────┐
│ PostgreSQL │ │ Smart Contract │
│ (cache) │ │ (source of truth) │
└──────────────┘ └─────────────────────┘
▲
│ events
┌──────┴───────┐
│ Indexer/Bot │
└──────────────┘
USERS (wallet addresses)
│
├──► LOAN_REQUESTS (on-chain collateral-locked orders)
│ │
│ ├──► TOKENS (collateral & principal assets)
│ │
│ └──► LOANS (funded on-chain loans)
│
└──► LOANS (as borrower or lender)
| Table | Description |
|---|---|
| USERS | Wallet address-based user identification |
| TOKENS | ERC20 tokens + native WLC, whitelist management |
| LOAN_REQUESTS | On-chain loan requests (OPEN / FUNDED / CANCELLED) |
| LOANS | Active/completed loans (ONGOING / REPAID / CLAIMED) |
- Framework: Express.js (TypeScript)
- ORM: Prisma (PostgreSQL)
- Validation: Zod
- Framework: Next.js (React 19)
- Styling: Tailwind CSS
- State: React Query
- Network:
Base Sepolia (Chain ID: 84532)→ WorldLand Mainnet (Chain ID: 103) - Native Token: WLC (Worldland)
- Contracts: Solidity 0.8.24 (Foundry)
- Libraries: OpenZeppelin (ReentrancyGuard, SafeERC20)
PAWNABLE/
├── backend/ # Express + Prisma backend
│ ├── prisma/
│ │ └── schema.prisma # Database schema
│ └── src/
│ ├── config/ # Environment & DB config
│ ├── controllers/ # Request handlers
│ ├── services/ # Indexer logic
│ ├── routes/ # API routes
│ ├── middlewares/ # CORS, error handler
│ ├── types/ # TypeScript types
│ ├── validators/ # Zod schemas
│ └── utils/ # Response helpers
│
├── contracts/ # Foundry smart contracts
│ ├── src/
│ │ └── PawnableLoan.sol
│ ├── test/
│ │ └── PawnableLoan.t.sol
│ └── script/
│ └── Deploy.s.sol
│
├── frontend/ # Next.js frontend
│ └── src/
│ ├── app/ # Next.js app router
│ ├── components/ # React components
│ └── hooks/ # Custom hooks
│
└── README.md
- Node.js 24+
- pnpm 10.26.2
- PostgreSQL 16+
- Foundry (for contracts)
git clone https://github.com/EarthIsMine/PAWNABLE.git
cd PAWNABLE
pnpm installcd backend
cp .env.example .env
# Edit .env: DATABASE_URL, LOAN_CONTRACT_ADDRESS
pnpm prisma:push # Schema sync
pnpm seed # Seed tokens (WLC)
pnpm dev # http://localhost:8080cd contracts
cp .env.example .env
# Edit .env: PRIVATE_KEY, WORLDLAND_RPC_URL
forge build # Compile
forge test # Run tests (22 tests)cd frontend
cp .env.example .env
# Edit .env: NEXT_PUBLIC_API_URL=http://localhost:8080
pnpm dev # http://localhost:3000Production deployment runs through GitHub Actions and Docker images.
.github/workflows/deploy-macmini.ymlbuildsbackendandfrontendDocker images forlinux/amd64andlinux/arm64with Buildx.- Images are pushed to GHCR:
ghcr.io/earthismine/pawnable-backendghcr.io/earthismine/pawnable-frontend
- The workflow SSHes into the Mac mini and runs
docker-compose -f docker-compose.prod.yml pull && up. - PostgreSQL runs as a container in the same production compose stack.
- Prisma migration runs on every deployment.
- Seed runs only once, guarded by a
.seededmarker file in/Users/Shared/srv/PAWNABLE.
| Secret | Description |
|---|---|
MACMINI_SSH_KEY |
Private key for SSH access to hoserver@pelicanlab.dev |
MACMINI_POSTGRES_PASSWORD |
Initial PostgreSQL password used when the DB volume is first created |
- Repository cloned at
/Users/Shared/srv/PAWNABLE - Docker Desktop available from the
hoserveraccount docker-composeavailable in a non-interactive SSH shell- GHCR packages set to Public, or Docker logged in to GHCR if private
The deployment script runs
git fetch --allandgit reset --hard origin/main, so do not edit source files directly on the server.
| Function | Caller | Description |
|---|---|---|
createLoanRequest() |
Borrower | Lock collateral, create request (OPEN) |
cancelLoanRequest() |
Borrower | Cancel request, return collateral |
fundLoan() |
Lender | Send principal to borrower, create loan |
repayLoan() |
Borrower | Repay principal+interest, get collateral back |
claimCollateral() |
Anyone | After deadline, collateral goes to lender |
getLoanRequest() |
View | Get request details |
getLoan() |
View | Get loan details |
getRepayAmount() |
View | Calculate repayment amount |
LoanRequest: OPEN ──→ FUNDED (lender funded)
│
└────→ CANCELLED (borrower cancelled)
Loan: ONGOING ──→ REPAID (repaid before deadline)
│
└──────→ CLAIMED (collateral claimed after deadline)
See Backend README for full API documentation.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/loan-requests |
List loan requests |
| GET | /api/loan-requests/:id |
Get request details |
| POST | /api/loan-requests |
Index new request (indexer) |
| PATCH | /api/loan-requests/:id/cancel |
Index cancellation (indexer) |
| GET | /api/loans |
List loans |
| GET | /api/loans/:id |
Get loan details |
| POST | /api/loans |
Index new loan (indexer) |
| PATCH | /api/loans/:id/status |
Update loan status (indexer) |
| GET | /api/tokens |
List supported tokens |
- ReentrancyGuard on all state-changing functions
- SafeERC20 for safe token transfers
- Self-fund prevention (borrower cannot fund own request)
- Time-based liquidation only (no price oracle attack surface)
- No MEV (no liquidation races)
- No admin keys (fully permissionless)
This project is licensed under the MIT License.
Built on WorldLand | Originally built on Base
