diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index b62430929ac803a47eaa221b53765c2b9c520802..52521d349425f41e6aa69d1c96efb217fd0fabdc 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -122,8 +122,11 @@
             <AndroidTestResultsTableState>
               <option name="preferredColumnWidths">
                 <map>
+                  <entry key="29211JEGR13699" value="120" />
                   <entry key="Duration" value="90" />
+                  <entry key="Google&#10; Pixel 6a" value="120" />
                   <entry key="Pixel_3a_API_33_x86_64" value="120" />
+                  <entry key="Pixel_C_API_33" value="120" />
                   <entry key="Tests" value="360" />
                 </map>
               </option>
@@ -195,6 +198,7 @@
                   <entry key="Duration" value="90" />
                   <entry key="Google&#10; Pixel 6a" value="120" />
                   <entry key="Pixel_3a_API_33_x86_64" value="120" />
+                  <entry key="Pixel_C_API_33" value="120" />
                   <entry key="R5CNC07TZCY" value="120" />
                   <entry key="Tests" value="360" />
                   <entry key="samsung&#10; SM-G998B" value="120" />
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8978d23db569daa721cb26dde7923f4c673d1fc9..773fe0fbde80a5dbea80f02ad9c211a31f18a53f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
 <project version="4">
   <component name="ExternalStorageConfigurationManager" enabled="true" />
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">
diff --git a/app/src/androidTest/java/com/futo/polycentric/core/ProcessHandleTests.kt b/app/src/androidTest/java/com/futo/polycentric/core/ProcessHandleTests.kt
index fa3d03619acf20ca9d452e0f50f3142d25741f0b..9f2c1a765b34fbc9132bbecb668e7f8ec22787b1 100644
--- a/app/src/androidTest/java/com/futo/polycentric/core/ProcessHandleTests.kt
+++ b/app/src/androidTest/java/com/futo/polycentric/core/ProcessHandleTests.kt
@@ -2,16 +2,25 @@ package com.futo.polycentric.core
 
 import android.content.Context
 import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
 import android.util.Log
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.google.protobuf.ByteString
+import com.goterl.lazysodium.interfaces.Sign
 import org.junit.Assert.*
 import org.junit.Test
 import org.junit.runner.RunWith
 import userpackage.Protocol
 import userpackage.Protocol.QueryReferencesRequestCountReferences
 import java.io.ByteArrayOutputStream
