OP_Scribe is a three-layer application built on Bitcoin Layer 1 via OP_NET that provides cryptographic proof of file existence by storing IPFS content identifiers (CIDs) on-chain with associated metadata.
┌──────────────────────────────────────────────────┐
│ Frontend (Vite + React + OPWallet) │
│ - Upload page (drag & drop) │
│ - File browser (enumerate on-chain records) │
│ - Verification page (check CID proof) │
└───────────────┬──────────────┬───────────────────┘
│ │
IPFS Upload Contract Calls
(HTTP POST) (via OPWallet)
│ │
┌───────────────▼──────┐ ┌───▼───────────────────┐
│ Backend (Node.js) │ │ OP_NET Smart Contract │
│ - Pinata IPFS API │ │ - registerFile() │
│ - File validation │ │ - getFile() │
│ - CID return │ │ - checkFileExists() │
└──────────────────────┘ │ - getFileByIndex() │
│ - getTotalFiles() │
│ - pause/unpause │
└────────────────────────┘
op-scribe/
├── contract/ # OP_NET smart contract (AssemblyScript -> WASM)
│ ├── src/
│ │ ├── index.ts # Entry point with factory + abort handler
│ │ └── OPScribe.ts # Contract implementation
│ ├── build/
│ │ └── OPScribe.wasm # Compiled contract
│ ├── abis/
│ │ └── OPScribe.abi.json # Auto-generated ABI
│ ├── asconfig.json
│ └── package.json
│
├── backend/ # Node.js IPFS upload API
│ ├── src/
│ │ └── index.ts # HTTP server with Pinata integration
│ ├── .env.example # Environment variable template
│ ├── tsconfig.json
│ └── package.json
│
├── frontend/ # Vite + React + TypeScript dApp
│ ├── src/
│ │ ├── main.tsx
│ │ ├── App.tsx # Root component with tab navigation
│ │ ├── abi/
│ │ │ └── OPScribeAbi.ts # Contract ABI + TypeScript interface
│ │ ├── components/
│ │ │ ├── UploadPage.tsx # File upload + IPFS + on-chain
│ │ │ ├── BrowsePage.tsx # Browse registered files
│ │ │ └── VerifyPage.tsx # Verify CID on-chain
│ │ ├── services/
│ │ │ └── ipfs.ts # Backend API client
│ │ ├── styles/
│ │ │ └── app.css # Full dark theme CSS
│ │ └── types/
│ │ └── config.ts # Config constants
│ ├── index.html
│ ├── vite.config.ts
│ ├── tsconfig.json
│ └── package.json
│
└── README.md
- Node.js >= 24
- npm
- A Pinata account (free tier: 1GB, 100 files)
cd contract
npm install --legacy-peer-deps
npm run build
# Output: build/OPScribe.wasm + abis/OPScribe.abi.jsonNOTE: After npm install, you may need to patch node_modules/bs58check
to use @noble/hashes/sha2.js instead of @noble/hashes/sha256 due to
the v2 export map change. The build script handles this automatically if
you use the included setup.
cd backend
npm install
cp .env.example .env
# Edit .env and add your Pinata JWT
npm run dev
# Server starts on http://localhost:3001cd frontend
npm install --legacy-peer-deps
npm run dev
# Dev server starts on http://localhost:5173| Method | Selector | Description |
|---|---|---|
registerFile(cid, fileName, fileSize) |
0x44887d59 |
Register a new file proof |
getFile(cid) |
0xb3ff079b |
Get file record by CID |
checkFileExists(cid) |
0xd007e2ec |
Check if CID is registered |
getTotalFiles() |
0x42b9be00 |
Total registered files |
getFileByIndex(index) |
0x8958fbb0 |
Get file by sequential index |
pause() |
0x2138ec0c |
Pause contract (deployer only) |
unpause() |
0x8144711b |
Unpause contract (deployer only) |
getIsPaused() |
0x28b3bca6 |
Check pause status |
- User selects a file in the frontend
- Frontend sends file to backend via POST /upload
- Backend pins file to Pinata IPFS, returns CID + metadata
- Frontend calls registerFile(cid, fileName, fileSize) via OPWallet
- User signs the OP_NET transaction in their wallet
- Contract stores the proof on Bitcoin L1
- Anyone can verify the proof using the Verify page
- Pointer 0: paused (bool)
- Pointer 1: totalFiles (u256 counter)
- Pointer 2: fileSizes (cidHash -> u256)
- Pointer 3: fileUploaders (cidHash -> u256-encoded address)
- Pointer 4: fileBlocks (cidHash -> u256 block number)
- Pointer 5: fileTimestamps (cidHash -> u256 timestamp)
- Pointer 6: fileExists (cidHash -> u256 0/1)
- Pointer 7: fileNameChunks (cidHash-based -> chunked string)
- Pointer 8: cidChunks (index-based -> chunked string)
- Deploy contract to regtest
- Set CONTRACT_ADDRESS in frontend/src/types/config.ts
- Integrate WalletConnect for real wallet connection
- Wire up contract calls in Upload/Browse/Verify pages (commented-out code shows the exact pattern)
- Test end-to-end on regtest
- Deploy to IPFS for static hosting
- All u256 arithmetic uses SafeMath (overflow/underflow protection)
- File CID uniqueness enforced (no duplicate registrations)
- Deployer-only pause/unpause
- No BTC custody (verify-don't-custody pattern)
- 10MB file size limit enforced at backend level
- CORS restricted to allowed origins
- Smart Contract: AssemblyScript + @btc-vision/btc-runtime
- Backend: Node.js + Pinata SDK (TypeScript)
- Frontend: Vite + React + TypeScript
- Wallet: @btc-vision/walletconnect (OPWallet)
- Network: OP_NET regtest (regtest.opnet.org)
MIT