Bahasa Indonesia Β· English
Dana jamaah adalah amanah, bukan modal kerja agen.
Setiap rupiah uang muka jamaah dikunci onchain per pos, hanya bisa dibayarkan ke vendor terverifikasi, dan bisa dikembalikan oleh siapa pun.
Kasus First Travel: 63.310 calon jamaah jadi korban, kerugian Rp 905 miliar (Kompas, 5 Jan 2023).
Rekaman layar asli dari konsol agen: Simulasi saja adalah panggilan nyata ke kontrak tanpa mengirim transaksi. Versi EarmarkMismatch yang benar-benar ditambang ada di baris pertama tabel di bawah.
Satu booking, empat percobaan. Agen mencoba memakai faktur hotel Ibu Siti dengan dana Pak Ahmad (EarmarkMismatch), mencoba membayar direkturnya sendiri (VendorClaimMissing), membayar maskapai (β Rp 14.000.000 ke penanda tangan faktur), lalu mencoba mengambil ujrah sebelum jamaah berangkat (NotDeparted). Batas tiket Ibu Siti lewat tanpa tiket dibeli, dan siapa pun bisa menekan refund: setiap rupiah yang belum terpakai kembali kepadanya.
Setiap percobaan itu adalah transaksi yang benar-benar ditambang di Arbitrum One: tiga penolakan di bawah adalah transaksi gagal yang bisa dibuka di Arbiscan, lengkap dengan nama error-nya. Buku besar lengkap ada di DEMO.md.
| Percobaan agen (onchain) | Hasil | Tx |
|---|---|---|
| Faktur hotel Ibu Siti dipakai pada booking Pak Ahmad | EarmarkMismatch |
0x9ed30e38β¦ |
| Faktur yang ditandatangani direktur agen | VendorClaimMissing |
0x887d6b86β¦ |
| Mengirim ulang faktur yang sudah dibayar | InvoiceReplayed |
0x952f30ad⦠|
| Agen menandatangani "keberangkatan" sendiri untuk mengambil ujrah | NotDeparted |
0xe4855ebd⦠|
| Faktur maskapai, Rp 14.000.000 | dibayar ke penanda tangan | 0x7bb513e7β¦ |
Batas tiket Ibu Siti lewat, pihak ketiga memanggil refund |
Rp 23.000.000 kembali ke Ibu Siti | 0x13b8a133β¦ |
Umrah dibayar di muka, sering berbulan-bulan sebelumnya, kepada biro perjalanan berizin (PPIU). Ketika agen memperlakukan dana itu sebagai modal kerja, jamaah baru membiayai keberangkatan jamaah lama sampai semuanya runtuh:
- First Travel: 63.310 calon jamaah, kerugian Rp 905 miliar (Kompas, 5 Jan 2023); pendaftar baru diduga membiayai keberangkatan jamaah sebelumnya (detik, 24 Jul 2017).
- Abu Tours: 86.720 jamaah, perkiraan kerugian Rp 1,8 triliun (Kompas, 29 Jan 2019).
- Skala: sekitar 1,4 juta jamaah berangkat melalui PPIU pada 2024 (data SISKOPATUH sebagaimana dilaporkan HIMPUH, 18 Feb 2025; sumber sekunder).
Mabrur menjadikan uang muka jamaah sebuah aset dunia nyata yang ia pegang sendiri: klaim atas layanan berizin
yang tidak bisa dipindahtangankan (mUMRAH), dipecah menjadi pos TIKET Β· HOTEL Β· VISA Β· UJRAH. Agen hanya punya satu
jalan untuk memindahkan uang.
Fitur utama (semuanya di MabrurPBM.sol):
- π§Ύ Dana dipisah per jamaah: satu tanda tangan permit EIP-2612 ditambah
book()membungkus rupiahnya ke dalam booking miliknya sendiri._updatememblokir setiap transfer, sehingga dana satu jamaah tidak pernah bisa membiayai perjalanan jamaah lain (MabrurPBM.sol:241). - βοΈ Tidak ada yang memilih penerima:
spend()membayar penanda tangan faktur EIP-712, dan hanya jika penanda tangan itu memegang klaim AIRLINE / HOTEL / VISA yang sah diClaimRegistry. Tidak ada kolom alamat yang bisa diisi agen dengan alamat direkturnya (MabrurPBM.sol:162). - π« Ujrah setelah berangkat: ujrah agen (maksimal 20 %) baru terbuka dengan tanda tangan
Departuredari jamaah, atau dari maskapai berizin yang dibayar dari pos tiketnya (MabrurPBM.sol:197). - π« "Tiket lunas atau sisa dana kembali": jika tiket tidak dibayar lunas sebelum
ticketByyang ditandatangani jamaah, atau setelahdepartBylewat, siapa pun (tetangga, LSM, regulator) bisa memanggilrefund()dan setiap rupiah yang belum terpakai kembali ke jamaah (MabrurPBM.sol:218).bookjuga menolak tanggal berangkat lebih dari 180 hari ke depan. - ποΈ Pandangan regulator secara langsung:
regulatorView(agency)(booking terbuka, kewajiban dari buku besar setoran β pembayaran yang independen, dana tersimpan) danconservation()menunjukkan bahwa seluruh uang muka yang belum terpakai dijamin rupiah yang ada di kontrak. - π Tanpa kunci darurat: tidak ada owner, pause, atau jalur upgrade yang bisa menyentuh saldo. Pemilik registry (kunci regulator, tidak pernah agen) hanya menentukan siapa yang diakui sebagai vendor.
| Rekening penampungan bank | Escrow bertahap (pendekatan umum) | Mabrur | |
|---|---|---|---|
| Pemisahan per jamaah | satu rekening gabungan | satu kolam atau per transaksi | dana per booking, ditegakkan di token |
| Siapa memilih penerima | agen memberi instruksi ke bank | agen menulis alamat | tidak ada: penanda tangan faktur yang terverifikasi |
| Ujrah sebelum layanan | agen menarik kapan saja | pada tahap yang ditentukan agen | terkunci sampai keberangkatan ditandatangani |
| Agen menghilang | proses pengadilan, bertahun-tahun | butuh arbiter | refund() oleh siapa pun, tanpa kerja sama agen |
| Pandangan regulator | setelah runtuh, lewat audit | tidak ada | langsung, dari state kontrak |
- Centrifuge, Ondo, Securitize menokenisasi aset milik investor (kredit, obligasi, dana). Mabrur melindungi uang muka milik konsumen: dana jamaah yang sudah dibayar untuk layanan yang belum diterima.
- MAS Project Orchid (Singapura) menguji purpose-bound money untuk voucher dan pembayaran (MAS, 31 Okt 2022). Mabrur menerapkan pola uang berbatas-tujuan itu pada uang muka konsumen: dipisah per jamaah, dibayar hanya ke vendor yang klaimnya terverifikasi, dan dikembalikan oleh siapa pun bila tiket tidak lunas.
flowchart LR
P[Dompet jamaah] -- permit + book --> PBM[MabrurPBM<br/>dana mUMRAH per pos]
A[Konsol agen] -- spend(faktur, tanda tangan vendor) --> PBM
V[Halaman vendor] -- tanda tangan Invoice EIP-712 --> A
PBM -- hasValidClaim --> R[ClaimRegistry<br/>pemilik = regulator]
I[Penerbit: pengganti Kemenhaj / IATA] -- issueClaim --> R
PBM -- penerima = penanda tangan --> V
PBM -- ujrah setelah tanda tangan berangkat --> A
X[Siapa pun] -- refund setelah ticketBy / departBy --> PBM
PBM -- sisa pos --> P
PBM -. regulatorView / conservation .-> G[Panel regulator]
| Lapisan | Teknologi |
|---|---|
| Kontrak | Solidity ^0.8.24, dikompilasi & diverifikasi dengan solc 0.8.33; OpenZeppelin 5.6.1 (ERC20Wrapper, ERC20Permit, EIP712, ECDSA, ReentrancyGuard) |
| Chain | Arbitrum One (42161), ketiga kontrak terverifikasi di Arbiscan; salinan di testnet Arbitrum Sepolia (421614) untuk dicoba siapa saja |
| Tooling | Foundry (unit, fuzz, invariant, skrip yang di-broadcast ke mainnet) |
| Aplikasi | Scaffold-ETH 2: Next.js App Router, wagmi + viem, RainbowKit; error kontrak didekode lewat simulateContract |
| Uang | tIDR: rupiah uji ERC20Permit tanpa nilai moneter, decimals = 0 |
| Kontrak | Alamat (Arbitrum One) |
|---|---|
| MabrurPBM | 0x36f1d899d9d4411b2DdfB60Dbbe989220336d2D5 |
| ClaimRegistry | 0xd5B731CD0f2c91D5D64b59d9E4a2A4E4b6315ADb |
| TIDR (token uji) | 0x66F838be32A624f4C797483a151C7f6209A43448 |
| Kontrak | Alamat (Arbitrum Sepolia, testnet) |
|---|---|
| MabrurPBM | 0xa2BC8c2959b1c9d5A90B750aCFfbD52181CE98f1 |
| ClaimRegistry | 0xCbf1247e292B8eA0bA151a1b5811B732Fc564480 |
| TIDR (token uji) | 0xc56c5561fc65274c55419FC031D90f68AE854B4B |
Coba sendiri di testnet. Tombol Mainnet | Testnet di header aplikasi memindahkan aplikasi ke Arbitrum Sepolia, dengan kontrak yang sama (terverifikasi). Ambil ETH uji gratis dari faucet, tekan faucet tIDR di aplikasi, lalu buat booking sendiri dengan batas tiket Demo: 10 menit, dan refund setelah 10 menit itu lewat: mabrur.edycu.dev/app/jamaah?net=testnet.
Biaya per jamaah (dari receipt asli, script/cost.sh): siklus lengkap jamaah yang berangkat (book + 3 pembayaran
vendor + pelepasan ujrah) memakai 907.185 gas β Rp 809, dan siklus yang di-refund (book + 1 pembayaran + refund)
582.938 gas β Rp 520, dengan kurs ETH/IDR 44.563.294 (CoinGecko, 9 Okt 2026 04:32 UTC). Sekitar 0,0025 % dari
paket Rp 32.000.000.
Untuk juri, tanpa instalasi: buka mabrur.edycu.dev (atau langsung /judge). Semua data bisa dibaca tanpa dompet.
/app/jamaah: cari booking berdasarkan alamat jamaah atau id, lihat status setiap pos langsung dari chain./app/agen: tekan Muat contoh faktur (atau tempel / pilih file faktur bertanda tangan), tekan Simulasi saja, dan dapatkan keputusan kontrak dari Arbitrum One tanpa dompet. Contohnya ada di/demo/invoices.json;/app/agen?contoh=1memuatnya otomatis. Panel regulator ada di sebelah kanan./app/vendor: tandatanganiInvoicedengan kunci burner lalu tempel ke konsol agen.
Jalankan sendiri:
git clone --recursive https://github.com/edycutjong/mabrur.git && cd mabrur
yarn install
cd packages/foundry && forge test # 87 tes: unit, fuzz, invariant
cd ../.. && yarn chain # anvil lokal (terminal 1)
yarn deploy # deploy + buat ABI (terminal 2)
NEXT_PUBLIC_LOCAL_CHAIN=true yarn start # aplikasi di localhost:3000 (terminal 3)Memutar ulang demo Arbitrum One (script/run.sh SeedDemo, script/proof.sh) membutuhkan kunci peran di
~/.config/mabrur/keys.env; kunci itu tidak pernah ada di repo ini.
| Apa | Hasil |
|---|---|
forge test |
87 tes, 0 gagal, cakupan baris Β· cabang Β· fungsi 100 % pada ketiga kontrak: setiap custom error punya tes; tes regresi dinamai sesuai cacat yang dicegahnya (mis. test_ReAddedIssuerDoesNotResurrectOldClaims) |
| Suite invariant | 7 invariant Γ 256 run Γ kedalaman 100: Ξ£ dana per pos == suplai mUMRAH == total tercatat; tIDR di kontrak == suplai + donasi; buku besar per booking dan per agen seimbang; tidak ada pembayaran ke alamat tanpa klaim; tidak ada pembayaran setelah booking bisa di-refund; setelah semua tenggat lewat dan semua di-refund, suplai menjadi 0 |
| Batas T1 | test_CaptureIssuer_Bound: penerbit yang dibajak pun tidak bisa mengambil lebih dari pos TIKET+HOTEL+VISA yang belum kedaluwarsa; ujrah hanya pernah dibayar ke agen |
| Revert yang ditambang | 4 percobaan curang ditambang di Arbitrum One, masing-masing diputar ulang oleh script/proof.sh dan wajib terdekode ke error yang diharapkan |
| Review | 3 putaran review adversarial internal atas kontrak: tidak ada temuan High atau Medium; empat temuan Low di ClaimRegistry (penimpaan klaim penerbit lain, penambahan ulang penerbit, penyempitan topik, klaim kedaluwarsa yang memblokir) diperbaiki dengan tes regresi; putaran 3 tanpa temuan baru (log review) |
| Lapisan | Alat |
|---|---|
| Kontrak | forge fmt --check, forge test (unit, fuzz, invariant), forge coverage di CI |
| Frontend | ESLint (0 peringatan), cek tipe tsc, build produksi Next.js, tes unit Vitest dengan cakupan 100 % per file (gerbang CI), E2E Playwright |
| Keamanan | CodeQL, peringatan + pembaruan Dependabot, gitleaks atas seluruh riwayat, secret scanning + push protection GitHub |
| Rilis | versi semantik dari conventional commits (release.yml); deploy otomatis ke Vercel setelah semua gerbang lulus |
Pertanyaan juri di Demo Day ETHJKT (11 Okt 2026). Jawaban jujurnya: blockchain tidak bisa melihat pesawat atau kamar hotel. Yang dibuktikan kontrak adalah siapa yang dibayar, berapa, dan atas tanda tangan siapa:
| Pertanyaan | Yang ditegakkan kontrak | Bukti |
|---|---|---|
| Apakah penerimanya benar maskapai / hotel? | Penerima selalu penanda tangan faktur, dan penanda tangan itu harus punya klaim berizin yang sesuai pos: pos tiket hanya ke klaim AIRLINE, pos hotel hanya ke klaim HOTEL (topicOf). Agen tidak bisa mengetik alamat penerima, dan tidak bisa membayar dirinya sendiri (SelfDealing) |
MabrurPBM.sol#L162, test_TopicOf_FlightReturnsAirline, test_TopicOf_HotelReturnsHotel, test_Demo_VendorClaimMissing_DirectorCannotBePaid, test_Spend_RevertsSelfDealing_LicensedAgencyAsVendor |
| Apakah tiketnya dibayar sungguhan, bukan "tiket Rp 1"? | Faktur tiket harus melunasi seluruh pos tiket, dan hanya satu kali | test_Spend_RevertsFlightNotFullyPaid_Rp1TicketIsNotATicket, test_Spend_RevertsFlightAlreadyPurchased |
| Faktur untuk booking yang mana? | Faktur menandatangani bookingId; faktur untuk jamaah lain ditolak (EarmarkMismatch, tertambang di mainnet) |
0x9ed30e38β¦ |
| Bisa dicek jamaah ke vendor? | Setiap faktur membawa nomor referensi publik (ref, 32 byte) yang tercatat di event Spent. Di demo isinya nomor faktur (mis. INV-HTL-0001); usulan produksi: PNR maskapai / nomor voucher hotel, yang bisa dicek jamaah langsung ke maskapai atau hotel |
MabrurPBM.sol#L79 |
| Kapan biro mendapat untung? | Ujrah baru cair dengan tanda tangan keberangkatan dari jamaah, atau dari maskapai yang memang dibayar dari pos tiket booking itu dan masih berizin | releaseMargin, test_ReleaseMargin_UnpaidAirlineCannotCoSign, test_ReleaseMargin_RevokedAirlineCannotCoSign |
| Kalau tiket tidak pernah dibeli? | Setelah batas tiket lewat tanpa tiket lunas, siapa pun bisa me-refund sisa dana ke jamaah | 0x13b8a133β¦ |
Yang tidak bisa dibuktikan onchain: apakah layanannya benar-benar diberikan (asumsi T2 di bawah). Vendor berizin yang menagih untuk layanan yang tidak diberikan tetap mungkin; kerugiannya dibatasi oleh pos booking itu, pembayarannya tercatat publik atas nama vendor, dan regulator bisa mencabut klaimnya sehingga pembayaran berikutnya ditolak (test_Spend_RevokedVendorClaimBlocksPayment).
Roadmap (belum dibangun): konfirmasi check-in hotel yang ditandatangani jamaah sebelum pos hotel dibayar penuh, dan klaim vendor yang diterbitkan langsung oleh maskapai / IATA dan regulator, bukan kunci pengganti.
Rencana, bukan traksi. Belum ada pengguna di luar tim, mitra, atau pendapatan. Harga di bawah adalah usulan untuk versi produksi; kontrak hari ini tidak memungut biaya apa pun.
Tiga angka yang sering tertukar:
| Angka | Milik siapa | Artinya |
|---|---|---|
| Ujrah (maks. 20 %) | Biro (PPIU) | Keuntungan biro dari jamaah, terkunci sampai jamaah berangkat (MAX_MARGIN_BPS = 2000, MabrurPBM.sol#L52). Mabrur tidak mengambil bagian apa pun dari ujrah |
| Rp 809 | Biaya jaringan | Gas Arbitrum untuk satu siklus jamaah lengkap (lihat Pengujian & bukti). Ini biaya, bukan pendapatan |
| Rp 50.000 | Mabrur (usulan) | Biaya layanan per jamaah yang dibayar biro |
- Margin per jamaah: Rp 50.000 β Rp 809 β Rp 49.000. Rp 50.000 setara sekitar 0,16 % dari paket Rp 32.000.000.
- Skala (ilustrasi): dari sekitar 1,4 juta jamaah per tahun melalui PPIU (lihat Masalah & solusinya), 1 % adopsi = 14.000 jamaah β Rp 700 juta per tahun; 10 % β Rp 7 miliar per tahun.
- Kenapa biro mau membayar: bukti terbuka bahwa dana jamaah aman, keunggulan kepercayaan terhadap biro promo. Usulan ke regulator (ide, bukan kebijakan yang ada): biro yang memakai earmark mendapat keringanan jaminan bank. Jangka panjang: mandat regulator, sehingga Mabrur menjadi infrastruktur yang dilisensikan ke regulator atau bank penerbit token rupiah.
- Yang tidak akan pernah dilakukan: mengambil bunga atau imbal hasil dari dana jamaah. Dana jamaah adalah amanah; kontrak tidak punya owner, treasury, atau fungsi yield (
MabrurPBM.sol#L19), dan hanya membayar ke tiga pihak: vendor penanda tangan faktur, ujrah biro setelah berangkat, dan refund jamaah. - Risiko yang diakui: pihak yang membayar (biro) adalah pihak yang dibatasi. Biro jujur mendapat keunggulan kepercayaan; biro nakal tidak akan memilihnya, itulah sebabnya jalur jangka panjangnya mandat regulator. Hipotesis ini belum diuji dengan biro sungguhan.
- T1 Β· integritas registry. Pemilik registry adalah kunci regulator, tidak pernah agen. Pemilik atau penerbit yang dibajak bisa mengakui direktur sebagai vendor; kerusakannya terbatas (
test_CaptureIssuer_Bound) dan terlihat onchain. - T2 Β· kejujuran vendor. Vendor berizin bisa menandatangani faktur untuk layanan yang lebih sedikit dari tagihan.
spendmemblokir alamat agen sendiri (SelfDealing) tetapi tidak bisa melihat afiliasi perusahaan; setiap pembayaran dibatasi oleh pos booking tersebut. - T3 Β· penanda tangan keberangkatan. Satu maskapai yang dibayar dari pos tiket bisa ikut menandatangani keberangkatan. Faktur tiket harus melunasi seluruh pos tiket, sehingga "tiket Rp 1" tidak bisa mematikan refund batas tiket (
test_Spend_RevertsFlightNotFullyPaid_Rp1TicketIsNotATicket). - T4 Β· penyimpanan kunci jamaah. Jika agen memegang kunci jamaah, agen bisa menandatangani atas namanya. Jalur produksi: dompet passkey / smart account yang diterbitkan bank atau onramp jamaah, tidak pernah oleh agen.
- Uangnya adalah token uji. Bank Indonesia tidak mengizinkan kripto sebagai alat pembayaran (ANTARA, 15 Jun 2021). Jalur produksinya adalah token rupiah berizin (deposito tertokenisasi bank atau Rupiah Digital) ditambah mandat regulator untuk dana PPIU; keduanya belum ada hari ini. Wrapper menerima ERC-20 standar apa pun.
- Tidak menjamin kursi. Kursi bergantung pada maskapai dan kuota visa. Mabrur menjamin tiket lunas, atau setiap rupiah yang belum terpakai kembali sebelum tanggal yang ditandatangani jamaah.
- Kunci penerbit adalah pengganti Kemenhaj (Kementerian Haji dan Umrah, yang memegang perizinan dan pengawasan PPIU sejak UU 14/2025 dan Permen Haji dan Umrah No. 2/2026; RRI, 6 Agu 2026) / IATA, dan semua nama dalam demo ("PT Amanah Contoh Wisata", "PT Contoh GSA") fiktif.
mabrur/
βββ packages/foundry/
β βββ contracts/ # MabrurPBM, ClaimRegistry, TIDR
β βββ test/ # unit + fuzz, suite invariant, tes cakupan
β βββ script/ # Deploy, Setup, SeedDemo, DemoRun, DemoRefund, proof.sh, cost.sh, ledger.py
β βββ broadcast/ # receipt Arbitrum One yang di-commit (DEMO.md dibuat dari sini)
βββ packages/nextjs/app/app/
β βββ jamaah/ # buku amanah jamaah: booking dengan permit, tanda tangan berangkat, refund
β βββ agen/ # konsol agen: pemuat faktur, cap revert terdekode, panel regulator
β βββ vendor/ # penanda tangan faktur vendor (EIP-712)
βββ DEMO.md # setiap langkah sebagai tautan Arbiscan
βββ JUDGE.md # jalur 30 detik untuk juri
βββ .github/ # CI, CodeQL, gitleaks, Dependabot, rilis
MIT Β© 2026 Edy Cu. Dibangun di atas Scaffold-ETH 2 (MIT, BuidlGuidl).
Dibuat untuk Ethereum Jakarta Hackathon 2026 (ETHJKT Γ HackQuest), track Build the Real World Onchain. Terima kasih kepada mentor dan panitia ETHJKT, OpenZeppelin, Foundry, dan Scaffold-ETH 2.
Cara dibangun. Dibangun solo bersama agen AI (Claude Code) dari spesifikasi desain yang ditulis sebelum sprint; semua kode di-commit setelah 10:00 WIB, 9 Okt 2026 (commit pertama 6837308, 10:38 WIB). Riwayat commit lengkap ada di repo ini.