+import java.io.DataInputStream
+
+
+
 
 @RunWith(AndroidJUnit4::class)
 class ProcessHandleTests {
@@ -86,16 +95,78 @@ class ProcessHandleTests {
         Store.initializeMemoryStore()
 
         val processHandle = ProcessHandle.create()
+        processHandle.addServer(TestConstants.SERVER)
 
-        val fakeImage = byteArrayOf(1, 2, 3, 4)
-        val imagePointer = processHandle.publishBlob("image/png", fakeImage)
-        processHandle.setAvatar(imagePointer)
+        //Load image
+        val imageBytes: ByteArray
+        run {
+            val context = getInstrumentation().targetContext
+            val imageId = context.resources.getIdentifier("image", "drawable", context.packageName)
+            val bitmap = BitmapFactory.decodeResource(context.resources, imageId)
+            val stream = ByteArrayOutputStream()
+            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
+            imageBytes = stream.toByteArray()
+            stream.close()
+        }
 
-        val serverState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(processHandle.system))
-        val loadedAvatar = processHandle.loadBlob(serverState.avatar!!)!!
+        //Set avatar
+        val eventLogicalClock: Long
+        run {
+            eventLogicalClock = processHandle.publishAvatar(listOf(
+                ImageData("image/jpeg", 2560, 424, imageBytes)
+            )).logicalClock
+        }
 
-        assertEquals("image/png", loadedAvatar.mime)
-        assertArrayEquals(fakeImage, loadedAvatar.content)
+        processHandle.fullyBackfillServers()
+
+        //Read avatar and compare
+        run {
+            val avatarProcessRangesToGet = Protocol.RangesForProcess.newBuilder()
+                .addRanges(Protocol.Range.newBuilder().setLow(eventLogicalClock).setHigh(eventLogicalClock).build())
+                .setProcess(processHandle.processSecret.process.toProto())
+                .build()
+            val avatarEvents = ApiMethods.getEvents(TestConstants.SERVER, processHandle.system.toProto(), Protocol.RangesForSystem.newBuilder()
+                .addRangesForProcesses(avatarProcessRangesToGet).build())
+
+            val avatarSignedEvent = avatarEvents.eventsList.first()
+            val avatarEvent = SignedEvent.fromProto(avatarSignedEvent).event
+            assertEquals(ContentType.AVATAR.value, avatarEvent.contentType)
+            assertNotNull(avatarEvent.lwwElement)
+
+            val imageBundle = Protocol.ImageBundle.parseFrom(avatarEvent.lwwElement!!.value)
+            assertEquals(1, imageBundle.imageManifestsList.size)
+
+            val imageManifest = imageBundle.imageManifestsList.first()
+            assertEquals("image/jpeg", imageManifest.mime)
+            assertEquals(2560, imageManifest.width)
+            assertEquals(424, imageManifest.height)
+            assertEquals(imageBytes.size.toLong(), imageManifest.byteCount)
+            assertEquals(processHandle.processSecret.process.toProto(), imageManifest.process)
+
+            val blobProcessRangesToGet = Protocol.RangesForProcess.newBuilder()
+                .addAllRanges(imageManifest.sectionsList)
+                .setProcess(processHandle.processSecret.process.toProto())
+                .build()
+            val blobEvents = ApiMethods.getEvents(TestConstants.SERVER, processHandle.system.toProto(), Protocol.RangesForSystem.newBuilder()
+                .addRangesForProcesses(blobProcessRangesToGet).build()).eventsList.map { SignedEvent.fromProto(it) }
+
+            val totalArray = ByteArray(imageManifest.byteCount.toInt())
+            var offset = 0
+
+            for (section in imageManifest.sectionsList) {
+                val sectionEvents = blobEvents.filter { it.event.logicalClock >= section.low && it.event.logicalClock <= section.high }.sortedBy { it.event.logicalClock }
+                sectionEvents.forEach {
+                    assertEquals(ContentType.BLOB_SECTION.value, it.event.contentType)
+                    val blobSection = Protocol.BlobSection.parseFrom(it.event.content)
+                    val size = blobSection.content.size()
+                    blobSection.content.copyTo(totalArray, offset)
+                    offset += size
+                }
+            }
+
+            assertEquals(imageManifest.byteCount.toInt(), offset)
+            assertArrayEquals(imageBytes, totalArray)
+        }
     }
 
     @Test
diff --git a/app/src/main/java/com/futo/polycentric/core/Models.kt b/app/src/main/java/com/futo/polycentric/core/Models.kt
index 58c96ea782ce84f869385634ccc3c96026234809..90aec854906336e4679467d336184a312f634e0e 100644
--- a/app/src/main/java/com/futo/polycentric/core/Models.kt
+++ b/app/src/main/java/com/futo/polycentric/core/Models.kt
@@ -14,7 +14,6 @@ enum class ContentType(val value: Long) {
     FOLLOW(4),
     USERNAME(5),
     DESCRIPTION(6),
-    BLOB_META(7),
     BLOB_SECTION(8),
     AVATAR(9),
     SERVER(10),
@@ -162,6 +161,20 @@ data class Pointer(
     }
 }
 
