diff --git a/api/src/main/kotlin/de/chaosdorf/mete/model/MeteApi.kt b/api/src/main/kotlin/de/chaosdorf/mete/model/MeteApi.kt
index 8313d89c82dceb7a519b58deeb4bd193be14c109..ae0cee0199d3b3edd57a6a77c7ec6d1f6f6c0dd6 100644
--- a/api/src/main/kotlin/de/chaosdorf/mete/model/MeteApi.kt
+++ b/api/src/main/kotlin/de/chaosdorf/mete/model/MeteApi.kt
@@ -24,11 +24,19 @@
 
 package de.chaosdorf.mete.model
 
+import kotlinx.datetime.Instant
+import kotlinx.datetime.LocalDate
+import retrofit2.http.Query
 import java.math.BigDecimal
 
 interface MeteApi {
   suspend fun getManifest(): PwaManifest?
-  suspend fun listTransactions(user: UserId? = null): TransactionSummaryModel
+  suspend fun listTransactions(
+    user: UserId,
+    startYear: Int, startMonth: Int, startDay: Int,
+    endYear: Int, endMonth: Int, endDay: Int,
+  ): TransactionSummaryModel
+
   suspend fun listBarcodes(): List<BarcodeModel>
   suspend fun getBarcode(id: BarcodeId): BarcodeModel?
   suspend fun listDrinks(): List<DrinkModel>
diff --git a/api/src/main/kotlin/de/chaosdorf/mete/v1/MeteApiV1.kt b/api/src/main/kotlin/de/chaosdorf/mete/v1/MeteApiV1.kt
index 7c25ea6828c4a9ef0e2fd1a52192c315cca613ea..cc9f921706cbdf72c4c540809d589ff011430977 100644
--- a/api/src/main/kotlin/de/chaosdorf/mete/v1/MeteApiV1.kt
+++ b/api/src/main/kotlin/de/chaosdorf/mete/v1/MeteApiV1.kt
@@ -28,12 +28,16 @@ import de.chaosdorf.mete.model.BarcodeId
 import de.chaosdorf.mete.model.DrinkId
 import de.chaosdorf.mete.model.MeteApi
 import de.chaosdorf.mete.model.PwaManifest
+import de.chaosdorf.mete.model.TransactionSummaryModel
 import de.chaosdorf.mete.model.UserId
+import kotlinx.datetime.Instant
+import kotlinx.datetime.LocalDate
 import retrofit2.Response
 import retrofit2.http.GET
 import retrofit2.http.Path
 import retrofit2.http.Query
 import java.math.BigDecimal
+import java.time.Year
 
 internal interface MeteApiV1 : MeteApi {
   @GET("manifest.json")
@@ -41,7 +45,13 @@ internal interface MeteApiV1 : MeteApi {
 
   @GET("api/v1/audits.json")
   override suspend fun listTransactions(
-    @Query("user") user: UserId?
+    @Query("user") user: UserId,
+    @Query("start_date[year]") startYear: Int,
+    @Query("start_date[month]") startMonth: Int,
+    @Query("start_date[day]") startDay: Int,
+    @Query("end_date[year]") endYear: Int,
+    @Query("end_date[month]") endMonth: Int,
+    @Query("end_date[day]") endDay: Int,
   ): TransactionSummaryModelV1
 
   @GET("api/v1/barcodes.json")
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/DrinkSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/DrinkSyncHandler.kt
index d9d2a162ff3c962aa0eac128b059985af0e1460d..b60189cf1f7d50e8938b861dbd754666e2d84a71 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/DrinkSyncHandler.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/DrinkSyncHandler.kt
@@ -32,18 +32,19 @@ import de.chaosdorf.meteroid.model.Drink
 import de.chaosdorf.meteroid.model.DrinkRepository
 import de.chaosdorf.meteroid.model.Server
 import de.chaosdorf.meteroid.model.ServerId
+import de.chaosdorf.meteroid.sync.base.BaseSyncHandler
 import javax.inject.Inject
 
 class DrinkSyncHandler @Inject constructor(
   private val factory: MeteApiFactory,
   private val db: MeteroidDatabase,
   private val repository: DrinkRepository
-) : SyncHandler<Server, Drink, DrinkSyncHandler.Key>() {
+) : BaseSyncHandler<Server, Drink, DrinkSyncHandler.Key>() {
   data class Key(
     val server: ServerId, val drink: DrinkId
   )
 
-  override suspend fun withTransaction(block: suspend () -> Unit) =
+  override suspend fun <T>withTransaction(block: suspend () -> T): T =
     db.withTransaction(block)
 
   override suspend fun store(entry: Drink) =
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncManager.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncManager.kt
index c217089b28f93c7bce8eea31d3a64bd1963f5f2f..5e9a4e3bc5e9580836764ed5713408abdf735a4c 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncManager.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncManager.kt
@@ -54,12 +54,16 @@ class SyncManager @Inject constructor(
     }
   }
 
-  suspend fun sync(server: Server, user: User?) {
+  suspend fun sync(server: Server, user: User?, incremental: Boolean) {
     try {
       userSyncHandler.sync(server)
       drinkSyncHandler.sync(server)
       if (user != null) {
-        transactionSyncHandler.sync(Pair(server, user.userId))
+        if (incremental) {
+          transactionSyncHandler.syncIncremental(Pair(server, user.userId))
+        } else {
+          transactionSyncHandler.sync(Pair(server, user.userId))
+        }
       }
     } catch (e: Exception) {
       Log.e(
@@ -75,7 +79,7 @@ class SyncManager @Inject constructor(
       val api = factory.newInstance(account.server.url)
       try {
         api.purchase(user.userId, drink.drinkId)
-        sync(account.server, user)
+        sync(account.server, user, incremental = true)
       } catch (e: Exception) {
         Log.e(
           "Sync",
@@ -91,7 +95,7 @@ class SyncManager @Inject constructor(
       try {
         val api = factory.newInstance(account.server.url)
         api.deposit(user.userId, amount)
-        sync(account.server, user)
+        sync(account.server, user, incremental = true)
       } catch (e: Exception) {
         Log.e(
           "Sync",
@@ -107,7 +111,7 @@ class SyncManager @Inject constructor(
       val api = factory.newInstance(account.server.url)
       try {
         api.withdraw(user.userId, amount)
-        sync(account.server, user)
+        sync(account.server, user, incremental = true)
       } catch (e: Exception) {
         Log.e(
           "Sync",
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/PurchaseSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/TransactionSyncHandler.kt
similarity index 61%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/sync/PurchaseSyncHandler.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/sync/TransactionSyncHandler.kt
index 071c20c4881f368c6397e03c370e69cc1a6f6168..e9797e920269ed6075625fa00167ffbc24fb3878 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/PurchaseSyncHandler.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/TransactionSyncHandler.kt
@@ -33,18 +33,25 @@ import de.chaosdorf.meteroid.model.Server
 import de.chaosdorf.meteroid.model.ServerId
 import de.chaosdorf.meteroid.model.Transaction
 import de.chaosdorf.meteroid.model.TransactionRepository
+import de.chaosdorf.meteroid.sync.base.BaseIncrementalSyncHandler
+import kotlinx.datetime.Clock
+import kotlinx.datetime.DateTimeUnit
+import kotlinx.datetime.LocalDate
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.plus
+import kotlinx.datetime.toLocalDateTime
 import javax.inject.Inject
 
 class TransactionSyncHandler @Inject constructor(
   private val factory: MeteApiFactory,
   private val db: MeteroidDatabase,
   private val repository: TransactionRepository
-) : SyncHandler<Pair<Server, UserId>, Transaction, TransactionSyncHandler.Key>() {
+) : BaseIncrementalSyncHandler<Pair<Server, UserId>, Transaction, TransactionSyncHandler.Key>() {
   data class Key(
     val server: ServerId, val transaction: TransactionId
   )
 
-  override suspend fun withTransaction(block: suspend () -> Unit) =
+  override suspend fun <T> withTransaction(block: suspend () -> T): T =
     db.withTransaction(block)
 
   override suspend fun store(entry: Transaction) =
@@ -61,7 +68,36 @@ class TransactionSyncHandler @Inject constructor(
   override suspend fun loadCurrent(context: Pair<Server, UserId>): List<Transaction> {
     val (server, userId) = context
     val api = factory.newInstance(server.url)
-    val loadedEntries = api.listTransactions(user = userId).entries
+    val startDate = LocalDate(1970, 1, 1)
+    val endDate = Clock.System.now().toLocalDateTime(TimeZone.UTC).date
+      .plus(1, DateTimeUnit.DAY)
+
+    val loadedEntries = api.listTransactions(
+      userId,
+      startDate.year, startDate.month.value, startDate.dayOfMonth,
+      endDate.year, endDate.month.value, endDate.dayOfMonth,
+    ).entries
+    return loadedEntries.map { Transaction.fromModel(server, userId, it) }
+  }
+
+  override suspend fun loadLatestEntry(context: Pair<Server, UserId>): Transaction? =
+    repository.getLatest(context.first.serverId, context.second)
+
+  override suspend fun loadAdded(
+    context: Pair<Server, UserId>,
+    latest: Transaction
+  ): List<Transaction> {
+    val (server, userId) = context
+    val api = factory.newInstance(server.url)
+    val startDate = latest.timestamp.toLocalDateTime(TimeZone.UTC).date
+    val endDate = Clock.System.now().toLocalDateTime(TimeZone.UTC).date
+      .plus(1, DateTimeUnit.DAY)
+
+    val loadedEntries = api.listTransactions(
+      userId,
+      startDate.year, startDate.month.value, startDate.dayOfMonth,
+      endDate.year, endDate.month.value, endDate.dayOfMonth,
+    ).entries
     return loadedEntries.map { Transaction.fromModel(server, userId, it) }
   }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/UserSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/UserSyncHandler.kt
index 179a5dbd20dd46c856f3ad6e99447bc1ceffad4b..0f50531c4e5c95f2c30af6dca0e560e53d7632dd 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/UserSyncHandler.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/UserSyncHandler.kt
@@ -32,18 +32,19 @@ import de.chaosdorf.meteroid.model.Server
 import de.chaosdorf.meteroid.model.ServerId
 import de.chaosdorf.meteroid.model.User
 import de.chaosdorf.meteroid.model.UserRepository
+import de.chaosdorf.meteroid.sync.base.BaseSyncHandler
 import javax.inject.Inject
 
 class UserSyncHandler @Inject constructor(
   private val factory: MeteApiFactory,
   private val db: MeteroidDatabase,
   private val repository: UserRepository
-) : SyncHandler<Server, User, UserSyncHandler.Key>() {
+) : BaseSyncHandler<Server, User, UserSyncHandler.Key>() {
   data class Key(
     val server: ServerId, val user: UserId
   )
 
-  override suspend fun withTransaction(block: suspend () -> Unit) =
+  override suspend fun <T>withTransaction(block: suspend () -> T): T =
     db.withTransaction(block)
 
   override suspend fun store(entry: User) =
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseIncrementalSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseIncrementalSyncHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..16ae412fcf7d5e88b5b8c4c2bc0f7e9b2df518d0
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseIncrementalSyncHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN 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 de.chaosdorf.meteroid.sync.base
+
+import android.util.Log
+
+abstract class BaseIncrementalSyncHandler<Context, Entry, Key> :
+  BaseSyncHandler<Context, Entry, Key>(),
+  IncrementalSyncHandler<Context> {
+  abstract suspend fun loadLatestEntry(context: Context): Entry?
+  abstract suspend fun loadAdded(context: Context, latest: Entry): List<Entry>
+
+  override suspend fun syncIncremental(context: Context) {
+    if (syncState.compareAndSet(State.Idle, State.Loading) ||
+      syncState.compareAndSet(State.Error(), State.Loading)) {
+      Log.w(this::class.simpleName, "Started incremental sync")
+      val success = try {
+        val result = withTransaction {
+          val latestEntry = loadLatestEntry(context)
+          if (latestEntry != null) {
+            val addedEntries = loadAdded(context, latestEntry)
+            for (loadedEntry in addedEntries) {
+              store(loadedEntry)
+            }
+            true
+          } else {
+            false
+          }
+        }
+        syncState.value = State.Idle
+        Log.w(this::class.simpleName, "Finished incremental sync")
+        result
+      } catch (e: Exception) {
+        Log.e(this::class.simpleName, "Error while syncing data", e)
+        syncState.value = State.Error("Error while syncing data: $e")
+        false
+      }
+      // If we can't do an incremental sync, do a full sync
+      if (!success) {
+        sync(context)
+      }
+    } else {
+      Log.w(this::class.simpleName, "Already syncing, disregarding sync request")
+    }
+  }
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseSyncHandler.kt
similarity index 83%
rename from app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncHandler.kt
rename to app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseSyncHandler.kt
index b7a61994adc21e850d8dc80cc570fe74de637ba6..00d33c17f74ae242e6eaa66755c147f1bbdee3c6 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncHandler.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseSyncHandler.kt
@@ -22,13 +22,13 @@
  * THE SOFTWARE.
  */
 
-package de.chaosdorf.meteroid.sync
+package de.chaosdorf.meteroid.sync.base
 
 import android.util.Log
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
-abstract class SyncHandler<Context, Entry, Key> {
+abstract class BaseSyncHandler<Context, Entry, Key>: SyncHandler<Context> {
   sealed class State {
     data object Idle : State()
     data object Loading : State()
@@ -45,7 +45,7 @@ abstract class SyncHandler<Context, Entry, Key> {
     }
   }
 
-  abstract suspend fun withTransaction(block: suspend () -> Unit)
+  abstract suspend fun <T>withTransaction(block: suspend () -> T): T
   abstract suspend fun loadCurrent(context: Context): List<Entry>
 
   abstract suspend fun loadStored(context: Context): List<Entry>
@@ -55,15 +55,12 @@ abstract class SyncHandler<Context, Entry, Key> {
   abstract suspend fun delete(key: Key)
   abstract suspend fun store(entry: Entry)
 
-  private val _state = MutableStateFlow<State>(State.Idle)
-  val state: StateFlow<State> = _state
+  protected val syncState = MutableStateFlow<State>(State.Idle)
+  val state: StateFlow<State> = syncState
 
-  suspend fun sync(context: Context) {
-    if (_state.compareAndSet(State.Idle, State.Loading) || _state.compareAndSet(
-        State.Error(),
-        State.Loading
-      )
-    ) {
+  override suspend fun sync(context: Context) {
+    if (syncState.compareAndSet(State.Idle, State.Loading) ||
+      syncState.compareAndSet(State.Error(), State.Loading)) {
       Log.w(this::class.simpleName, "Started sync")
       try {
         val loadedEntries = loadCurrent(context)
@@ -80,11 +77,11 @@ abstract class SyncHandler<Context, Entry, Key> {
             store(loadedEntry)
           }
         }
-        _state.value = State.Idle
+        syncState.value = State.Idle
         Log.w(this::class.simpleName, "Finished sync")
       } catch (e: Exception) {
         Log.e(this::class.simpleName, "Error while syncing data", e)
-        _state.value = State.Error("Error while syncing data: $e")
+        syncState.value = State.Error("Error while syncing data: $e")
       }
     } else {
       Log.w(this::class.simpleName, "Already syncing, disregarding sync request")
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/IncrementalSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/IncrementalSyncHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c07f0af190dd16b862cb5e799232dee6fff0017a
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/IncrementalSyncHandler.kt
@@ -0,0 +1,29 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN 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 de.chaosdorf.meteroid.sync.base
+
+interface IncrementalSyncHandler<Context> {
+  suspend fun syncIncremental(context: Context)
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/SyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/SyncHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7046454852242e021d13a64684d63b46d18e4686
--- /dev/null
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/SyncHandler.kt
@@ -0,0 +1,29 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013-2023 Chaosdorf e.V.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN 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 de.chaosdorf.meteroid.sync.base
+
+interface SyncHandler<Context> {
+  suspend fun sync(context: Context)
+}
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
index 853744c36968967346db957c350110449dc7b3c9..8e0cf09af985c05ee25db0f6a43f05870e838ca7 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
@@ -66,7 +66,7 @@ class AppViewModel @Inject constructor(
   init {
     accountProvider.account.distinctUntilChanged().onEach { account ->
       account?.let { (server, maybeUser) ->
-        syncManager.sync(server, maybeUser)
+        syncManager.sync(server, maybeUser, incremental = true)
       }
     }.launchIn(viewModelScope)
   }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/PriceBadge.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/PriceBadge.kt
index 7ded488064f050a2dae6b47c24ceb08ba5ee660d..2231c375270f284fcd144aa8f4cf47e4e33b494c 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/PriceBadge.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/PriceBadge.kt
@@ -41,10 +41,10 @@ fun PriceBadge(
   price: BigDecimal,
   modifier: Modifier = Modifier,
   containerColor: Color =
-    if (price > BigDecimal.ZERO) MaterialTheme.colorScheme.primary
+    if (price >= BigDecimal.ZERO) MaterialTheme.colorScheme.primary
     else MaterialTheme.colorScheme.error,
   textColor: Color =
-    if (price > BigDecimal.ZERO) MaterialTheme.colorScheme.onPrimary
+    if (price >= BigDecimal.ZERO) MaterialTheme.colorScheme.onPrimary
     else MaterialTheme.colorScheme.onError,
   textStyle: TextStyle = MaterialTheme.typography.labelLarge
 ) {
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListViewModel.kt
index e56a782ba0ca524025f9aadd6a574900592ee836..52c751eb9fed226b7f244be5532bb4552f5aaa5d 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkListViewModel.kt
@@ -98,6 +98,14 @@ class DrinkListViewModel @Inject constructor(
     }
   }
 
+  fun sync() {
+    account.value?.let { (server, user) ->
+      viewModelScope.launch {
+        syncManager.sync(server, user, incremental = true)
+      }
+    }
+  }
+
   suspend fun checkOffline(server: Server?): Boolean =
     if (server == null) true
     else syncManager.checkOffline(server)
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkTile.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkTile.kt
index ab9b07c052f48a4d853fb4d05e6d68e4def8b967..f079ffd6976cb23c8b53f70b7c4f2894d36da180 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkTile.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/drinks/DrinkTile.kt
@@ -57,6 +57,7 @@ import coil.compose.rememberAsyncImagePainter
 import de.chaosdorf.meteroid.model.Drink
 import de.chaosdorf.meteroid.sample.SampleDrinkProvider
 import de.chaosdorf.meteroid.ui.PriceBadge
+import java.math.BigDecimal
 
 @Preview(widthDp = 120, showBackground = true)
 @Composable
@@ -109,8 +110,13 @@ fun DrinkTile(
     )
     Spacer(Modifier.height(4.dp))
     Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
+      val unitPrice =
+        if (item.volume <= BigDecimal.ZERO) null
+        else item.price / item.volume
+
       Text(
-        String.format("%.02fl · %.02f€/l", item.volume, item.price / item.volume),
+        if (unitPrice == null) String.format("%.02fl", item.volume)
+        else String.format("%.02fl · %.02f€/l", item.volume, item.price / item.volume),
         modifier = Modifier
           .fillMaxWidth()
           .padding(horizontal = 8.dp),
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt
index 5cc575b87cb1c33b52c0d42a6e544d277cdf2cfb..f0b6bc200d1ea6e201c7b74fe0c9ff3a88ff1798 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt
@@ -36,7 +36,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.PushPin
 import androidx.compose.material.icons.outlined.PushPin
-import androidx.compose.material.icons.twotone.PushPin
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
@@ -48,19 +47,19 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.navigation.NavOptions
 import coil.compose.AsyncImage
 import de.chaosdorf.meteroid.model.AccountInfo
 import de.chaosdorf.meteroid.ui.PriceBadge
 import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.internal.toCanonicalHost
 
 @Composable
 fun MeteroidTopBar(
   account: AccountInfo?,
   onNavigate: (String, NavOptions) -> Unit,
-  onTogglePin: () -> Unit,
+  onTogglePin: () -> Unit
 ) {
   Surface(
     modifier = Modifier.padding(8.dp),
@@ -83,35 +82,44 @@ fun MeteroidTopBar(
           .background(MaterialTheme.colorScheme.tertiary)
       )
       Spacer(Modifier.width(16.dp))
-      Column(modifier = Modifier.align(Alignment.CenterVertically)) {
+      Column(
+        modifier = Modifier
+          .align(Alignment.CenterVertically)
+          .weight(1.0f, fill = true)
+      ) {
         if (account != null) {
           if (account.user != null) {
             Text(
               account.user!!.name,
-              fontWeight = FontWeight.SemiBold
+              fontWeight = FontWeight.SemiBold,
+              overflow = TextOverflow.Ellipsis,
+              softWrap = false
             )
             Text(
               account.server.url.toHttpUrl().host,
               color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.67f),
-              fontWeight = FontWeight.Medium
+              fontWeight = FontWeight.Medium,
+              overflow = TextOverflow.Ellipsis,
+              softWrap = false
             )
           } else {
             Text(
               account.server.url.toHttpUrl().host,
-              fontWeight = FontWeight.SemiBold
+              fontWeight = FontWeight.SemiBold,
+              overflow = TextOverflow.Ellipsis,
+              softWrap = false
             )
           }
         } else {
           Text(
             "Meteroid",
-            fontWeight = FontWeight.SemiBold
+            fontWeight = FontWeight.SemiBold,
+            overflow = TextOverflow.Ellipsis,
+            softWrap = false
           )
         }
       }
-      Spacer(
-        Modifier
-          .weight(1.0f)
-          .width(16.dp))
+      Spacer(Modifier.width(16.dp))
       IconButton(onClick = onTogglePin) {
         Icon(
           if (account?.pinned == true) Icons.Filled.PushPin
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/transactions/PurchaseViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/transactions/PurchaseViewModel.kt
index 15e41aa85b282727d8332841ee72e565b6f65096..7242558d27e2738ca796588b9ab8eb34540a1ea9 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/transactions/PurchaseViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/transactions/PurchaseViewModel.kt
@@ -24,6 +24,7 @@
 
 package de.chaosdorf.meteroid.ui.transactions
 
+import android.util.Log
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import dagger.hilt.android.lifecycle.HiltViewModel
@@ -32,9 +33,7 @@ import de.chaosdorf.meteroid.model.DrinkRepository
 import de.chaosdorf.meteroid.model.Server
 import de.chaosdorf.meteroid.model.TransactionRepository
 import de.chaosdorf.meteroid.sync.AccountProvider
-import de.chaosdorf.meteroid.sync.SyncHandler
 import de.chaosdorf.meteroid.sync.SyncManager
-import de.chaosdorf.meteroid.sync.TransactionSyncHandler
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -75,8 +74,10 @@ class TransactionViewModel @Inject constructor(
         }
       } ?: flowOf(emptyList())
     }.mapLatest { list ->
-      list.mergeAdjecentDeposits()
-        .filter { it.drink != null || it.transaction.difference != BigDecimal.ZERO }
+      list.mergeAdjecentDeposits().filter {
+        it.drink != null ||
+          it.transaction.difference.abs() >= 0.01.toBigDecimal()
+      }
     }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
 
   fun togglePin() {
@@ -89,6 +90,14 @@ class TransactionViewModel @Inject constructor(
     }
   }
 
+  fun sync() {
+    account.value?.let { (server, user) ->
+      viewModelScope.launch {
+        syncManager.sync(server, user, incremental = true)
+      }
+    }
+  }
+
   suspend fun checkOffline(server: Server?): Boolean =
     if (server == null) true
     else syncManager.checkOffline(server)
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedSlide.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedSlide.kt
index 4899c2ecda023e56684a4cdaa1d58fe2f90f8953..ec262adfa75678e180ef581d1bdd2cade205ed9f 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedSlide.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedSlide.kt
@@ -95,7 +95,7 @@ sealed class WrappedSlide {
             dosage,
             Animal.values()
               .sortedBy(Animal::lethalDosage)
-              .firstOrNull { it.lethalDosage < dosage }
+              .lastOrNull { it.lethalDosage < dosage }
           )
         }
     }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedViewModel.kt
index 722226a4dfc8c7c0b7f2bfde6c43e20b096cee1c..d50c28a621a80cc311df6a607dc88cc5af72aefe 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedViewModel.kt
@@ -32,6 +32,7 @@ import de.chaosdorf.meteroid.model.AccountInfo
 import de.chaosdorf.meteroid.model.Drink
 import de.chaosdorf.meteroid.model.DrinkRepository
 import de.chaosdorf.meteroid.model.Server
+import de.chaosdorf.meteroid.model.Transaction
 import de.chaosdorf.meteroid.model.TransactionRepository
 import de.chaosdorf.meteroid.sync.AccountProvider
 import de.chaosdorf.meteroid.sync.SyncManager
@@ -60,6 +61,16 @@ class WrappedViewModel @Inject constructor(
   val account: StateFlow<AccountInfo?> = accountProvider.account
     .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
 
+  private fun List<Transaction>.filterAudits(year: Int): List<Transaction> {
+    val yearBegin = LocalDateTime(year, Month.JANUARY, 1, 0, 0, 0)
+      .toInstant(TimeZone.UTC)
+    val yearEnd = LocalDateTime(year, Month.DECEMBER, 31, 23, 59, 59)
+      .toInstant(TimeZone.UTC)
+    return this.filter {
+      it.timestamp in yearBegin..yearEnd
+    }
+  }
+
   val slides: StateFlow<List<WrappedSlide>> = accountProvider.account
     .flatMapLatest { account ->
       account?.let { (server, maybeUser) ->
@@ -74,28 +85,9 @@ class WrappedViewModel @Inject constructor(
               WrappedSlide.Caffeine,
               WrappedSlide.MostActive
             )
-            val timeZone = TimeZone.currentSystemDefault()
-            val now = Clock.System.now().toLocalDateTime(timeZone)
-            val yearBegin = LocalDateTime(
-              year = now.year,
-              month = Month.JANUARY,
-              dayOfMonth =  1,
-              hour = 0,
-              minute = 0,
-              second = 0
-            ).toInstant(timeZone)
-            val yearEnd = LocalDateTime(
-              year = now.year + 1,
-              month = Month.JANUARY,
-              dayOfMonth =  1,
-              hour = 0,
-              minute = 0,
-              second = 0
-            ).toInstant(timeZone)
-            val thisYear = transactions.filter {
-              it.timestamp in yearBegin..yearEnd
-            }
-            factories.mapNotNull { it.create(thisYear, drinkMap) }
+            val now = Clock.System.now().toLocalDateTime(TimeZone.UTC)
+            val content = transactions.filterAudits(now.year)
+            factories.mapNotNull { it.create(content, drinkMap) }
           }
         }
       } ?: flowOf(emptyList())
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7d17d98c36153e3781bde380b0437745f8e56180..25ea8c9b8d705f693154d2c4971d74e8f084a3f1 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,13 +1,13 @@
 [versions]
-androidGradlePlugin = "8.1.3"
-androidx-activity = "1.8.0"
+androidGradlePlugin = "8.1.4"
+androidx-activity = "1.8.1"
 androidx-appcompat = "1.6.1"
 androidx-compose-bom = "2023.10.01"
 androidx-compose-compiler = "1.5.4"
 androidx-compose-material = "1.5.0-alpha04"
-androidx-compose-material3 = "1.2.0-alpha10"
-androidx-compose-runtimetracing = "1.0.0-alpha04"
-androidx-compose-tooling = "1.6.0-alpha08"
+androidx-compose-material3 = "1.2.0-alpha11"
+androidx-compose-runtimetracing = "1.0.0-alpha05"
+androidx-compose-tooling = "1.6.0-beta01"
 androidx-datastore = "1.0.0"
 androidx-hilt = "1.1.0"
 androidx-navigation = "2.7.5"
diff --git a/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Transaction.kt b/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Transaction.kt
index ac662512f36498378124d296e93df268c076d18c..f3206899c63ef84c68c0f5a77dcafb8eb80aa5f2 100644
--- a/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Transaction.kt
+++ b/persistence/src/main/kotlin/de/chaosdorf/meteroid/model/Transaction.kt
@@ -86,6 +86,8 @@ interface TransactionRepository {
 
   @Query("SELECT * FROM `Transaction` WHERE serverId = :serverId AND userId = :userId ORDER BY timestamp DESC")
   fun getAllFlow(serverId: ServerId, userId: UserId): Flow<List<Transaction>>
+  @Query("SELECT * FROM `Transaction` WHERE serverId = :serverId AND userId = :userId ORDER BY timestamp DESC LIMIT 1")
+  suspend fun getLatest(serverId: ServerId, userId: UserId): Transaction?
 
   @Insert(onConflict = OnConflictStrategy.REPLACE)
   suspend fun save(transaction: Transaction)