Fernlink is a decentralized, open-source protocol for peer-to-peer Solana transaction verification. Nearby devices collaborate over BLE, Wi-Fi/TCP, and NFC to share cryptographically signed verification proofs, removing the dependency on centralized RPC infrastructure.
Full protocol specification:
whitepaper.md
Live website: fernlink.vercel.app
Every wallet and dApp on Solana polls centralized RPC providers to confirm transactions. At scale this:
- Burns RPC credits for every mobile user and developer
- Fails in low-bandwidth or offline environments
- Creates reliability risk when public RPCs are rate-limited or down
Fernlink lets nearby devices act as verification nodes — peers with internet fetch, sign, and return proofs to devices that can't or don't want to hit RPC directly.
- Request — A device broadcasts a lightweight verification request (tx signature + commitment level) over BLE, Wi-Fi/TCP, or NFC.
- Verify — Peers with internet query Solana RPC, build an Ed25519-signed proof, and broadcast it back into the mesh.
- Propagate — Proofs spread via gossip with TTL and UUID deduplication. Two matching proofs settle consensus; conflicting proofs trigger direct RPC fallback.
All proofs are Ed25519-signed. Consensus requires 2+ independent matching proofs. There is no trusted coordinator.
| Package | Language | Registry | Description |
|---|---|---|---|
packages/fernlink-core |
Rust | crates.io | Ed25519 signing/verification, gossip deduplication, stateless consensus, RPC client |
packages/sdk |
TypeScript | npm | Full SDK: FernlinkClient, SimulatedPeer, Web Bluetooth peer, consensus, RPC |
packages/demo |
TypeScript | npm | End-to-end devnet demo CLI (npx fernlink-demo) |
packages/android |
Kotlin + Rust (JNI) | — | Android SDK: BLE GATT service/client, Wi-Fi Direct, NFC bootstrapping, JNI bridge to Rust core |
packages/ios |
Swift | — | iOS SDK: CoreBluetooth BLE, Multipeer Connectivity, CoreNFC bootstrapping |
packages/ble |
TypeScript | — | Desktop BLE transport (bleno/noble) + hardware-free BLE simulator for CI |
packages/wifi |
TypeScript | — | Wi-Fi/TCP transport with mDNS peer discovery (_fernlink._tcp.local.) |
packages/ble-desktop |
Rust | — | Rust desktop binary: BLE + Wi-Fi simultaneously via btleplug/bluer |
| Transport | Android | iOS | TypeScript | Rust |
|---|---|---|---|---|
| BLE (GATT) | ✅ Native Kotlin | ✅ CoreBluetooth | ✅ bleno/noble + simulator | ✅ btleplug/bluer |
| Wi-Fi / TCP | ✅ Wi-Fi Direct | ✅ Multipeer Connectivity | ✅ TCP + mDNS | ✅ mdns-sd |
| NFC (bootstrap) | ✅ Android Beam / NFC | ✅ CoreNFC (read-only) | — | — |
All transports support negotiable LZ4 + zstd wire compression (Protocol v2). A 1-byte codec prefix wraps each payload; peers advertise supported codecs via the STATUS characteristic. Backwards-compatible with uncompressed v1 messages.
- Ed25519 throughout — Rust (
ed25519-dalek), TypeScript (tweetnacl), Android via JNI to Rust core, iOS via CryptoKit - Wire compression — negotiable LZ4 + zstd on every transport (codec byte:
0x00none,0x01LZ4,0x02zstd) - Store-and-forward —
ProofStorequeues up to 64 requests when no peers are connected; drains automatically on reconnect - Multi-transport orchestration —
TransportManageron Android, iOS, and TypeScript coordinates transports simultaneously; falls back to direct RPC if no peers respond within the timeout - Gossip deduplication — UUID-based seen-cache with TTL eviction prevents message storms
npm install fernlink-sdkimport { FernlinkClient } from "fernlink-sdk";
import { TransportManager } from "@fernlink/wifi";
const client = new FernlinkClient({
rpcEndpoint: "https://api.mainnet-beta.solana.com",
minProofs: 2,
});
// Discovers peers automatically via mDNS on the local network
const transport = new TransportManager(client, "https://api.mainnet-beta.solana.com");
await transport.start();
const result = await client.verifyTransaction(txSignature, {
commitment: "confirmed",
timeoutMs: 15_000,
});
console.log(result.status, result.slot, result.proofCount);
// "confirmed" 312847291 3cargo add fernlink-coreuse fernlink_core::{crypto::Keypair, consensus::evaluate, message::VerificationProof};
let keypair = Keypair::generate();
let proof = fernlink_core::crypto::sign_proof(&keypair, &request)?;
let verdict = evaluate(&[proof1, proof2, proof3]);npx fernlink-demoAdd the fernlink-sdk AAR to your Gradle project (see packages/android/README.md):
val client = FernlinkClient(context, rpcEndpoint = "https://api.devnet.solana.com")
client.attachBleService(bleService)
val result = client.verifyTransaction(signature)import FernlinkSDK
let client = FernlinkClient(rpcEndpoint: "https://api.devnet.solana.com")
try await client.start()
let result = try await client.verifyTransaction(signature: sig)/ # Marketing website (Vite + React + Tailwind)
src/pages/ # Index, About, Docs, UseCases, Downloads, Contact
whitepaper.md # Full technical specification
packages/
fernlink-core/ # Rust crate — core protocol (crates.io)
sdk/ # TypeScript SDK (npm: fernlink-sdk)
demo/ # Devnet demo CLI (npm: fernlink-demo)
ble/ # Desktop BLE transport + CI simulator
wifi/ # Wi-Fi/TCP transport with mDNS
ble-desktop/ # Rust desktop binary (BLE + Wi-Fi)
android/
fernlink-sdk/ # Android library (Kotlin + JNI)
fernlink-demo-app/ # Android demo app
fernlink-core-ffi/ # Rust cdylib — JNI exports for Android
ios/
Sources/FernlinkSDK/ # Swift SDK (CoreBluetooth + Multipeer + NFC)
npm install
npm run dev # dev server at http://localhost:8080
npm run build # production build → dist/
npm test # Vitest unit testscd packages/sdk
npm install
npm run build # tsup → dist/ (CJS + ESM + .d.ts)
npm test # Vitest
npm run lint # tsc --noEmitcd packages/fernlink-core
cargo build
cargo test # runs all unit testscd packages/android
./gradlew :fernlink-sdk:assembleRelease
./gradlew :fernlink-demo-app:assembleDebugRebuild native .so files (requires NDK 28 + cargo-ndk):
cd packages/android/fernlink-core-ffi
cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 \
-o ../fernlink-sdk/src/main/jniLibs build --release| Workflow | Trigger | Jobs |
|---|---|---|
rust.yml |
Push/PR to packages/fernlink-core/**, packages/ble-desktop/**, packages/android/fernlink-core-ffi/** |
cargo test (core), cargo build (desktop), cargo check (Android FFI targets), auto-publish to crates.io on merge to main |
sdk.yml |
Push/PR to packages/sdk/**, packages/demo/**, packages/ble/**, packages/wifi/** |
npm test (SDK + BLE simulator), tsc --noEmit (demo + wifi), auto-publish fernlink-sdk and fernlink-demo to npm on merge to main |
android.yml |
Push/PR to packages/android/** |
gradle assembleRelease, uploads debug APK as artifact |
Publishing uses npm Trusted Publishing (OIDC) for npm packages and CARGO_REGISTRY_TOKEN for crates.io. No long-lived npm tokens are stored.
# Rust (no hardware needed)
cd packages/fernlink-core && cargo test
# TypeScript SDK
cd packages/sdk && npm test
# BLE simulator (8 tests, no hardware needed)
cd packages/ble && npm test
# Android JNI (instrumented, requires device or emulator)
cd packages/android
./gradlew :fernlink-sdk:connectedAndroidTestSee TESTING.md for the full test matrix.
Contributions are welcome. Please open an issue or pull request describing:
- The problem you're solving
- Any protocol or security assumptions your change makes
- How it affects existing transport compatibility or the wire format
All transports must remain backwards-compatible with Protocol v1 (uncompressed) peers.