+data class ImageData(val mimeType: String, val width: Int, val height: Int, val data: ByteArray) {
+    override fun equals(other: Any?): Boolean {
+        if (other is ImageData) {
+            return mimeType == other.mimeType && width == other.width && height == other.height && data.contentEquals(other.data)
+        }
+
+        return false
+    }
+
+    override fun hashCode(): Int {
+        return combineHashCodes(listOf(width, height, mimeType.hashCode(), data.contentHashCode()))
+    }
+}
+
 data class Opinion(val data: ByteArray) {
     companion object {
         fun makeOpinion(x: Int): Opinion {
diff --git a/app/src/main/java/com/futo/polycentric/core/ProcessHandle.kt b/app/src/main/java/com/futo/polycentric/core/ProcessHandle.kt
index 1d6fb69c0cfa77f2b8d701cfbc23a9ceb9aea016..f235a5895d7c98680777854ee9a247d58c7dcdb5 100644
--- a/app/src/main/java/com/futo/polycentric/core/ProcessHandle.kt
+++ b/app/src/main/java/com/futo/polycentric/core/ProcessHandle.kt
@@ -1,7 +1,10 @@
 package com.futo.polycentric.core
 
+import android.graphics.Bitmap
+import android.util.Log
 import com.google.protobuf.ByteString
 import userpackage.Protocol
+import java.lang.Integer.min
 import java.util.*
 
 class ProcessHandle constructor(
@@ -81,10 +84,10 @@ class ProcessHandle constructor(
         )
     }
 
-    fun setAvatar(avatar: Pointer): Pointer {
+    fun setAvatar(avatar: Protocol.ImageBundle): Pointer {
         return setCRDTItem(
             ContentType.AVATAR.value,
-            avatar.toProto().toByteArray(),
+            avatar.toByteArray(),
         )
     }
 
@@ -158,49 +161,53 @@ class ProcessHandle constructor(
         )
     }
 
-    fun publishBlob(mime: String, content: ByteArray): Pointer {
-        val meta = publish(
-            ContentType.BLOB_META.value,
-            Protocol.BlobMeta.newBuilder()
-                .setSectionCount(1L)
-                .setMime(mime)
-                .build().toByteArray(),
-            null,
-            null,
-            mutableListOf()
-        )
+    fun publishBlob(content: ByteArray): List<Range> {
+        val maxBytes = 1024 * 512
+        var i = 0
+        val ranges = mutableListOf<Range>()
 
-        publish(
-            ContentType.BLOB_SECTION.value,
-            Protocol.BlobSection.newBuilder()
-                .setMetaPointer(meta.logicalClock)
-                .setContent(ByteString.copyFrom(content))
-                .build().toByteArray(),
-            null,
-            null,
-            mutableListOf()
-        )
+        while (true) {
+            if (i >= content.size - 1) {
+                break;
+            }
 
-        return meta
-    }
+            val end = min(i + maxBytes, content.size - 1)
+            val bytesToUpload = content.sliceArray(IntRange(i, end))
+            val pointer = publish(
+                ContentType.BLOB_SECTION.value,
+                Protocol.BlobSection.newBuilder()
+                    .setContent(ByteString.copyFrom(bytesToUpload))
+                    .build().toByteArray(),
+                null,
+                null,
+                mutableListOf()
+            )
 
-    fun loadBlob(pointer: Pointer): Blob? {
-        val signedEvent: SignedEvent = Store.instance.getSignedEvent(pointer) ?: return null
-        val event = signedEvent.event
-        if (event.contentType != ContentType.BLOB_META.value) {
-            return null
+            Ranges.insert(ranges, pointer.logicalClock)
+            i = end + 1
         }
 
-        val meta = Protocol.BlobMeta.parseFrom(event.content)
+        return ranges
+    }
 
-        val nextSignedEvent: SignedEvent = Store.instance.getSignedEvent(pointer.system, pointer.process, pointer.logicalClock + 1L) ?: return null
-        val nextEvent = nextSignedEvent.event
-        if (nextEvent.contentType != ContentType.BLOB_SECTION.value) {
-            return null
+    fun publishAvatar(images: List<ImageData>): Pointer {
+        val imageBundleBuilder = Protocol.ImageBundle.newBuilder()
+
+        for (image in images) {
+            val imageRanges = publishBlob(image.data)
+            val imageManifest = Protocol.ImageManifest.newBuilder()
+                .setMime(image.mimeType)
+                .setWidth(image.width.toLong())
+                .setHeight(image.height.toLong())
+                .setByteCount(image.data.size.toLong())
+                .setProcess(processSecret.process.toProto())
+                .addAllSections(imageRanges.map { it.toProto() })
+                .build()
+
+            imageBundleBuilder.addImageManifests(imageManifest)
         }
 
-        val section = Protocol.BlobSection.parseFrom(nextEvent.content)
-        return Blob(meta.mime, section.content.toByteArray())
+        return setAvatar(imageBundleBuilder.build())
     }
 
     private fun publish(contentType: Long, content: ByteArray, lwwElementSet: LWWElementSet?, lwwElement: LWWElement?, references: MutableList<Protocol.Reference>): Pointer {
diff --git a/app/src/main/java/com/futo/polycentric/core/SystemState.kt b/app/src/main/java/com/futo/polycentric/core/SystemState.kt
index 129f1d3f2c841f8793fa9476cc3df468cbc3e85a..68d02c27f5f8572a95c052fcfb25035723ed4ff7 100644
--- a/app/src/main/java/com/futo/polycentric/core/SystemState.kt
+++ b/app/src/main/java/com/futo/polycentric/core/SystemState.kt
@@ -1,5 +1,6 @@
 package com.futo.polycentric.core
 
+import com.futo.polycentric.core.serializers.ImageBundleSerializer
 import kotlinx.serialization.Serializable
 import userpackage.Protocol
 
@@ -10,7 +11,7 @@ class SystemState(
     val username: String,
     val description: String,
     val store: String,
-    val avatar: Pointer?,
+    @Serializable(with = ImageBundleSerializer::class) val avatar: Protocol.ImageBundle?,
     val banner: Pointer?
 ) {
     override fun toString(): String {
@@ -29,7 +30,7 @@ class SystemState(
             var username = ""
             var description = ""
             var store = ""
-            var avatar: Pointer? = null
+            var avatar: Protocol.ImageBundle? = null
             var banner: Pointer? = null
             proto.crdtItems.forEach { item ->
                 when (item.contentType) {
@@ -43,7 +44,7 @@ class SystemState(
                         store = item.value.decodeToString()
                     }
                     ContentType.AVATAR.value -> {
-                        avatar = Pointer.fromProto(Protocol.Pointer.parseFrom(item.value))
+                        avatar = Protocol.ImageBundle.parseFrom(item.value)
                     }
                     ContentType.BANNER.value -> {
                         banner = Pointer.fromProto(Protocol.Pointer.parseFrom(item.value))
diff --git a/app/src/main/java/com/futo/polycentric/core/serializers/ImageBundleSerializer.kt b/app/src/main/java/com/futo/polycentric/core/serializers/ImageBundleSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6fcdc7c8c4b1d43e01874604a4f2faecc5ac9b28
--- /dev/null
+++ b/app/src/main/java/com/futo/polycentric/core/serializers/ImageBundleSerializer.kt
@@ -0,0 +1,23 @@
+package com.futo.polycentric.core.serializers
+
+import com.futo.polycentric.core.base64UrlToByteArray
+import com.futo.polycentric.core.toBase64Url
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import userpackage.Protocol
+
+class ImageBundleSerializer : KSerializer<Protocol.ImageBundle> {
+    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ImageBundle", PrimitiveKind.STRING)
+
+    override fun serialize(encoder: Encoder, value: Protocol.ImageBundle) {
+        encoder.encodeString(value.toByteArray().toBase64Url())
+    }
+    override fun deserialize(decoder: Decoder): Protocol.ImageBundle {
+        val value = decoder.decodeString();
+        return Protocol.ImageBundle.parseFrom(value.base64UrlToByteArray());
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/proto/com/futo/polycentric/protos/protocol.proto b/app/src/main/proto/com/futo/polycentric/protos/protocol.proto
index b858ad6ddce5326cb80e3c01df585be4f4a1d866..214825d7e127b86c4ac1e268f9d927048292c500 100644
--- a/app/src/main/proto/com/futo/polycentric/protos/protocol.proto
+++ b/app/src/main/proto/com/futo/polycentric/protos/protocol.proto
@@ -54,8 +54,20 @@ message BlobMeta {
 }
 
 message BlobSection {
-    uint64 meta_pointer = 1;
-    bytes content = 2;
+    bytes content = 1;
+}
+
+message ImageManifest {
+             string  mime       = 1;
+             uint64  width      = 2;
+             uint64  height     = 3;
+             uint64  byte_count = 4;
+             Process process    = 5; 
+    repeated Range   sections   = 6;
+}
+
+message ImageBundle {
+    repeated ImageManifest image_manifests = 1;
 }
 
 message Event {
diff --git a/app/src/main/res/drawable/avatar.jpg b/app/src/main/res/drawable/avatar.jpg
deleted file mode 100644
index 6586937a09d2369a7b12676c118e232cfcfc8e4c..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/drawable/avatar.jpg and /dev/null differ
diff --git a/app/src/main/res/drawable/banner.jpg b/app/src/main/res/drawable/image.jpg
similarity index 100%
rename from app/src/main/res/drawable/banner.jpg
rename to app/src/main/res/drawable/image.jpg