diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipBEBle/AndroidBleTransport.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipBEBle/transport/AndroidBleTransport.kt
similarity index 98%
rename from quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipBEBle/AndroidBleTransport.kt
rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipBEBle/transport/AndroidBleTransport.kt
index 8b646ddcf..640670baa 100644
--- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipBEBle/AndroidBleTransport.kt
+++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipBEBle/transport/AndroidBleTransport.kt
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.transport
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
@@ -41,6 +41,10 @@ import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.os.ParcelUuid
+import com.vitorpamplona.quartz.nipBEBle.AndroidBleTransportContract
+import com.vitorpamplona.quartz.nipBEBle.BleConfig
+import com.vitorpamplona.quartz.nipBEBle.BlePeer
+import com.vitorpamplona.quartz.nipBEBle.BleRole
import com.vitorpamplona.quartz.utils.Log
import java.util.UUID
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrMesh.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrMesh.kt
index edcd8bd9f..7641c0af1 100644
--- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrMesh.kt
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrMesh.kt
@@ -24,6 +24,11 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
+import com.vitorpamplona.quartz.nipBEBle.relay.BleMeshListener
+import com.vitorpamplona.quartz.nipBEBle.relay.BleMeshManager
+import com.vitorpamplona.quartz.nipBEBle.relay.BleNostrServer
+import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
+import com.vitorpamplona.quartz.nipBEBle.transport.BleTransportListener
/**
* Easy-to-use facade for NIP-BE Nostr BLE mesh networking.
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/README.md b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/README.md
new file mode 100644
index 000000000..dad393f67
--- /dev/null
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/README.md
@@ -0,0 +1,145 @@
+# NIP-BE: Nostr BLE Communications Protocol
+
+Kotlin Multiplatform implementation of [NIP-BE](https://github.com/nostr-protocol/nips/blob/master/BE.md)
+for peer-to-peer Nostr event synchronization over Bluetooth Low Energy.
+
+Compatible with [KoalaSat/samiz](https://github.com/KoalaSat/samiz).
+
+## Quick Start (Android)
+
+```kotlin
+// 1. Create the mesh (auto-wires transport)
+val mesh = BleNostrMesh(AndroidBleTransport(context))
+
+// 2. Listen for events from nearby peers
+mesh.onEvent { event, peer ->
+ Log.d("BLE", "Received kind ${event.kind} from ${peer.deviceUuid}")
+ myDatabase.save(event)
+}
+
+// 3. Optional: track peer connectivity
+mesh.onPeerConnected { peer -> updatePeerCount(mesh.connectedPeers().size) }
+mesh.onPeerDisconnected { peer -> updatePeerCount(mesh.connectedPeers().size) }
+mesh.onError { error -> Log.e("BLE", error) }
+
+// 4. Start (begins advertising + scanning)
+mesh.start()
+
+// 5. Broadcast events to all connected peers
+mesh.broadcast(mySignedEvent)
+
+// 6. Stop when done
+mesh.stop()
+```
+
+### Required Android Permissions
+
+```xml
+
+
+
+
+```
+
+Request these at runtime before calling `mesh.start()`.
+
+## How It Works
+
+When two devices running NIP-BE discover each other:
+
+1. **Discovery** - Both devices advertise and scan for the Nostr BLE service UUID
+2. **Role assignment** - The device with the higher UUID becomes the GATT Server (relay),
+ the other becomes the GATT Client
+3. **Connection** - The client connects to the server's GATT service
+4. **Messaging** - NIP-01 JSON messages are DEFLATE-compressed, split into chunks,
+ and exchanged via BLE characteristics
+5. **Event spreading** - New events are automatically forwarded to all connected peers
+
+## Package Structure
+
+```
+nipBEBle/
+ BleNostrMesh.kt # Easy-to-use facade (start here)
+ BleConfig.kt # NIP-BE constants (UUIDs, sizes)
+ BlePeer.kt # Peer data model
+ BleRole.kt # Role assignment (SERVER/CLIENT)
+
+ protocol/ # Wire format
+ BleMessageChunker.kt # DEFLATE compression + chunk splitting/joining
+ BleChunkAssembler.kt # Reassembles incoming chunks into messages
+
+ transport/ # Platform BLE abstraction
+ BleTransport.kt # Interface for BLE operations
+ AndroidBleTransport.kt # Android implementation (androidMain)
+
+ relay/ # Nostr relay protocol over BLE
+ BleNostrClient.kt # Client side (implements IRelayClient)
+ BleNostrServer.kt # Server side (receives commands, sends messages)
+ BleMeshManager.kt # Orchestrates discovery, connections, broadcasting
+```
+
+## Advanced Usage
+
+### Use a BLE peer as a standard relay client
+
+`BleNostrClient` implements `IRelayClient`, so you can use BLE peers alongside
+WebSocket relays in the existing subscription infrastructure:
+
+```kotlin
+val relayClient = mesh.getRelayClient(peer.deviceUuid)
+relayClient?.sendIfConnected(ReqCmd("sub1", listOf(filter)))
+```
+
+### Access raw protocol messages
+
+```kotlin
+mesh.onMessage { relay, msg ->
+ // Nostr messages received when acting as client (EVENT, EOSE, OK, etc.)
+}
+
+mesh.onCommand { server, cmd ->
+ // Nostr commands received when acting as server (REQ, EVENT, CLOSE, etc.)
+}
+```
+
+### Implement a custom BLE transport
+
+For platforms without a built-in transport, implement `BleTransport`:
+
+```kotlin
+class MyPlatformBleTransport : BleTransport {
+ override val deviceUuid = UUID.randomUUID().toString()
+ override fun startAdvertising() { /* ... */ }
+ override fun startScanning() { /* ... */ }
+ // ... other methods
+}
+```
+
+### Use BleMeshManager directly
+
+For full control over the relay protocol layer:
+
+```kotlin
+val transport = AndroidBleTransport(context)
+val manager = BleMeshManager(transport, object : BleMeshListener {
+ override fun onEventReceived(event: Event, fromPeer: BlePeer) { /* ... */ }
+})
+transport.setListener(manager)
+manager.start()
+```
+
+## Wire Protocol
+
+Messages follow [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) JSON format,
+compressed with DEFLATE and split into chunks:
+
+```
+[chunk index (1 byte)][payload (up to 500 bytes)][total chunks (1 byte)]
+```
+
+| GATT Characteristic | UUID | Direction |
+|---------------------|------|-----------|
+| Write | `87654321-0000-1000-8000-00805f9b34fb` | Client -> Server |
+| Read (Notify) | `12345678-0000-1000-8000-00805f9b34fb` | Server -> Client |
+
+Service UUID: `0000180f-0000-1000-8000-00805f9b34fb`
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleChunkAssembler.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleChunkAssembler.kt
similarity index 97%
rename from quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleChunkAssembler.kt
rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleChunkAssembler.kt
index 941681edd..dfd154708 100644
--- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleChunkAssembler.kt
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleChunkAssembler.kt
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.protocol
/**
* Accumulates incoming BLE chunks for a single message and reassembles
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMessageChunker.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleMessageChunker.kt
similarity index 97%
rename from quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMessageChunker.kt
rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleMessageChunker.kt
index 2a2c5b0f3..b26a227a5 100644
--- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMessageChunker.kt
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleMessageChunker.kt
@@ -18,8 +18,9 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.protocol
+import com.vitorpamplona.quartz.nipBEBle.BleConfig
import com.vitorpamplona.quartz.utils.Deflate
/**
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMeshManager.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleMeshManager.kt
similarity index 96%
rename from quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMeshManager.kt
rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleMeshManager.kt
index d7a68989b..f2f4a6574 100644
--- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMeshManager.kt
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleMeshManager.kt
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.relay
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
@@ -27,6 +27,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
+import com.vitorpamplona.quartz.nipBEBle.BlePeer
+import com.vitorpamplona.quartz.nipBEBle.BleRole
+import com.vitorpamplona.quartz.nipBEBle.assignRole
+import com.vitorpamplona.quartz.nipBEBle.protocol.BleMessageChunker
+import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
+import com.vitorpamplona.quartz.nipBEBle.transport.BleTransportListener
import com.vitorpamplona.quartz.utils.Log
/**
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrClient.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleNostrClient.kt
similarity index 94%
rename from quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrClient.kt
rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleNostrClient.kt
index d0deb8ce6..371b701d7 100644
--- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrClient.kt
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleNostrClient.kt
@@ -18,13 +18,18 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.relay
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
+import com.vitorpamplona.quartz.nipBEBle.BleConfig
+import com.vitorpamplona.quartz.nipBEBle.BlePeer
+import com.vitorpamplona.quartz.nipBEBle.protocol.BleChunkAssembler
+import com.vitorpamplona.quartz.nipBEBle.protocol.BleMessageChunker
+import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
import com.vitorpamplona.quartz.utils.Log
/**
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrServer.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleNostrServer.kt
similarity index 94%
rename from quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrServer.kt
rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleNostrServer.kt
index 3e6554b85..2a0b493e6 100644
--- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleNostrServer.kt
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/relay/BleNostrServer.kt
@@ -18,11 +18,16 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.relay
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
+import com.vitorpamplona.quartz.nipBEBle.BleConfig
+import com.vitorpamplona.quartz.nipBEBle.BlePeer
+import com.vitorpamplona.quartz.nipBEBle.protocol.BleChunkAssembler
+import com.vitorpamplona.quartz.nipBEBle.protocol.BleMessageChunker
+import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
import com.vitorpamplona.quartz.utils.Log
/**
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleTransport.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/transport/BleTransport.kt
similarity index 97%
rename from quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleTransport.kt
rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/transport/BleTransport.kt
index 417fc5524..354d1d49f 100644
--- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/BleTransport.kt
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipBEBle/transport/BleTransport.kt
@@ -18,7 +18,9 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.transport
+
+import com.vitorpamplona.quartz.nipBEBle.BlePeer
/**
* Platform-agnostic interface for BLE transport operations.
diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/BleChunkAssemblerTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleChunkAssemblerTest.kt
similarity index 98%
rename from quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/BleChunkAssemblerTest.kt
rename to quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleChunkAssemblerTest.kt
index 691d3b538..523ff36ec 100644
--- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/BleChunkAssemblerTest.kt
+++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleChunkAssemblerTest.kt
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.protocol
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMessageChunkerTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleMessageChunkerTest.kt
similarity index 99%
rename from quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMessageChunkerTest.kt
rename to quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleMessageChunkerTest.kt
index 17f7a6468..e8c6d997e 100644
--- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/BleMessageChunkerTest.kt
+++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipBEBle/protocol/BleMessageChunkerTest.kt
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.quartz.nipBEBle
+package com.vitorpamplona.quartz.nipBEBle.protocol
import kotlin.test.Test
import kotlin.test.assertEquals