P2P sync engine for offline-first
applications.
One binary. Connect. Sync. No server needed.
Choose your language:
| Language | Package | Import |
|---|---|---|
| Rust | lynk-core crate |
cargo add lynk-core |
| Node.js | @lynk/core npm |
npm install @lynk/core |
| Python | lynk-core pip |
pip install lynk-core |
| C/C++ | lynk_c.h header |
#include "lynk_c.h" |
Every node needs a Lic JWT to connect. It’s a signed token that tells the signaling server who you are and which peers you can reach.
You (developer B) → POST /api/v1/lic/provision → get Lic JWT
→ pass to LynkEngine::new(lic_token)
Via API (one line):
curl -X POST https://api.lynkcore.dev/api/v1/lic/provision \
-H "Authorization: Bearer *** \
-H "Content-Type: application/json" \
-d '{"node_id":"my-node","expires_at":"2027-06-10T00:00:00Z","signaling_mode":"http"}'Via USB bootstrap (offline):
POS operators never see this — plug in a USB stick with
bootstrap.poskey, the app reads lic.jwt from
it automatically.
📖 Full provisioning guide: docs.lynkcore.dev/license
# Cargo.toml
[dependencies]
lynk-core = "0.1"
tokio = { version = "1", features = ["full"] }use lynk_core::{PosyncEngine, SyncEngine, SyncCallback, LicContext, Transaction};
use std::sync::Arc;
struct MyApp;
impl SyncCallback for MyApp {
fn on_transaction_received(&self, tx: Transaction) {
println!("New tx: {} — {}", tx.tx_id, tx.amount);
}
fn on_peer_status_changed(&self, node: &str, online: bool) {
println!("Peer {} is {}", node, if online { "online" } else { "offline" });
}
fn on_sync_completed(&self, n: usize) { println!("Synced {} items", n); }
fn on_master_changed(&self, master: &str) { println!("Master: {}", master); }
// ... implement remaining callbacks with todo!()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Parse your Lic token (from USB bootstrap or API)
let lic: LicContext = serde_json::from_str(r#"{
"lic_id":"lic-001","node_id":"pos-cashier-1","b_id":"b-001",
"expires_at":"2027-06-09T00:00:00Z","signaling_mode":"http"
}")?;
// 2. Create engine with your Lic JWT
let engine = PosyncEngine::new(
lic,
"your-lic-jwt-token".into(), // JWT from provision API
Arc::new(MyApp),
Some("https://api.lynkcore.dev"), // Signaling server URL
)?;
// 3. Connect — engine handles election, discovery, P2P, sync
engine.connect().await?;
// 4. Your POS is now live
println!("I am master: {}", engine.is_master());
// 5. Push transactions — engine routes to master automatically
let tx = Transaction { /* your data */ };
engine.push_transaction(tx)?;
Ok(())
}What happens when you call
connect():
connect()
├── Start LAN master election (UDP broadcast)
├── Wait → become master → register with Signaling
├── Discover peers → establish P2P TCP (AES-256-GCM)
├── Start heartbeat loop (30s)
└── Start queue drainer (10s) → sync pending items
npm install @lynk/coreconst { createLynkEngine } = require('@lynk/core');
async function main() {
// Create engine with Lic JWT + signaling URL
const engine = createLynkEngine(
'your-lic-jwt-token',
'https://api.lynkcore.dev'
);
// Connect — automatic election + P2P
await engine.connect();
console.log('Master:', engine.isMaster());
// Push data
const tx = { txId: 'tx-001', amount: 29900, items: [...] };
engine.pushTransaction(JSON.stringify(tx));
// Poll status
setInterval(() => {
console.log('Peers:', engine.peerStatus());
console.log('Pending sync:', engine.getPending().length);
}, 5000);
}
main().catch(console.error);pip install lynk-corefrom lynk_core import LynkEngine
def main():
# Create engine
engine = LynkEngine(
lic_token="your-lic-jwt-token",
signaling_url="https://api.lynkcore.dev"
)
# Connect — automatic election + P2P
engine.connect()
print(f"Master: {engine.is_master()}")
# Push data
tx = {"tx_id": "tx-001", "amount": 29900}
engine.push_transaction(tx)
# Query
peers = engine.peer_status()
pending = engine.get_pending()
print(f"Peers: {len(peers)}, Pending: {len(pending)}")
if __name__ == "__main__":
main()#include "lynk_c.h"
#include <stdio.h>
void on_tx_received(void* ctx, const lynk_transaction_t* tx) {
printf("New tx: %s — %lld\n", tx->tx_id, tx->amount);
}
int main() {
// Parse Lic context from JSON
lynk_lic_t lic;
lynk_parse_lic("{\"lic_id\":\"lic-001\",...}", &lic);
// Create engine
lynk_engine_t* engine = lynk_engine_new(
&lic,
"your-lic-jwt-token",
"https://api.lynkcore.dev"
);
// Register callback
lynk_on_transaction(engine, on_tx_received, NULL);
// Connect
lynk_connect(engine);
printf("Master: %d\n", lynk_is_master(engine));
// Push transaction
lynk_transaction_t tx = { .tx_id = "tx-001", .amount = 29900 };
lynk_push_transaction(engine, &tx);
// Cleanup
lynk_engine_free(engine);
return 0;
}Compile:
gcc -o myapp myapp.c -llynk_c -lpthread -ldlLic token controls how your node discovers peers (Section 6.9):
| Mode | Best for | Transport |
|---|---|---|
http |
POS, batch sync | REST heartbeat (30s) |
mqtt |
Always-on, real-time | Persistent MQTT (port 8883) |
both |
Hybrid fallback | MQTT first → HTTP backup |
// Provision Lic with MQTT mode (via API)
POST /api/v1/lic/provision
{
"node_id": "pos-1",
"expires_at": "2027-06-10T00:00:00Z",
"signaling_mode": "mqtt"
}┌──────────┐ master election ┌──────────┐
│ POS 1 │◄──── UDP LAN ────►│ POS 2 │
│ (Master) │ │(Follower)│
└────┬─────┘ └──────────┘
│ heartbeat + peer hints
▼
┌──────────┐
│ Signaling │ lynkcore.dev (REST + MQTT)
└──────────┘
│
▼ AES-256-GCM over TCP
Peer-to-peer data sync
(A not involved after handshake)
Key principles: - Data belongs to the shop owner, never passes through A - Offline-first — POS works even without internet - 1 binary — License unlocks features, no reinstall - Master election automatic within LAN branches
Built by A Platform — P2P infrastructure for
developers.
📧 hello@lynkcore.dev