fix: build a release apk, by dropping the app's copies of what the library already ships
`:composeApp:assembleRelease` dies in `mergeDexRelease`:
Type com.machankura.compose.ui.composable.widgets.nfc.ComposableSingletons$HceMonitorKt
is defined multiple times:
composeApp/build/intermediates/project_dex_archive/release/dexBuilderRelease/out/...
lightning-kmp-app/library/build/.transforms/.../bundleLibRuntimeToDirAndroidMain_dex/...
Both paths are ours. composeApp compiles a class, lightning-kmp-app's `:library`
compiles the same fully-qualified name, and D8 will not merge the two into one
apk. Debug never objected, because it packages the per-project dex archives as
they stand and only the release merge walks the whole set looking for
collisions -- so this has been true for a while and surfaced the first time
anyone asked for a release. There were two such copies, and fixing the first
only uncovered the second.
**The NFC widgets were renamed by directory, not by package.** 9abdf42
("Refactor torch to mantra", 2026-07-15) moved three files under
`composeApp/src/androidMain/kotlin/press/mantra/compose/ui/composable/widgets/nfc/`
and left their `package com.machankura.compose.ui.composable.widgets.nfc` line
alone. The library holds the same three at
`fr/acinq/phoenix/compose/ui/widgets/nfc/`, also declaring `com.machankura...`.
So three directories say three different things and the package -- the only one
of them D8 reads -- says one. `NfcState.kt` is byte-identical across the two;
`HceMonitor.kt` and `NfcReaderMonitor.kt` differ in a single import line,
`press.mantra` against `fr.acinq.phoenix` for `ModalBottomSheet`.
Deleted the app's three rather than renaming their package to `press.mantra`,
which was the other way out and is the worse one. `NfcStateRepository` is an
`object`. The library's own `HceService` and `NfcReaderCallback` import it under
the `com.machankura` name, and `MainActivity:50-52` reaches for it fully
qualified. Renaming the app's copy would have compiled, dexed and run -- with
two singletons: one that `MainActivity` sets back to `Inactive` on a new intent,
and a different one the HCE service is collecting. Nothing would warn, because
by then the names genuinely differ; the tag emulation would just not stop.
Deleting leaves one object, and `MainActivity`'s existing fully-qualified
references land on it untouched. The two composables went along with it as dead
weight -- nothing outside their own files names `HceMonitor` or
`NfcReaderMonitor` anywhere in composeApp.
**The sqldelight databases were a second copy of the same kind.** With the NFC
clash gone, `mergeDexRelease` came back with
`fr.acinq.phoenix.db.sqldelight.AppDatabase$Companion`, out of the same pair of
directories. composeApp's build.gradle.kts declared `ChannelsDatabase`,
`PaymentsDatabase` and `AppDatabase` at packageName
`fr.acinq.phoenix.db.sqldelight` from `.sq` sources under
`src/commonMain/sqldelight`; the library's build.gradle.kts declares the same
three names, the same package and the same layout, over a tree `diff -rq` reports
as identical file for file. Both plugins ran, and both generated the same
classes.
Removed the plugin alias and the whole `sqldelight { }` block from composeApp
and deleted its 32 `.sq`/`.sqm` files, rather than moving the app's generated
package to `press.mantra`. Nothing under `press.mantra` imports
`fr.acinq.phoenix.db` -- grep finds no file -- and nothing there touches
`app.cash.sqldelight` either, against 18 files in the library that do. The app
was generating a database layer it has never opened. Renaming would have kept
generating it, and kept a second identical schema in the tree for someone to
edit and then wonder why nothing moved. A comment sits where the plugin alias
was, since the absence is the load-bearing part and an alias is a one-line thing
to add back by reflex.
**The sqldelight driver dependencies stay.** `-android-driver`,
`-sqlite-driver`, `-native-driver`, `-runtime` and `-coroutines-extensions` are
still declared in composeApp. They are runtime drivers rather than code
generation, and are very likely redundant -- the library declares the same five
as `implementation`, which still carries them onto the runtime classpath. But
they are not what D8 objected to, and a missing driver fails when the app opens
a database on one platform, not when it builds, so retiring them wants more
evidence than a green build and is its own change.
**What this does not fix.** Anything else copied into both trees fails exactly
this way, and only on release. A scan of the two source sets for colliding
fully-qualified top-level types is clean now -- the three NFC files were the
only ones -- but that scan reads `.kt`, and the sqldelight collision had no
`.kt` to find. Generated code is where the next one hides: Room and
compose-resources generate into composeApp, and the library generates
compose-resources too, which is why settings.gradle.kts keeps the submodule's
project named `:library` and renames only the coordinate.
**Verified.** `:composeApp:assembleRelease` produces the unsigned 36MB apk;
`:composeApp:assembleDebug` builds. 906 tests pass, 576 jvm over 69 classes and
330 android over 41 classes, unchanged from before the change since it adds
none. The apk was not installed, so that NFC still works on a device is read
from the code -- one `NfcStateRepository`, the one both sides were already
naming -- rather than watched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,11 @@ plugins {
|
||||
alias(libs.plugins.composeHotReload)
|
||||
alias(libs.plugins.kotlinPluginSerialization)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.sqldelight)
|
||||
// No sqldelight plugin here on purpose. The fr.acinq.phoenix.db.sqldelight databases this
|
||||
// module used to generate are the same three lightning-kmp-app's :library generates from an
|
||||
// identical copy of the .sq sources, and nothing under press.mantra touches them. Generating
|
||||
// them on both sides put every generated class in the apk twice, which debug tolerates and
|
||||
// mergeDexRelease rejects as duplicate types. The library's copies are the ones that count.
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@@ -206,23 +210,6 @@ room3 {
|
||||
schemaDirectory("$projectDir/schemas")
|
||||
}
|
||||
|
||||
sqldelight {
|
||||
databases {
|
||||
create("ChannelsDatabase") {
|
||||
packageName.set("fr.acinq.phoenix.db.sqldelight")
|
||||
srcDirs.from("src/commonMain/sqldelight/channelsdb")
|
||||
}
|
||||
create("PaymentsDatabase") {
|
||||
packageName.set("fr.acinq.phoenix.db.sqldelight")
|
||||
srcDirs.from("src/commonMain/sqldelight/paymentsdb")
|
||||
}
|
||||
create("AppDatabase") {
|
||||
packageName.set("fr.acinq.phoenix.db.sqldelight")
|
||||
srcDirs.from("src/commonMain/sqldelight/appdb")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "press.mantra.desktop.MainKt"
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.machankura.compose.ui.composable.widgets.nfc
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import press.mantra.compose.ui.composable.widgets.dialogs.ModalBottomSheet
|
||||
import fr.acinq.phoenix.utils.extensions.findActivitySafe
|
||||
|
||||
|
||||
/** Displays a blocking bottom sheet when the HCE service is started, using the state in [NfcStateRepository]. */
|
||||
@Composable
|
||||
fun HceMonitorDialog() {
|
||||
val context = LocalContext.current
|
||||
val activity = context.findActivitySafe() ?: return
|
||||
|
||||
val state by NfcStateRepository.state.collectAsState(initial = null)
|
||||
|
||||
val onDone = { activity.stopHceService() }
|
||||
|
||||
when (state) {
|
||||
is NfcState.EmulatingTag -> ModalBottomSheet(
|
||||
onDismiss = onDone,
|
||||
scrimAlpha = .5f,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
internalPadding = PaddingValues(horizontal = 24.dp),
|
||||
dismissOnScrimClick = false,
|
||||
) {
|
||||
Text(text = "HCE Emulation", style = MaterialTheme.typography.headlineSmall)
|
||||
// TODO: Spacer(Modifier.height(16.dp))
|
||||
// Image(
|
||||
// painter = painterResource(id = R.drawable.ic_nfc),
|
||||
// contentDescription = stringResource(R.string.nfc_button),
|
||||
// modifier = Modifier.size(64.dp),
|
||||
// colorFilter = ColorFilter.tint(MaterialTheme.colors.primary)
|
||||
// )
|
||||
Spacer(Modifier.height(24.dp))
|
||||
FilledTonalButton(
|
||||
// TODO: icon = R.drawable.ic_cross,
|
||||
onClick = onDone,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
Spacer(Modifier.height(24.dp))
|
||||
}
|
||||
is NfcState.ShowReader -> {}
|
||||
is NfcState.Inactive -> {}
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.machankura.compose.ui.composable.widgets.nfc
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import press.mantra.compose.ui.composable.widgets.dialogs.ModalBottomSheet
|
||||
import fr.acinq.phoenix.utils.extensions.findActivitySafe
|
||||
|
||||
@Composable
|
||||
fun NfcReaderMonitor() {
|
||||
val context = LocalContext.current
|
||||
val state by NfcStateRepository.state.collectAsState(initial = null)
|
||||
val activity = context.findActivitySafe() ?: return
|
||||
|
||||
val onDismiss = { activity.stopNfcReader() }
|
||||
|
||||
when (state) {
|
||||
null -> {}
|
||||
is NfcState.Inactive -> {}
|
||||
is NfcState.EmulatingTag -> {}
|
||||
is NfcState.ShowReader -> {
|
||||
ModalBottomSheet(
|
||||
onDismiss = onDismiss,
|
||||
scrimAlpha = .5f,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
internalPadding = PaddingValues(horizontal = 24.dp),
|
||||
dismissOnScrimClick = false,
|
||||
) {
|
||||
Text(text = "NFC Reader", style = MaterialTheme.typography.headlineSmall)
|
||||
// TODO: Spacer(Modifier.height(16.dp))
|
||||
// Image(
|
||||
// painter = painterResource(id = R.drawable.ic_nfc),
|
||||
// contentDescription = stringResource(R.string.nfc_button),
|
||||
// modifier = Modifier.size(64.dp),
|
||||
// colorFilter = ColorFilter.tint(MaterialTheme.colors.primary)
|
||||
// )
|
||||
Spacer(Modifier.height(16.dp))
|
||||
// Text(text = stringResource(R.string.nfc_reader_desc), style = MaterialTheme.typography.subtitle2)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
FilledTonalButton(
|
||||
// TODO: icon = R.drawable.ic_cross,
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
Spacer(Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.machankura.compose.ui.composable.widgets.nfc
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
sealed class NfcState {
|
||||
data object Inactive: NfcState()
|
||||
sealed class Busy : NfcState()
|
||||
data object ShowReader: Busy()
|
||||
data class EmulatingTag(val paymentRequest: String): Busy()
|
||||
}
|
||||
|
||||
object NfcStateRepository {
|
||||
private val _state = MutableStateFlow<NfcState?>(null)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
fun isReading() = state.value is NfcState.ShowReader
|
||||
fun isEmulating() = state.value is NfcState.EmulatingTag
|
||||
|
||||
fun updateState(s: NfcState) {
|
||||
_state.value = s
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import fr.acinq.phoenix.data.ExchangeRate;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS exchange_rates (
|
||||
fiat TEXT NOT NULL PRIMARY KEY,
|
||||
price REAL NOT NULL,
|
||||
type TEXT AS ExchangeRate.Type NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
insert:
|
||||
INSERT INTO exchange_rates(
|
||||
fiat, price, type, source, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?);
|
||||
|
||||
update:
|
||||
UPDATE exchange_rates SET price=?, type=?, source=?, updated_at=? WHERE fiat=?;
|
||||
|
||||
get:
|
||||
SELECT * FROM exchange_rates WHERE fiat=?;
|
||||
|
||||
list:
|
||||
SELECT * FROM exchange_rates;
|
||||
|
||||
delete:
|
||||
DELETE FROM exchange_rates WHERE fiat=?;
|
||||
@@ -1,22 +0,0 @@
|
||||
-- Generic key/value store
|
||||
|
||||
CREATE TABLE IF NOT EXISTS key_value_store (
|
||||
key TEXT NOT NULL PRIMARY KEY,
|
||||
value BLOB NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
get:
|
||||
SELECT * FROM key_value_store WHERE key = ?;
|
||||
|
||||
exists:
|
||||
SELECT COUNT(*) FROM key_value_store WHERE key = ?;
|
||||
|
||||
insert:
|
||||
INSERT INTO key_value_store(key, value, updated_at) VALUES (?, ?, ?);
|
||||
|
||||
update:
|
||||
UPDATE key_value_store SET value = ?, updated_at = ? WHERE key = ?;
|
||||
|
||||
delete:
|
||||
DELETE FROM key_value_store WHERE key = ?;
|
||||
@@ -1,42 +0,0 @@
|
||||
-- This table stores notifications of all kinds.
|
||||
-- * id => UUID of a notification
|
||||
-- * type_version => string tracking the type/version of a notification
|
||||
-- * data_json => json-serialized blob containing the notification details
|
||||
-- * created_at => when the notification was created, in millis
|
||||
-- * read_at => when the notification was read, in millis. Read notifications are typically not shown anymore.
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
type_version TEXT NOT NULL,
|
||||
data_json BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
read_at INTEGER DEFAULT NULL,
|
||||
node_id_hash TEXT DEFAULT NULL
|
||||
);
|
||||
|
||||
-- Create index to optimize the `listUnread` query.
|
||||
-- Tip: Use "explain query plan" to ensure they're actually being used.
|
||||
CREATE INDEX notifications_source ON notifications(node_id_hash) WHERE read_at IS NULL;
|
||||
|
||||
listUnread:
|
||||
SELECT id, group_concat(id, ';') AS grouped_ids, type_version, data_json, max(created_at)
|
||||
FROM notifications
|
||||
WHERE read_at IS NULL AND node_id_hash = ?
|
||||
GROUP BY type_version, data_json
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
get:
|
||||
SELECT id, type_version, data_json, created_at, read_at FROM notifications WHERE id=?;
|
||||
|
||||
insert:
|
||||
INSERT INTO notifications (
|
||||
id, type_version, data_json, created_at, node_id_hash
|
||||
) VALUES (?, ?, ?, ?, ?);
|
||||
|
||||
markAsRead:
|
||||
UPDATE notifications SET read_at=? WHERE id IN ?;
|
||||
|
||||
markAllAsRead:
|
||||
UPDATE notifications SET read_at=? WHERE read_at IS NULL;
|
||||
|
||||
initializeNodeIdHashColumn:
|
||||
UPDATE notifications SET node_id_hash=? WHERE node_id_hash IS NULL;
|
||||
@@ -1,10 +0,0 @@
|
||||
-- Migration: v1 -> v2
|
||||
--
|
||||
-- Changes:
|
||||
-- * Added table key_value_store
|
||||
|
||||
CREATE TABLE IF NOT EXISTS key_value_store (
|
||||
key TEXT NOT NULL PRIMARY KEY,
|
||||
value BLOB NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
@@ -1,15 +0,0 @@
|
||||
-- Migration: v2 -> v3
|
||||
--
|
||||
-- Changes:
|
||||
-- * Deleted table bitcoin_price_rates
|
||||
-- * Added table exchange_rates
|
||||
|
||||
DROP TABLE IF EXISTS bitcoin_price_rates;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS exchange_rates (
|
||||
fiat TEXT NOT NULL PRIMARY KEY,
|
||||
price REAL NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
@@ -1,12 +0,0 @@
|
||||
-- Migration: v3 -> v4
|
||||
--
|
||||
-- Changes:
|
||||
-- * add notifications table
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
type_version TEXT AS NotificationTypeVersion NOT NULL,
|
||||
data_json BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
read_at INTEGER DEFAULT NULL
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
-- Migration: v4 -> v5
|
||||
--
|
||||
-- Changes:
|
||||
-- * add contacts table
|
||||
-- * add contact_offers table
|
||||
|
||||
CREATE TABLE IF NOT EXISTS contacts (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
photo_uri TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS contact_offers (
|
||||
offer_id BLOB NOT NULL PRIMARY KEY,
|
||||
contact_id TEXT NOT NULL,
|
||||
offer TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY(contact_id) REFERENCES contacts(id)
|
||||
);
|
||||
@@ -1,7 +0,0 @@
|
||||
-- Migration: v5 -> v6
|
||||
--
|
||||
-- Changes:
|
||||
-- * add use_offer_key flag to the contacts table. By default, a contact is trusted.
|
||||
|
||||
ALTER TABLE contacts
|
||||
ADD COLUMN use_offer_key INTEGER AS Boolean DEFAULT 1 NOT NULL;
|
||||
@@ -1,23 +0,0 @@
|
||||
-- Migration: v6 -> v7
|
||||
--
|
||||
-- Changes:
|
||||
-- * Added table cloudkit_contacts_metadata
|
||||
-- * Added index on table cloudkit_contacts_metadata
|
||||
-- * Added table cloudkit_contacts_queue
|
||||
--
|
||||
-- See CloudKitContacts.sq for more details.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_contacts_metadata (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
record_creation INTEGER NOT NULL,
|
||||
record_blob BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS record_creation_idx
|
||||
ON cloudkit_contacts_metadata(record_creation);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_contacts_queue (
|
||||
rowid INTEGER PRIMARY KEY,
|
||||
id TEXT NOT NULL,
|
||||
date_added INTEGER NOT NULL
|
||||
);
|
||||
@@ -1,26 +0,0 @@
|
||||
-- Migration: v7 -> v8
|
||||
--
|
||||
-- Changes:
|
||||
-- * Migrating contacts to paymentsDb
|
||||
--
|
||||
|
||||
DROP INDEX IF EXISTS contact_id_index;
|
||||
|
||||
--- The original `contact_offers` table has a FOREIGN KEY constraint.
|
||||
--- This is going to cause a problem when we rename the contacts table.
|
||||
--- And there's no way to drop a constraint in sqlite.
|
||||
--- So we need to migrate the old table to a new one without the constraint.
|
||||
CREATE TABLE IF NOT EXISTS contact_offers_old (
|
||||
offer_id BLOB NOT NULL PRIMARY KEY,
|
||||
contact_id TEXT NOT NULL,
|
||||
offer TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
INSERT INTO contact_offers_old SELECT * FROM contact_offers;
|
||||
DROP TABLE IF EXISTS contact_offers;
|
||||
|
||||
ALTER TABLE contacts RENAME TO contacts_old;
|
||||
|
||||
DROP INDEX IF EXISTS record_creation_idx;
|
||||
ALTER TABLE cloudkit_contacts_metadata RENAME TO cloudkit_contacts_metadata_old;
|
||||
DROP TABLE IF EXISTS cloudkit_contacts_queue;
|
||||
@@ -1,10 +0,0 @@
|
||||
-- Migration: v8 -> v9
|
||||
--
|
||||
-- Changes:
|
||||
-- * Adding column `node_id_hash` to notifications table
|
||||
--
|
||||
|
||||
ALTER TABLE notifications
|
||||
ADD COLUMN node_id_hash TEXT DEFAULT NULL;
|
||||
|
||||
CREATE INDEX notifications_source ON notifications(node_id_hash) WHERE read_at IS NULL;
|
||||
@@ -1,48 +0,0 @@
|
||||
import fr.acinq.bitcoin.ByteVector32;
|
||||
import fr.acinq.lightning.channel.states.PersistedChannelState;
|
||||
import kotlin.Boolean;
|
||||
|
||||
-- channels table
|
||||
-- note: boolean are stored as INTEGER, with 0=false
|
||||
CREATE TABLE local_channels (
|
||||
channel_id BLOB AS ByteVector32 NOT NULL PRIMARY KEY,
|
||||
data BLOB AS PersistedChannelState NOT NULL,
|
||||
is_closed INTEGER AS Boolean DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
-- htlcs info table
|
||||
CREATE TABLE htlc_infos (
|
||||
channel_id BLOB AS ByteVector32 NOT NULL,
|
||||
commitment_number INTEGER NOT NULL,
|
||||
payment_hash BLOB AS ByteVector32 NOT NULL,
|
||||
cltv_expiry INTEGER NOT NULL,
|
||||
FOREIGN KEY(channel_id) REFERENCES local_channels(channel_id)
|
||||
);
|
||||
|
||||
CREATE INDEX htlc_infos_idx ON htlc_infos(channel_id, commitment_number);
|
||||
|
||||
-- channels queries
|
||||
getChannel:
|
||||
SELECT * FROM local_channels WHERE channel_id=?;
|
||||
|
||||
updateChannel:
|
||||
UPDATE local_channels SET data=? WHERE channel_id=?;
|
||||
|
||||
insertChannel:
|
||||
INSERT INTO local_channels VALUES (?, ?, 0);
|
||||
|
||||
closeLocalChannel:
|
||||
UPDATE local_channels SET is_closed=1 WHERE channel_id=?;
|
||||
|
||||
listLocalChannels:
|
||||
SELECT data FROM local_channels WHERE is_closed=0;
|
||||
|
||||
-- htlcs info queries
|
||||
insertHtlcInfo:
|
||||
INSERT INTO htlc_infos VALUES (?, ?, ?, ?);
|
||||
|
||||
listHtlcInfos:
|
||||
SELECT payment_hash, cltv_expiry FROM htlc_infos WHERE channel_id=? AND commitment_number=?;
|
||||
|
||||
deleteHtlcInfo:
|
||||
DELETE FROM htlc_infos WHERE channel_id=?;
|
||||
@@ -1,85 +0,0 @@
|
||||
|
||||
|
||||
-- This table stores the CKRecord metadata corresponding to a synced contact.
|
||||
-- * id => stores the primary key of the contact row
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_contacts_metadata (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
record_creation INTEGER NOT NULL,
|
||||
record_blob BLOB NOT NULL
|
||||
);
|
||||
|
||||
-- When resuming the download process (e.g. after app relaunch),
|
||||
-- we need to fetch the earliest creationDate.
|
||||
CREATE INDEX IF NOT EXISTS record_creation_idx
|
||||
ON cloudkit_contacts_metadata(record_creation);
|
||||
|
||||
-- This table stores the queue of items that need to be pushed to the cloud.
|
||||
-- * rowid => because we might store the same `id` multiple times
|
||||
-- * id => stores the primary key of the contact row
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_contacts_queue (
|
||||
rowid INTEGER PRIMARY KEY,
|
||||
id TEXT NOT NULL,
|
||||
date_added INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- ########## cloudkit_contacts_metadata ##########
|
||||
|
||||
addMetadata:
|
||||
INSERT INTO cloudkit_contacts_metadata (
|
||||
id,
|
||||
record_creation,
|
||||
record_blob)
|
||||
VALUES (?, ?, ?);
|
||||
|
||||
updateMetadata:
|
||||
UPDATE cloudkit_contacts_metadata
|
||||
SET record_blob = ?
|
||||
WHERE id = ?;
|
||||
|
||||
existsMetadata:
|
||||
SELECT COUNT(*) FROM cloudkit_contacts_metadata
|
||||
WHERE id = ?;
|
||||
|
||||
fetchMetadata:
|
||||
SELECT * FROM cloudkit_contacts_metadata
|
||||
WHERE id = ?;
|
||||
|
||||
scanMetadata:
|
||||
SELECT id FROM cloudkit_contacts_metadata;
|
||||
|
||||
fetchOldestCreation_Contacts:
|
||||
SELECT id, record_creation FROM cloudkit_contacts_metadata
|
||||
ORDER BY record_creation ASC
|
||||
LIMIT 1;
|
||||
|
||||
deleteMetadata:
|
||||
DELETE FROM cloudkit_contacts_metadata
|
||||
WHERE id = ?;
|
||||
|
||||
deleteAllFromMetadata:
|
||||
DELETE FROM cloudkit_contacts_metadata;
|
||||
|
||||
-- ########## cloudkit_contacts_queue ##########
|
||||
|
||||
addToQueue:
|
||||
INSERT INTO cloudkit_contacts_queue (
|
||||
id,
|
||||
date_added)
|
||||
VALUES (?, ?);
|
||||
|
||||
fetchQueueBatch:
|
||||
SELECT * FROM cloudkit_contacts_queue
|
||||
ORDER BY date_added ASC
|
||||
LIMIT :limit;
|
||||
|
||||
fetchQueueCount:
|
||||
SELECT COUNT(*) FROM cloudkit_contacts_queue;
|
||||
|
||||
deleteFromQueue:
|
||||
DELETE FROM cloudkit_contacts_queue
|
||||
WHERE rowid = ?;
|
||||
|
||||
deleteAllFromQueue:
|
||||
DELETE FROM cloudkit_contacts_queue;
|
||||
@@ -1,90 +0,0 @@
|
||||
import fr.acinq.lightning.utils.UUID;
|
||||
|
||||
-- This table stores the CKRecord metadata corresponding to a synced payment.
|
||||
-- * id => stores the primary key of the payment row
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_payments_metadata (
|
||||
id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
unpadded_size INTEGER NOT NULL,
|
||||
record_creation INTEGER NOT NULL,
|
||||
record_blob BLOB NOT NULL
|
||||
);
|
||||
|
||||
-- When resuming the download process (e.g. after app relaunch),
|
||||
-- we need to fetch the earliest creationDate.
|
||||
CREATE INDEX IF NOT EXISTS record_creation_idx
|
||||
ON cloudkit_payments_metadata(record_creation);
|
||||
|
||||
-- This table stores the queue of items that need to be pushed to the cloud.
|
||||
-- * id => stores the primary key of the payment row
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_payments_queue (
|
||||
rowid INTEGER PRIMARY KEY,
|
||||
id BLOB AS UUID NOT NULL,
|
||||
date_added INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- ########## cloudkit_payments_metadata ##########
|
||||
|
||||
addMetadata:
|
||||
INSERT INTO cloudkit_payments_metadata (
|
||||
id,
|
||||
unpadded_size,
|
||||
record_creation,
|
||||
record_blob)
|
||||
VALUES (?, ?, ?, ?);
|
||||
|
||||
updateMetadata:
|
||||
UPDATE cloudkit_payments_metadata
|
||||
SET unpadded_size = ?,
|
||||
record_blob = ?
|
||||
WHERE id = ?;
|
||||
|
||||
existsMetadata:
|
||||
SELECT COUNT(*) FROM cloudkit_payments_metadata
|
||||
WHERE id = ?;
|
||||
|
||||
fetchMetadata:
|
||||
SELECT * FROM cloudkit_payments_metadata
|
||||
WHERE id = ?;
|
||||
|
||||
scanMetadata:
|
||||
SELECT id FROM cloudkit_payments_metadata;
|
||||
|
||||
listNonZeroSizes:
|
||||
SELECT id, unpadded_size FROM cloudkit_payments_metadata WHERE unpadded_size > 0;
|
||||
|
||||
fetchOldestCreation:
|
||||
SELECT id, record_creation FROM cloudkit_payments_metadata
|
||||
ORDER BY record_creation ASC
|
||||
LIMIT 1;
|
||||
|
||||
deleteMetadata:
|
||||
DELETE FROM cloudkit_payments_metadata
|
||||
WHERE id = ?;
|
||||
|
||||
deleteAllFromMetadata:
|
||||
DELETE FROM cloudkit_payments_metadata;
|
||||
|
||||
-- ########## cloudkit_payments_queue ##########
|
||||
|
||||
addToQueue:
|
||||
INSERT INTO cloudkit_payments_queue (
|
||||
id,
|
||||
date_added)
|
||||
VALUES (?, ?);
|
||||
|
||||
fetchQueueBatch:
|
||||
SELECT * FROM cloudkit_payments_queue
|
||||
ORDER BY date_added ASC
|
||||
LIMIT :limit;
|
||||
|
||||
fetchQueueCount:
|
||||
SELECT COUNT(*) FROM cloudkit_payments_queue;
|
||||
|
||||
deleteFromQueue:
|
||||
DELETE FROM cloudkit_payments_queue
|
||||
WHERE rowid = ?;
|
||||
|
||||
deleteAllFromQueue:
|
||||
DELETE FROM cloudkit_payments_queue;
|
||||
@@ -1,36 +0,0 @@
|
||||
import fr.acinq.lightning.utils.UUID;
|
||||
import fr.acinq.phoenix.data.ContactInfo;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS contacts (
|
||||
id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
data BLOB AS ContactInfo NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER DEFAULT NULL
|
||||
);
|
||||
|
||||
listContacts:
|
||||
SELECT data
|
||||
FROM contacts;
|
||||
|
||||
getContact:
|
||||
SELECT data
|
||||
FROM contacts
|
||||
WHERE id = :contactId;
|
||||
|
||||
scanContacts:
|
||||
SELECT id, created_at FROM contacts;
|
||||
|
||||
existsContact:
|
||||
SELECT COUNT(*) FROM contacts
|
||||
WHERE id = ?;
|
||||
|
||||
insertContact:
|
||||
INSERT INTO contacts(id, data, created_at, updated_at)
|
||||
VALUES (:id, :data, :createdAt, :updatedAt);
|
||||
|
||||
updateContact:
|
||||
UPDATE contacts SET data=:data, updated_at=:updatedAt
|
||||
WHERE id=:contactId;
|
||||
|
||||
deleteContact:
|
||||
DELETE FROM contacts WHERE id=:contactId;
|
||||
@@ -1,35 +0,0 @@
|
||||
import fr.acinq.bitcoin.ByteVector32;
|
||||
import fr.acinq.bitcoin.TxId;
|
||||
import fr.acinq.lightning.utils.UUID;
|
||||
|
||||
CREATE TABLE on_chain_txs (
|
||||
payment_id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
tx_id BLOB AS TxId NOT NULL,
|
||||
confirmed_at INTEGER,
|
||||
locked_at INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX on_chain_txs_tx_id ON on_chain_txs(tx_id);
|
||||
|
||||
insert:
|
||||
INSERT INTO on_chain_txs(
|
||||
payment_id,
|
||||
tx_id,
|
||||
confirmed_at,
|
||||
locked_at)
|
||||
VALUES (?, ?, ?, ?);
|
||||
|
||||
setConfirmed:
|
||||
UPDATE on_chain_txs
|
||||
SET confirmed_at=?
|
||||
WHERE tx_id=?;
|
||||
|
||||
setLocked:
|
||||
UPDATE on_chain_txs
|
||||
SET locked_at=?
|
||||
WHERE tx_id=?;
|
||||
|
||||
listUnconfirmed:
|
||||
SELECT tx_id
|
||||
FROM on_chain_txs
|
||||
WHERE confirmed_at IS NULL;
|
||||
@@ -1,59 +0,0 @@
|
||||
-- This view returns all outgoing payments, but only successful incoming payments.
|
||||
CREATE VIEW payments
|
||||
AS SELECT id, created_at, completed_at, succeeded_at, order_ts, data
|
||||
FROM (
|
||||
SELECT id, created_at, received_at AS completed_at, received_at AS succeeded_at, received_at AS order_ts, data
|
||||
FROM payments_incoming
|
||||
WHERE received_at IS NOT NULL -- we only consider completed incoming payments
|
||||
UNION ALL
|
||||
SELECT id, created_at, completed_at, succeeded_at, created_at AS order_ts, data
|
||||
FROM payments_outgoing
|
||||
);
|
||||
|
||||
get:
|
||||
SELECT data
|
||||
FROM payments
|
||||
WHERE id=:id;
|
||||
|
||||
list:
|
||||
SELECT p.data, pm.*
|
||||
FROM payments AS p
|
||||
LEFT OUTER JOIN payments_metadata AS pm ON pm.payment_id = p.id
|
||||
ORDER BY order_ts DESC
|
||||
LIMIT :limit OFFSET :offset;
|
||||
|
||||
listSuccessful:
|
||||
SELECT p.data, pm.*
|
||||
FROM payments AS p
|
||||
LEFT OUTER JOIN payments_metadata AS pm ON pm.payment_id = p.id
|
||||
WHERE
|
||||
succeeded_at BETWEEN :succeeded_at_from AND :succeeded_at_to
|
||||
ORDER BY order_ts
|
||||
LIMIT :limit OFFSET :offset;
|
||||
|
||||
listInFlight:
|
||||
SELECT p.data, pm.*
|
||||
FROM payments AS p
|
||||
LEFT OUTER JOIN payments_metadata AS pm ON pm.payment_id = p.id
|
||||
WHERE
|
||||
completed_at IS NULL
|
||||
ORDER BY order_ts DESC
|
||||
LIMIT :limit OFFSET :offset;
|
||||
|
||||
listRecent:
|
||||
SELECT p.data, pm.*
|
||||
FROM payments AS p
|
||||
LEFT OUTER JOIN payments_metadata AS pm ON pm.payment_id = p.id
|
||||
WHERE
|
||||
order_ts >= :min_ts
|
||||
ORDER BY order_ts DESC
|
||||
LIMIT :limit OFFSET :offset;
|
||||
|
||||
getOldestCompletedAt:
|
||||
SELECT min(completed_at) AS completed_at
|
||||
FROM payments;
|
||||
|
||||
countCompletedInRange:
|
||||
SELECT count(id)
|
||||
FROM payments
|
||||
WHERE completed_at BETWEEN :completed_at_from AND :completed_at_to;
|
||||
@@ -1,90 +0,0 @@
|
||||
import fr.acinq.bitcoin.ByteVector32;
|
||||
import fr.acinq.bitcoin.TxId;
|
||||
import fr.acinq.lightning.db.IncomingPayment;
|
||||
import fr.acinq.lightning.utils.UUID;
|
||||
|
||||
-- incoming payments
|
||||
CREATE TABLE payments_incoming (
|
||||
id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
payment_hash BLOB AS ByteVector32 UNIQUE,
|
||||
tx_id BLOB AS TxId,
|
||||
created_at INTEGER NOT NULL,
|
||||
received_at INTEGER,
|
||||
data BLOB AS IncomingPayment NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX payments_incoming_payment_hash_idx ON payments_incoming(payment_hash);
|
||||
CREATE INDEX payments_incoming_tx_id_idx ON payments_incoming(tx_id);
|
||||
|
||||
-- Create indexes to optimize the queries in AggregatedQueries.
|
||||
-- Tip: Use "explain query plan" to ensure they're actually being used.
|
||||
CREATE INDEX payments_incoming_filter_idx
|
||||
ON payments_incoming(received_at)
|
||||
WHERE received_at IS NOT NULL;
|
||||
|
||||
-- queries
|
||||
|
||||
insert:
|
||||
INSERT INTO payments_incoming (
|
||||
id,
|
||||
payment_hash,
|
||||
tx_id,
|
||||
created_at,
|
||||
received_at,
|
||||
data)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
|
||||
update:
|
||||
UPDATE payments_incoming
|
||||
SET received_at=:receivedAt,
|
||||
tx_id=:txId,
|
||||
data=:data
|
||||
WHERE id=:id;
|
||||
|
||||
get:
|
||||
SELECT data
|
||||
FROM payments_incoming
|
||||
WHERE id=?;
|
||||
|
||||
getByPaymentHash:
|
||||
SELECT data
|
||||
FROM payments_incoming
|
||||
WHERE payment_hash=?;
|
||||
|
||||
list:
|
||||
SELECT payment.data
|
||||
FROM payments_incoming AS payment
|
||||
WHERE
|
||||
payment.created_at BETWEEN :created_at_from AND :created_at_to
|
||||
ORDER BY payment.created_at DESC
|
||||
LIMIT :limit OFFSET :offset;
|
||||
|
||||
listSuccessful:
|
||||
SELECT payment.data
|
||||
FROM payments_incoming AS payment
|
||||
WHERE
|
||||
payment.received_at BETWEEN :received_at_from AND :received_at_to
|
||||
ORDER BY payment.received_at DESC
|
||||
LIMIT :limit OFFSET :offset;
|
||||
|
||||
listByTxId:
|
||||
SELECT data
|
||||
FROM payments_incoming
|
||||
WHERE tx_id=?;
|
||||
|
||||
listSuccessfulIds:
|
||||
SELECT id, received_at
|
||||
FROM payments_incoming
|
||||
WHERE received_at IS NOT NULL;
|
||||
|
||||
deleteById:
|
||||
DELETE FROM payments_incoming
|
||||
WHERE id = ?;
|
||||
|
||||
deleteByPaymentHash:
|
||||
DELETE FROM payments_incoming
|
||||
WHERE payment_hash = ?;
|
||||
|
||||
-- use this in a `transaction` block to know how many rows were changed after an UPDATE
|
||||
changes:
|
||||
SELECT changes();
|
||||
@@ -1,65 +0,0 @@
|
||||
import fr.acinq.phoenix.db.payments.LnurlBase;
|
||||
import fr.acinq.phoenix.db.payments.LnurlMetadata;
|
||||
import fr.acinq.phoenix.db.payments.LnurlSuccessAction;
|
||||
import fr.acinq.lightning.utils.UUID;
|
||||
|
||||
-- This table stores metadata corresponding to a payment.
|
||||
-- * payment_id => uuid of an incoming or an outgoing payments
|
||||
-- * lnurl_base => serialized lnurl object (e.g. LNUrl.Pay), excluding metadata content
|
||||
-- * lnurl_metadata => serialized lnurl metadata (e.g. LNUrl.Pay.Metadata)
|
||||
-- * lnurl_successAction => serialized LUD-09 (e.g. LNUrl.PayInvoice.SuccessAction.Message)
|
||||
-- * user_description => user-customized short description
|
||||
-- * user_notes => user-customized notes (can be much longer than description)
|
||||
-- * modified_at => last time this DB entry was modified (i.e. within payments_metadata table)
|
||||
-- * original_fiat => stores original fiat price (via conversion rate) at time of transaction
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS payments_metadata (
|
||||
payment_id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
lnurl_base_type TEXT AS LnurlBase.TypeVersion,
|
||||
lnurl_base_blob BLOB,
|
||||
lnurl_description TEXT,
|
||||
lnurl_metadata_type TEXT AS LnurlMetadata.TypeVersion,
|
||||
lnurl_metadata_blob BLOB,
|
||||
lnurl_successAction_type TEXT AS LnurlSuccessAction.TypeVersion,
|
||||
lnurl_successAction_blob BLOB,
|
||||
user_description TEXT,
|
||||
user_notes TEXT,
|
||||
modified_at INTEGER,
|
||||
original_fiat_type TEXT,
|
||||
original_fiat_rate REAL,
|
||||
lightning_address TEXT
|
||||
);
|
||||
|
||||
-- queries for payments_metadata table
|
||||
|
||||
hasMetadata:
|
||||
SELECT COUNT(*) FROM payments_metadata
|
||||
WHERE payment_id = ?;
|
||||
|
||||
addMetadata:
|
||||
INSERT INTO payments_metadata (
|
||||
payment_id,
|
||||
lnurl_base_type, lnurl_base_blob,
|
||||
lnurl_description,
|
||||
lnurl_metadata_type, lnurl_metadata_blob,
|
||||
lnurl_successAction_type, lnurl_successAction_blob,
|
||||
user_description, user_notes,
|
||||
modified_at,
|
||||
original_fiat_type, original_fiat_rate,
|
||||
lightning_address)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
|
||||
updateUserInfo:
|
||||
UPDATE payments_metadata
|
||||
SET user_description = ?,
|
||||
user_notes = ?,
|
||||
modified_at = ?
|
||||
WHERE payment_id = ?;
|
||||
|
||||
get:
|
||||
SELECT * FROM payments_metadata
|
||||
WHERE payment_id = ?;
|
||||
|
||||
-- use this in a `transaction` block to know how many rows were changed after an UPDATE
|
||||
changes:
|
||||
SELECT changes();
|
||||
@@ -1,99 +0,0 @@
|
||||
import fr.acinq.bitcoin.ByteVector32;
|
||||
import fr.acinq.bitcoin.TxId;
|
||||
import fr.acinq.lightning.db.OutgoingPayment;
|
||||
import fr.acinq.lightning.utils.UUID;
|
||||
|
||||
CREATE TABLE payments_outgoing (
|
||||
id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
payment_hash BLOB AS ByteVector32,
|
||||
tx_id BLOB AS TxId,
|
||||
created_at INTEGER NOT NULL,
|
||||
completed_at INTEGER, -- a completed outgoing payment can be a success or a failure
|
||||
succeeded_at INTEGER, -- will only be set for a successful payment
|
||||
data BLOB AS OutgoingPayment NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX payments_outgoing_payment_hash_idx ON payments_outgoing(payment_hash);
|
||||
CREATE INDEX payments_outgoing_tx_id_idx ON payments_outgoing(tx_id);
|
||||
|
||||
-- Create indexes to optimize the queries in AggregatedQueries.
|
||||
-- Tip: Use "explain query plan" to ensure they're actually being used.
|
||||
CREATE INDEX payments_outgoing_filter_idx ON payments_outgoing(completed_at) WHERE completed_at IS NOT NULL;
|
||||
|
||||
CREATE TABLE link_lightning_outgoing_payment_parts (
|
||||
part_id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
parent_id BLOB AS UUID NOT NULL
|
||||
);
|
||||
|
||||
-- queries
|
||||
|
||||
insert:
|
||||
INSERT INTO payments_outgoing (
|
||||
id,
|
||||
payment_hash,
|
||||
tx_id,
|
||||
created_at,
|
||||
completed_at,
|
||||
succeeded_at,
|
||||
data)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
|
||||
insertPartLink:
|
||||
INSERT INTO link_lightning_outgoing_payment_parts(
|
||||
part_id,
|
||||
parent_id)
|
||||
VALUES (?, ?);
|
||||
|
||||
update:
|
||||
UPDATE payments_outgoing
|
||||
SET completed_at=?,
|
||||
succeeded_at=?,
|
||||
data=:data
|
||||
WHERE id = :id;
|
||||
|
||||
get:
|
||||
SELECT data
|
||||
FROM payments_outgoing
|
||||
WHERE id=?;
|
||||
|
||||
getParentId:
|
||||
SELECT parent_id
|
||||
FROM link_lightning_outgoing_payment_parts
|
||||
WHERE part_id=?;
|
||||
|
||||
listByPaymentHash:
|
||||
SELECT data
|
||||
FROM payments_outgoing
|
||||
WHERE payment_hash=?;
|
||||
|
||||
listByTxId:
|
||||
SELECT data
|
||||
FROM payments_outgoing
|
||||
WHERE tx_id=?;
|
||||
|
||||
deleteById:
|
||||
DELETE FROM payments_outgoing
|
||||
WHERE id = ?;
|
||||
|
||||
deleteByPaymentHash:
|
||||
DELETE FROM payments_outgoing
|
||||
WHERE payment_hash = ?;
|
||||
|
||||
list:
|
||||
SELECT data
|
||||
FROM payments_outgoing
|
||||
WHERE
|
||||
created_at BETWEEN :created_at_from AND :created_at_to
|
||||
LIMIT :limit OFFSET :offset;
|
||||
|
||||
listSuccessful:
|
||||
SELECT data
|
||||
FROM payments_outgoing
|
||||
WHERE
|
||||
succeeded_at BETWEEN :succeeded_at_from AND :succeeded_at_to
|
||||
LIMIT :limit OFFSET :offset;
|
||||
|
||||
listSuccessfulIds:
|
||||
SELECT id, completed_at
|
||||
FROM payments_outgoing
|
||||
WHERE completed_at IS NOT NULL;
|
||||
@@ -1,24 +0,0 @@
|
||||
-- Migration: v1 -> v2
|
||||
--
|
||||
-- Changes:
|
||||
-- * Added table cloudkit_payments_metadata
|
||||
-- * Added table cloudkit_payments_queue
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_payments_metadata (
|
||||
type INTEGER NOT NULL,
|
||||
id TEXT NOT NULL,
|
||||
unpadded_size INTEGER NOT NULL,
|
||||
record_creation INTEGER NOT NULL,
|
||||
record_blob BLOB NOT NULL,
|
||||
PRIMARY KEY (type, id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS record_creation_idx
|
||||
ON cloudkit_payments_metadata(record_creation);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_payments_queue (
|
||||
rowid INTEGER PRIMARY KEY,
|
||||
type INTEGER NOT NULL,
|
||||
id TEXT NOT NULL,
|
||||
date_added INTEGER NOT NULL
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
-- Migration: v10 -> v11
|
||||
--
|
||||
-- This is a code migration, see AfterVersion10.kt
|
||||
|
||||
-- incoming payments
|
||||
CREATE TABLE payments_incoming (
|
||||
id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
payment_hash BLOB AS ByteVector32 UNIQUE,
|
||||
tx_id BLOB AS TxId,
|
||||
created_at INTEGER NOT NULL,
|
||||
received_at INTEGER,
|
||||
data BLOB AS IncomingPayment NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX payments_incoming_payment_hash_idx ON payments_incoming(payment_hash);
|
||||
CREATE INDEX payments_incoming_tx_id_idx ON payments_incoming(tx_id);
|
||||
|
||||
-- Create indexes to optimize the queries in AggregatedQueries.
|
||||
-- Tip: Use "explain query plan" to ensure they're actually being used.
|
||||
CREATE INDEX payments_incoming_filter_idx
|
||||
ON payments_incoming(received_at)
|
||||
WHERE received_at IS NOT NULL;
|
||||
@@ -1,92 +0,0 @@
|
||||
-- Migration: v11 -> v12
|
||||
--
|
||||
-- Changes:
|
||||
-- * add table payments_outgoing, which stores all types of outgoing payments.
|
||||
-- * add table link_lightning_outgoing_payment_parts, which links lightning parts with the parent.
|
||||
--
|
||||
-- /!\ There is also a code migration, see AfterVersion11.kt.
|
||||
-- This code migration moves existing data to the new tables and drops the old tables.
|
||||
|
||||
CREATE TABLE payments_outgoing (
|
||||
id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
payment_hash BLOB AS ByteVector32,
|
||||
tx_id BLOB AS TxId,
|
||||
created_at INTEGER NOT NULL,
|
||||
completed_at INTEGER, -- a completed outgoing payment can be a success or a failure
|
||||
succeeded_at INTEGER, -- will only be set for a successful payment
|
||||
data BLOB AS OutgoingPayment NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX payments_outgoing_payment_hash_idx ON payments_outgoing(payment_hash);
|
||||
CREATE INDEX payments_outgoing_tx_id_idx ON payments_outgoing(tx_id);
|
||||
|
||||
-- Create indexes to optimize the queries in AggregatedQueries.
|
||||
-- Tip: Use "explain query plan" to ensure they're actually being used.
|
||||
CREATE INDEX payments_outgoing_filter_idx ON payments_outgoing(completed_at) WHERE completed_at IS NOT NULL;
|
||||
|
||||
CREATE TABLE link_lightning_outgoing_payment_parts (
|
||||
part_id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
parent_id BLOB AS UUID NOT NULL
|
||||
);
|
||||
|
||||
-- This table will be read then deleted in the code migration
|
||||
ALTER TABLE payments_metadata RENAME TO payments_metadata_old;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS payments_metadata (
|
||||
payment_id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
lnurl_base_type TEXT AS LnurlBase.TypeVersion,
|
||||
lnurl_base_blob BLOB,
|
||||
lnurl_description TEXT,
|
||||
lnurl_metadata_type TEXT AS LnurlMetadata.TypeVersion,
|
||||
lnurl_metadata_blob BLOB,
|
||||
lnurl_successAction_type TEXT AS LnurlSuccessAction.TypeVersion,
|
||||
lnurl_successAction_blob BLOB,
|
||||
user_description TEXT,
|
||||
user_notes TEXT,
|
||||
modified_at INTEGER,
|
||||
original_fiat_type TEXT,
|
||||
original_fiat_rate REAL
|
||||
);
|
||||
|
||||
CREATE TABLE on_chain_txs (
|
||||
payment_id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
tx_id BLOB AS TxId NOT NULL,
|
||||
confirmed_at INTEGER,
|
||||
locked_at INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX on_chain_txs_tx_id ON on_chain_txs(tx_id);
|
||||
|
||||
CREATE VIEW payments
|
||||
AS SELECT id, created_at, completed_at, succeeded_at, order_ts, data
|
||||
FROM (
|
||||
SELECT id, created_at, received_at AS completed_at, received_at AS succeeded_at, received_at AS order_ts, data
|
||||
FROM payments_incoming
|
||||
WHERE received_at IS NOT NULL -- we only consider completed incoming payments
|
||||
UNION ALL
|
||||
SELECT id, created_at, completed_at, succeeded_at, created_at AS order_ts, data
|
||||
FROM payments_outgoing
|
||||
);
|
||||
|
||||
-- This table will be read then deleted in the code migration
|
||||
ALTER TABLE cloudkit_payments_metadata RENAME TO cloudkit_payments_metadata_old;
|
||||
DROP INDEX record_creation_idx;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_payments_metadata (
|
||||
id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
unpadded_size INTEGER NOT NULL,
|
||||
record_creation INTEGER NOT NULL,
|
||||
record_blob BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS record_creation_idx
|
||||
ON cloudkit_payments_metadata(record_creation);
|
||||
|
||||
-- This table will be read then deleted in the code migration
|
||||
ALTER TABLE cloudkit_payments_queue RENAME TO cloudkit_payments_queue_old;
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_payments_queue (
|
||||
rowid INTEGER PRIMARY KEY,
|
||||
id BLOB AS UUID NOT NULL,
|
||||
date_added INTEGER NOT NULL
|
||||
);
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
-- Migration: v12 -> v13
|
||||
--
|
||||
-- Changes:
|
||||
-- * Added a new column [lightning_address] in table [payments_metadata]
|
||||
-- * Migration of contacts from appDb to paymentsDb:
|
||||
-- * Added new table: contacts
|
||||
-- * Added new table: cloudkit_contacts_metadata
|
||||
-- * Added new table: cloudkit_contacts_queue
|
||||
-- * Added new index: record_creation_idx
|
||||
|
||||
import fr.acinq.lightning.utils.UUID;
|
||||
import fr.acinq.phoenix.data.ContactInfo;
|
||||
|
||||
ALTER TABLE payments_metadata
|
||||
ADD COLUMN lightning_address TEXT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS contacts (
|
||||
id BLOB AS UUID NOT NULL PRIMARY KEY,
|
||||
data BLOB AS ContactInfo NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_contacts_metadata (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
record_creation INTEGER NOT NULL,
|
||||
record_blob BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS record_creation_idx
|
||||
ON cloudkit_contacts_metadata(record_creation);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cloudkit_contacts_queue (
|
||||
rowid INTEGER PRIMARY KEY,
|
||||
id TEXT NOT NULL,
|
||||
date_added INTEGER NOT NULL
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
-- Migration: v2 -> v3
|
||||
--
|
||||
-- Changes:
|
||||
-- * Added table payments_metadata
|
||||
|
||||
import fr.acinq.phoenix.db.payments.LnurlBase;
|
||||
import fr.acinq.phoenix.db.payments.LnurlMetadata;
|
||||
import fr.acinq.phoenix.db.payments.LnurlSuccessAction;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS payments_metadata (
|
||||
type INTEGER NOT NULL,
|
||||
id TEXT NOT NULL,
|
||||
lnurl_base_type TEXT AS LnurlBase.TypeVersion,
|
||||
lnurl_base_blob BLOB,
|
||||
lnurl_description TEXT,
|
||||
lnurl_metadata_type TEXT AS LnurlMetadata.TypeVersion,
|
||||
lnurl_metadata_blob BLOB,
|
||||
lnurl_successAction_type TEXT AS LnurlSuccessAction.TypeVersion,
|
||||
lnurl_successAction_blob BLOB,
|
||||
user_description TEXT,
|
||||
PRIMARY KEY (type, id)
|
||||
);
|
||||
@@ -1,12 +0,0 @@
|
||||
-- Migration: v3 -> v4
|
||||
--
|
||||
-- Changes:
|
||||
-- * Added column: payments_metadata.user_notes
|
||||
-- * Added column: payments_metadata.modified_at
|
||||
-- * Added indexes to improve performance of listAllPaymentsOrder query
|
||||
|
||||
ALTER TABLE payments_metadata ADD COLUMN user_notes TEXT DEFAULT NULL;
|
||||
ALTER TABLE payments_metadata ADD COLUMN modified_at INTEGER DEFAULT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS incoming_payments_created_at_idx ON incoming_payments(created_at);
|
||||
CREATE INDEX IF NOT EXISTS outgoing_payments_created_at_idx ON outgoing_payments(created_at);
|
||||
@@ -1,8 +0,0 @@
|
||||
-- Migration: v4 -> v5
|
||||
--
|
||||
-- Changes:
|
||||
-- * Added column: payments_metadata.original_fiat_type
|
||||
-- * Added column: payments_metadata.original_fiat_rate
|
||||
|
||||
ALTER TABLE payments_metadata ADD COLUMN original_fiat_type TEXT DEFAULT NULL;
|
||||
ALTER TABLE payments_metadata ADD COLUMN original_fiat_rate REAL DEFAULT NULL;
|
||||
@@ -1,22 +0,0 @@
|
||||
import fr.acinq.phoenix.db.payments.OutgoingPartClosingInfoTypeVersion;
|
||||
|
||||
-- Migration: v5 -> v6
|
||||
--
|
||||
-- Changes:
|
||||
-- * Add a new table that stores the transactions closing a lightning channel, represented
|
||||
-- as parts of an outgoing payment.
|
||||
-- * Add an index on parent_id in the closing txs table.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS outgoing_payment_closing_tx_parts (
|
||||
part_id TEXT NOT NULL PRIMARY KEY,
|
||||
part_parent_id TEXT NOT NULL,
|
||||
part_tx_id BLOB NOT NULL,
|
||||
part_amount_sat INTEGER NOT NULL,
|
||||
part_closing_info_type TEXT AS OutgoingPartClosingInfoTypeVersion NOT NULL,
|
||||
part_closing_info_blob BLOB NOT NULL,
|
||||
part_created_at INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY(part_parent_id) REFERENCES outgoing_payments(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS parent_id_idx ON outgoing_payment_closing_tx_parts(part_parent_id);
|
||||
@@ -1,17 +0,0 @@
|
||||
-- Migration: v6 -> v7
|
||||
--
|
||||
-- Changes:
|
||||
-- * Removed index: received_amount_msat_idx
|
||||
-- * Replaced index: incoming_payments_created_at_idx -> incoming_payments_filter_idx
|
||||
-- * Replaced index: outgoing_payments_created_at_idx -> outgoing_payments_filter_idx
|
||||
|
||||
DROP INDEX IF EXISTS received_amount_msat_idx;
|
||||
|
||||
DROP INDEX IF EXISTS incoming_payments_created_at_idx;
|
||||
CREATE INDEX IF NOT EXISTS incoming_payments_filter_idx
|
||||
ON incoming_payments(received_at)
|
||||
WHERE received_at IS NOT NULL;
|
||||
|
||||
DROP INDEX IF EXISTS outgoing_payments_created_at_idx;
|
||||
CREATE INDEX IF NOT EXISTS outgoing_payments_filter_idx
|
||||
ON outgoing_payments(completed_at);
|
||||
@@ -1,57 +0,0 @@
|
||||
import fr.acinq.phoenix.db.payments.OutgoingPartClosingInfoTypeVersion;
|
||||
|
||||
-- Migration: v7 -> v8
|
||||
--
|
||||
-- Changes:
|
||||
-- * add a new splice_outgoing_payments table to store outgoing splices
|
||||
-- * add a new channel_close_outgoing_payments table to store channel closings
|
||||
-- * add a new link_tx_to_payments table that links payments to on-chain transactions
|
||||
-- * add a new expected_amount_msat column in the incoming_payments table
|
||||
|
||||
CREATE TABLE IF NOT EXISTS splice_outgoing_payments (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
recipient_amount_sat INTEGER NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
mining_fees_sat INTEGER NOT NULL,
|
||||
tx_id BLOB NOT NULL,
|
||||
channel_id BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
confirmed_at INTEGER DEFAULT NULL,
|
||||
locked_at INTEGER DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_close_outgoing_payments (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
recipient_amount_sat INTEGER NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
is_default_address INTEGER NOT NULL,
|
||||
mining_fees_sat INTEGER NOT NULL,
|
||||
tx_id BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
confirmed_at INTEGER DEFAULT NULL,
|
||||
locked_at INTEGER DEFAULT NULL,
|
||||
channel_id BLOB NOT NULL,
|
||||
closing_info_type TEXT AS OutgoingPartClosingInfoTypeVersion NOT NULL,
|
||||
closing_info_blob BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS link_tx_to_payments (
|
||||
tx_id BLOB NOT NULL,
|
||||
type INTEGER NOT NULL,
|
||||
id TEXT NOT NULL,
|
||||
confirmed_at INTEGER DEFAULT NULL,
|
||||
locked_at INTEGER DEFAULT NULL,
|
||||
PRIMARY KEY (tx_id, type, id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS splice_cpfp_outgoing_payments (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
mining_fees_sat INTEGER NOT NULL,
|
||||
channel_id BLOB NOT NULL,
|
||||
tx_id BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
confirmed_at INTEGER DEFAULT NULL,
|
||||
locked_at INTEGER DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS link_tx_to_payments_txid ON link_tx_to_payments(tx_id);
|
||||
@@ -1,18 +0,0 @@
|
||||
import fr.acinq.phoenix.db.payments.InboundLiquidityLeaseTypeVersion;
|
||||
|
||||
-- Migration: v8 -> v9
|
||||
--
|
||||
-- Changes:
|
||||
-- * add a new inbound_liquidity_outgoing_payments table to store inbound liquidity payments
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inbound_liquidity_outgoing_payments (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
mining_fees_sat INTEGER NOT NULL,
|
||||
channel_id BLOB NOT NULL,
|
||||
tx_id BLOB NOT NULL,
|
||||
lease_type TEXT AS InboundLiquidityLeaseTypeVersion NOT NULL,
|
||||
lease_blob BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
confirmed_at INTEGER DEFAULT NULL,
|
||||
locked_at INTEGER DEFAULT NULL
|
||||
);
|
||||
@@ -1,8 +0,0 @@
|
||||
import fr.acinq.phoenix.db.payments.InboundLiquidityLeaseTypeVersion;
|
||||
|
||||
-- Migration: v9 -> v10
|
||||
--
|
||||
-- Changes:
|
||||
-- * Added a new column [payment_details_type] in table [inbound_liquidity_outgoing_payments]
|
||||
|
||||
ALTER TABLE inbound_liquidity_outgoing_payments ADD COLUMN payment_details_type TEXT DEFAULT NULL;
|
||||
Reference in New Issue
Block a user