[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-wallet-kotlin] 02/02: Add initial code for updating an exchange
From: |
gnunet |
Subject: |
[taler-wallet-kotlin] 02/02: Add initial code for updating an exchange |
Date: |
Thu, 09 Jul 2020 21:10:20 +0200 |
This is an automated email from the git hooks/post-receive script.
torsten-grote pushed a commit to branch master
in repository wallet-kotlin.
commit 4379d554c72d80328dff1e873d7cc007a1a5881a
Author: Torsten Grote <t@grobox.de>
AuthorDate: Wed Jul 8 12:00:53 2020 -0300
Add initial code for updating an exchange
This introduces a common Database interface that gets currently only
implemented by a fake in-memmory database.
---
build.gradle | 23 +-
settings.gradle | 1 +
.../kotlin/net/taler/wallet/kotlin/Db.kt} | 9 +-
.../net/taler/wallet/kotlin/TestUtilsAndroid.kt} | 2 +
.../net/taler/wallet/kotlin/crypto/RefreshTest.kt | 4 +-
.../kotlin/net/taler/wallet/kotlin/Db.kt | 59 ++++
.../kotlin/net/taler/wallet/kotlin/Timestamp.kt | 8 +-
.../kotlin/net/taler/wallet/kotlin/Types.kt | 89 ------
.../kotlin/net/taler/wallet/kotlin/Utils.kt} | 19 +-
.../net/taler/wallet/kotlin/crypto/Refresh.kt | 2 +-
.../net/taler/wallet/kotlin/crypto/Signature.kt | 2 +-
.../kotlin/{Timestamp.kt => exchange/Auditor.kt} | 53 ++--
.../net/taler/wallet/kotlin/exchange/Exchange.kt | 142 +++++++++
.../{Types.kt => exchange/ExchangeRecord.kt} | 206 +++++++------
.../net/taler/wallet/kotlin/exchange/Keys.kt | 177 ++++++++++++
.../net/taler/wallet/kotlin/operations/Withdraw.kt | 7 +-
.../kotlin/net/taler/wallet/kotlin/DbTest.kt | 67 +++++
.../kotlin/net/taler/wallet/kotlin/TestUtils.kt | 14 +-
.../taler/wallet/kotlin/crypto/SignatureTest.kt | 6 +-
.../taler/wallet/kotlin/exchange/ExchangeTest.kt | 317 +++++++++++++++++++++
.../kotlin/net/taler/wallet/kotlin/Db.kt} | 9 +-
.../kotlin/{runCoroutine.kt => TestUtils.kt} | 2 +
.../kotlin/net/taler/wallet/kotlin/Db.kt} | 9 +-
.../kotlin/net/taler/wallet/kotlin/TestUtils.kt} | 2 +
24 files changed, 989 insertions(+), 240 deletions(-)
diff --git a/build.gradle b/build.gradle
index 342690d..c173b78 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,13 +14,19 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+buildscript {
+ ext.kotlin_version = '1.3.72'
+}
+
plugins {
- id 'org.jetbrains.kotlin.multiplatform' version '1.3.72'
- id 'org.jetbrains.kotlin.plugin.serialization' version '1.3.72'
+ id 'org.jetbrains.kotlin.multiplatform' version "$kotlin_version"
+ id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
}
+
repositories {
mavenCentral()
jcenter()
+ maven { url "https://dl.bintray.com/terl/lazysodium-maven" }
}
group 'net.taler'
version '0.0.1'
@@ -97,12 +103,11 @@ kotlin {
implementation "io.ktor:ktor-client-js:$ktor_version"
implementation
"io.ktor:ktor-client-serialization-js:$ktor_version"
// bug: https://github.com/ktorio/ktor/issues/1822
- implementation npm('abort-controller', '3.0.0') // work-around
for above bug
- implementation npm('utf-8-validate', '5.0.2') // work-around
for above bug
- implementation npm('text-encoding', '0.7.0') // work-around
for above bug
- implementation npm('node-fetch', '2.6.0') // work-around for
above bug
- implementation npm('bufferutil', '4.0.1') // work-around for
above bug
- implementation npm('fs', '*') // work-around for above bug
+ api npm("text-encoding", '0.7.0') // work-around for above bug
+ api npm("bufferutil", '4.0.1') // work-around for above bug
+ api npm("utf-8-validate", '5.0.2') // work-around for above bug
+ api npm("abort-controller", '3.0.0') // work-around for above
bug
+ api npm("fs", '*') // work-around for above bug
implementation npm('tweetnacl', '1.0.3')
implementation npm('ed2curve', '0.3.0')
@@ -119,7 +124,7 @@ kotlin {
dependencies {
implementation
"org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutines_version"
implementation "io.ktor:ktor-client-curl:$ktor_version"
-
implementation("io.ktor:ktor-client-serialization-native:$ktor_version")
+ implementation
"io.ktor:ktor-client-serialization-native:$ktor_version"
}
}
linuxTest {
diff --git a/settings.gradle b/settings.gradle
index 8a34848..bf5e773 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,3 @@
rootProject.name = 'wallet-kotlin'
+enableFeaturePreview("GRADLE_METADATA")
diff --git a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
b/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt
similarity index 80%
copy from src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
copy to src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt
index 524da15..45cbfc3 100644
--- a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
+++ b/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt
@@ -16,7 +16,8 @@
package net.taler.wallet.kotlin
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.runBlocking
-
-actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit) =
runBlocking { block(this) }
+internal actual class DbFactory {
+ actual fun openDb(): Db {
+ return FakeDb()
+ }
+}
diff --git a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
b/src/androidTest/kotlin/net/taler/wallet/kotlin/TestUtilsAndroid.kt
similarity index 92%
copy from src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
copy to src/androidTest/kotlin/net/taler/wallet/kotlin/TestUtilsAndroid.kt
index 524da15..a362874 100644
--- a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
+++ b/src/androidTest/kotlin/net/taler/wallet/kotlin/TestUtilsAndroid.kt
@@ -20,3 +20,5 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit) =
runBlocking { block(this) }
+
+actual fun getPlatformTarget(): PlatformTarget = PlatformTarget.ANDROID
diff --git
a/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
b/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
index 37cf10f..4ed903e 100644
--- a/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
+++ b/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
@@ -21,13 +21,13 @@ import net.taler.wallet.kotlin.Base32Crockford
import net.taler.wallet.kotlin.CoinRecord
import net.taler.wallet.kotlin.CoinSourceType.WITHDRAW
import net.taler.wallet.kotlin.CoinStatus.DORMANT
-import net.taler.wallet.kotlin.DenominationRecord
-import net.taler.wallet.kotlin.DenominationStatus
import net.taler.wallet.kotlin.Timestamp
import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo
import net.taler.wallet.kotlin.crypto.Refresh.RefreshPlanchetRecord
import net.taler.wallet.kotlin.crypto.Refresh.RefreshSessionRecord
import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination
+import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.DenominationStatus
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
new file mode 100644
index 0000000..303c526
--- /dev/null
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt
@@ -0,0 +1,59 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin
+
+import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.ExchangeRecord
+
+internal interface Db {
+ suspend fun put(exchange: ExchangeRecord)
+ suspend fun listExchanges(): List<ExchangeRecord>
+ suspend fun getExchangeByBaseUrl(baseUrl: String): ExchangeRecord?
+ suspend fun deleteExchangeByBaseUrl(baseUrl: String)
+ suspend fun put(denomination: DenominationRecord)
+}
+
+internal expect class DbFactory() {
+ fun openDb(): Db
+}
+
+internal class FakeDb: Db {
+
+ private val exchanges = HashMap<String, ExchangeRecord>()
+ private val denominations = HashMap<String, DenominationRecord>()
+
+ override suspend fun put(exchange: ExchangeRecord) {
+ exchanges[exchange.baseUrl] = exchange
+ }
+
+ override suspend fun listExchanges(): List<ExchangeRecord> {
+ return exchanges.values.toList()
+ }
+
+ override suspend fun getExchangeByBaseUrl(baseUrl: String):
ExchangeRecord? {
+ return exchanges[baseUrl]
+ }
+
+ override suspend fun deleteExchangeByBaseUrl(baseUrl: String) {
+ exchanges.remove(baseUrl)
+ }
+
+ override suspend fun put(denomination: DenominationRecord) {
+ denominations[denomination.exchangeBaseUrl] = denomination
+ }
+
+}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
index d0260cf..b5c850f 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
@@ -17,11 +17,13 @@
package net.taler.wallet.kotlin
import com.soywiz.klock.DateTime
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
import net.taler.wallet.kotlin.crypto.CryptoImpl.Companion.toByteArray
-
-class Timestamp(
- // @JsonProperty("t_ms")
+@Serializable
+data class Timestamp(
+ @SerialName("t_ms")
val ms: Long
) {
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
index 4702a19..2365795 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
@@ -39,95 +39,6 @@ data class WireFee(
val signature: String
)
-data class DenominationRecord(
- /**
- * Value of one coin of the denomination.
- */
- val value: Amount,
- /**
- * The denomination public key.
- */
- val denomPub: String,
- /**
- * Hash of the denomination public key.
- * Stored in the database for faster lookups.
- */
- val denomPubHash: String,
- /**
- * Fee for withdrawing.
- */
- val feeWithdraw: Amount,
- /**
- * Fee for depositing.
- */
- val feeDeposit: Amount,
- /**
- * Fee for refreshing.
- */
- val feeRefresh: Amount,
- /**
- * Fee for refunding.
- */
- val feeRefund: Amount,
- /**
- * Validity start date of the denomination.
- */
- val stampStart: Timestamp,
- /**
- * Date after which the currency can't be withdrawn anymore.
- */
- val stampExpireWithdraw: Timestamp,
- /**
- * Date after the denomination officially doesn't exist anymore.
- */
- val stampExpireLegal: Timestamp,
- /**
- * Data after which coins of this denomination can't be deposited anymore.
- */
- val stampExpireDeposit: Timestamp,
- /**
- * Signature by the exchange's master key over the denomination
- * information.
- */
- val masterSig: String,
- /**
- * Did we verify the signature on the denomination?
- */
- val status: DenominationStatus,
- /**
- * Was this denomination still offered by the exchange the last time
- * we checked?
- * Only false when the exchange redacts a previously published
denomination.
- */
- val isOffered: Boolean,
- /**
- * Did the exchange revoke the denomination?
- * When this field is set to true in the database, the same transaction
- * should also mark all affected coins as revoked.
- */
- val isRevoked: Boolean,
- /**
- * Base URL of the exchange.
- */
- val exchangeBaseUrl: String
-)
-
-enum class DenominationStatus {
- /**
- * Verification was delayed.
- */
- Unverified,
-
- /**
- * Verified as valid.
- */
- VerifiedGood,
-
- /**
- * Verified as invalid.
- */
- VerifiedBad
-}
class CoinRecord(
/**
diff --git a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
similarity index 57%
copy from src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
copy to src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
index 524da15..aa3fd91 100644
--- a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt
@@ -16,7 +16,20 @@
package net.taler.wallet.kotlin
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.runBlocking
+import io.ktor.client.HttpClient
+import io.ktor.client.features.json.JsonFeature
+import io.ktor.client.features.json.serializer.KotlinxSerializer
+import kotlinx.serialization.UnstableDefault
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonConfiguration
-actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit) =
runBlocking { block(this) }
+fun getDefaultHttpClient(): HttpClient = HttpClient {
+ install(JsonFeature) {
+ serializer = KotlinxSerializer(Json(getJsonConfiguration()))
+ }
+}
+
+@OptIn(UnstableDefault::class)
+internal fun getJsonConfiguration() = JsonConfiguration(
+ ignoreUnknownKeys = true
+)
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
index 4d6377a..602a1ab 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
@@ -19,11 +19,11 @@ package net.taler.wallet.kotlin.crypto
import net.taler.wallet.kotlin.Amount
import net.taler.wallet.kotlin.Base32Crockford
import net.taler.wallet.kotlin.CoinRecord
-import net.taler.wallet.kotlin.DenominationRecord
import net.taler.wallet.kotlin.Timestamp
import net.taler.wallet.kotlin.crypto.Signature.Companion.WALLET_COIN_LINK
import net.taler.wallet.kotlin.crypto.Signature.Companion.WALLET_COIN_MELT
import net.taler.wallet.kotlin.crypto.Signature.PurposeBuilder
+import net.taler.wallet.kotlin.exchange.DenominationRecord
internal class Refresh(private val crypto: Crypto) {
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt
index 5e4a65e..ee1dff4 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt
@@ -17,9 +17,9 @@
package net.taler.wallet.kotlin.crypto
import net.taler.wallet.kotlin.Base32Crockford
-import net.taler.wallet.kotlin.DenominationRecord
import net.taler.wallet.kotlin.WireFee
import net.taler.wallet.kotlin.crypto.CryptoImpl.Companion.toByteArray
+import net.taler.wallet.kotlin.exchange.DenominationRecord
internal class Signature(private val crypto: Crypto) {
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Auditor.kt
similarity index 50%
copy from src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
copy to src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Auditor.kt
index d0260cf..4df0bdf 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Auditor.kt
@@ -14,32 +14,43 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.wallet.kotlin
+package net.taler.wallet.kotlin.exchange
-import com.soywiz.klock.DateTime
-import net.taler.wallet.kotlin.crypto.CryptoImpl.Companion.toByteArray
+import kotlinx.serialization.Serializable
+/**
+ * Auditor information as given by the exchange in /keys.
+ */
+@Serializable
+data class Auditor(
+ /**
+ * Auditor's public key.
+ */
+ val auditor_pub: String,
-class Timestamp(
- // @JsonProperty("t_ms")
- val ms: Long
-) {
-
- companion object {
- const val NEVER: Long = -1
- fun now(): Timestamp = Timestamp(DateTime.now().unixMillisLong)
- }
+ /**
+ * Base URL of the auditor.
+ */
+ val auditor_url: String,
/**
- * Returns a copy of this [Timestamp] rounded to seconds.
+ * List of signatures for denominations by the auditor.
*/
- fun truncateSeconds(): Timestamp {
- if (ms == NEVER) return Timestamp(ms)
- return Timestamp((ms / 1000L) * 1000L)
- }
+ val denomination_keys: List<AuditorDenomSig>
+)
- fun roundedToByteArray(): ByteArray = ByteArray(8).apply {
- (truncateSeconds().ms * 1000L).toByteArray().copyInto(this)
- }
+/**
+ * Signature by the auditor that a particular denomination key is audited.
+ */
+@Serializable
+data class AuditorDenomSig(
+ /**
+ * Denomination public key's hash.
+ */
+ val denom_pub_h: String,
-}
+ /**
+ * The signature.
+ */
+ val auditor_sig: String
+)
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
new file mode 100644
index 0000000..4124c30
--- /dev/null
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
@@ -0,0 +1,142 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin.exchange
+
+import io.ktor.client.HttpClient
+import io.ktor.client.request.get
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Base32Crockford
+import net.taler.wallet.kotlin.Db
+import net.taler.wallet.kotlin.DbFactory
+import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.compareVersions
+import net.taler.wallet.kotlin.crypto.Crypto
+import net.taler.wallet.kotlin.crypto.CryptoFactory
+import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.ExchangeUpdateReason.Initial
+import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchKeys
+import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchWire
+import net.taler.wallet.kotlin.getDefaultHttpClient
+
+internal class Exchange(
+ private val crypto: Crypto = CryptoFactory.getCrypto(),
+ private val httpClient: HttpClient = getDefaultHttpClient(),
+ private val db: Db = DbFactory().openDb()
+) {
+
+ companion object {
+ const val PROTOCOL_VERSION = "7:0:0"
+ fun normalizeUrl(exchangeBaseUrl: String): String {
+ var url = exchangeBaseUrl
+ if (!url.startsWith("http")) url = "http://$url"
+ if (!url.endsWith("/")) url = "$url/"
+ // TODO also remove query and hash
+ return url
+ }
+ }
+
+ suspend fun updateFromUrl(baseUrl: String): ExchangeRecord {
+ val now = Timestamp.now()
+ val url = normalizeUrl(baseUrl)
+ var record = db.getExchangeByBaseUrl(url) ?: ExchangeRecord(
+ baseUrl = url,
+ timestampAdded = now,
+ updateStatus = FetchKeys,
+ updateStarted = now,
+ updateReason = Initial
+ ).also { db.put(it) }
+ record = updateKeys(record)
+ // TODO update wire
+ // TODO update ToS
+ return record
+ }
+
+ /**
+ * Fetch the exchange's /keys and update database accordingly.
+ *
+ * Exceptions thrown in this method must be caught and reported in the
pending operations.
+ */
+ internal suspend fun updateKeys(record: ExchangeRecord): ExchangeRecord {
+ val keys: Keys = fetchKeys(record.baseUrl)
+ // check if there are denominations offered
+ // TODO provide more error information for catcher
+ if (keys.denoms.isEmpty()) {
+ throw Error("Exchange doesn't offer any denominations")
+ }
+ // check if the exchange version is compatible
+ val versionMatch = compareVersions(PROTOCOL_VERSION, keys.version)
+ if (versionMatch == null || !versionMatch.compatible) {
+ throw Error("Exchange protocol version not compatible with wallet")
+ }
+ val currency = keys.denoms[0].value.currency
+ val newDenominations = keys.denoms.map { d ->
+ getDenominationRecord(record.baseUrl, currency, d)
+ }
+ // update exchange details
+ val details = ExchangeDetails(
+ auditors = keys.auditors,
+ currency = currency,
+ lastUpdateTime = keys.list_issue_date,
+ masterPublicKey = keys.master_public_key,
+ protocolVersion = keys.version,
+ signingKeys = keys.signkeys
+ )
+ val updatedRecord = record.copy(details = details, updateStatus =
FetchWire)
+ for (newDenomination in newDenominations) {
+ // TODO check oldDenominations and do consistency checks
+ db.put(newDenomination)
+ }
+
+ // TODO handle keys.recoup
+
+ return updatedRecord
+ }
+
+ /**
+ * Fetch an exchange's /keys with the given normalized base URL.
+ *
+ * Visible for testing.
+ */
+ internal suspend fun fetchKeys(baseUrl: String): Keys {
+ return httpClient.get("${baseUrl}keys")
+ }
+
+ /**
+ * Turn an exchange's denominations from /keys into [DenominationRecord]s
+ *
+ * Visible for testing.
+ */
+ internal fun getDenominationRecord(baseUrl: String, currency: String, d:
Denomination): DenominationRecord {
+ checkCurrency(currency, d.value)
+ checkCurrency(currency, d.fee_refund)
+ checkCurrency(currency, d.fee_withdraw)
+ checkCurrency(currency, d.fee_refresh)
+ checkCurrency(currency, d.fee_deposit)
+ return d.toDenominationRecord(
+ baseUrl = baseUrl,
+ denomPubHash = crypto.sha512(Base32Crockford.decode(d.denom_pub)),
+ isOffered = true,
+ isRevoked = false,
+ status = Unverified
+ )
+ }
+
+ private fun checkCurrency(currency: String, amount: Amount) {
+ if (currency != amount.currency) throw Error("Expected currency
$currency, but found ${amount.currency}")
+ }
+
+}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
similarity index 52%
copy from src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
copy to src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
index 4702a19..d882249 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
@@ -14,31 +14,135 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.wallet.kotlin
+package net.taler.wallet.kotlin.exchange
-data class WireFee(
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.WireFee
+
+/**
+ * Exchange record as stored in the wallet's database.
+ */
+data class ExchangeRecord(
/**
- * Fee for wire transfers.
+ * Base url of the exchange.
*/
- val wireFee: Amount,
+ val baseUrl: String,
+
/**
- * Fees to close and refund a reserve.
+ * Was the exchange added as a built-in exchange?
*/
- val closingFee: Amount,
+ val builtIn: Boolean = false,
+
/**
- * Start date of the fee.
+ * Details, once known.
*/
- val startStamp: Timestamp,
+ val details: ExchangeDetails? = null,
+
/**
- * End date of the fee.
+ * Mapping from wire method type to the wire fee.
*/
- val endStamp: Timestamp,
+ val wireInfo: ExchangeWireInfo? = null,
+
/**
- * Signature made by the exchange master key.
+ * When was the exchange added to the wallet?
*/
- val signature: String
+ val timestampAdded: Timestamp,
+
+ /**
+ * Terms of service text or undefined if not downloaded yet.
+ */
+ val termsOfServiceText: String? = null,
+
+ /**
+ * ETag for last terms of service download.
+ */
+ val termsOfServiceLastEtag: String? = null,
+
+ /**
+ * ETag for last terms of service download.
+ */
+ val termsOfServiceAcceptedEtag: String? = null,
+
+ /**
+ * ETag for last terms of service download.
+ */
+ val termsOfServiceAcceptedTimestamp: Timestamp? = null,
+
+ /**
+ * Time when the update to the exchange has been started or
+ * undefined if no update is in progress.
+ */
+ val updateStarted: Timestamp? = null,
+
+ val updateStatus: ExchangeUpdateStatus,
+
+ val updateReason: ExchangeUpdateReason? = null
+) {
+ init {
+ check(baseUrl == Exchange.normalizeUrl(baseUrl)) { "Base URL was not
normalized" }
+ }
+}
+
+/**
+ * Details about the exchange that we only know after querying /keys and /wire.
+ */
+data class ExchangeDetails(
+ /**
+ * Master public key of the exchange.
+ */
+ val masterPublicKey: String,
+
+ /**
+ * Auditors (partially) auditing the exchange.
+ */
+ val auditors: List<Auditor>,
+
+ /**
+ * Currency that the exchange offers.
+ */
+ val currency: String,
+
+ /**
+ * Last observed protocol version.
+ */
+ val protocolVersion: String,
+
+ /**
+ * Signing keys we got from the exchange, can also contain
+ * older signing keys that are not returned by /keys anymore.
+ */
+ val signingKeys: List<SigningKey>,
+
+ /**
+ * Timestamp for last update.
+ */
+ val lastUpdateTime: Timestamp
)
+data class ExchangeWireInfo(
+ val feesForType: Map<String, List<WireFee>>,
+ val accounts: List<ExchangeBankAccount>
+)
+
+data class ExchangeBankAccount(
+ val paytoUri: String
+)
+
+sealed class ExchangeUpdateStatus(val value: String) {
+ object FetchKeys: ExchangeUpdateStatus("fetch-keys")
+ object FetchWire: ExchangeUpdateStatus("fetch-wire")
+ object FetchTerms: ExchangeUpdateStatus("fetch-terms")
+ object FinalizeUpdate: ExchangeUpdateStatus("finalize-update")
+ object Finished: ExchangeUpdateStatus("finished")
+}
+
+sealed class ExchangeUpdateReason(val value: String) {
+ object Initial: ExchangeUpdateReason("initial")
+ object Forced: ExchangeUpdateReason("forced")
+ object Scheduled: ExchangeUpdateReason("scheduled")
+}
+
data class DenominationRecord(
/**
* Value of one coin of the denomination.
@@ -128,81 +232,3 @@ enum class DenominationStatus {
*/
VerifiedBad
}
-
-class CoinRecord(
- /**
- * Where did the coin come from? Used for recouping coins.
- */
- val coinSource: CoinSourceType,
-
- /**
- * Public key of the coin.
- */
- val coinPub: String,
-
- /**
- * Private key to authorize operations on the coin.
- */
- val coinPriv: String,
-
- /**
- * Key used by the exchange used to sign the coin.
- */
- val denomPub: String,
-
- /**
- * Hash of the public key that signs the coin.
- */
- val denomPubHash: String,
-
- /**
- * Unblinded signature by the exchange.
- */
- val denomSig: String,
-
- /**
- * Amount that's left on the coin.
- */
- val currentAmount: Amount,
-
- /**
- * Base URL that identifies the exchange from which we got the coin.
- */
- val exchangeBaseUrl: String,
-
- /**
- * The coin is currently suspended, and will not be used for payments.
- */
- val suspended: Boolean,
-
- /**
- * Blinding key used when withdrawing the coin.
- * Potentially send again during payback.
- */
- val blindingKey: String,
-
- /**
- * Status of the coin.
- */
- val status: CoinStatus
-)
-
-enum class CoinSourceType(val value: String) {
- WITHDRAW("withdraw"),
- REFRESH("refresh"),
- TIP("tip")
-}
-
-enum class CoinStatus(val value: String) {
-
- /**
- * Withdrawn and never shown to anybody.
- */
- FRESH("fresh"),
-
- /**
- * A coin that has been spent and refreshed.
- */
- DORMANT("dormant")
-
-}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
new file mode 100644
index 0000000..c7ea966
--- /dev/null
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
@@ -0,0 +1,177 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin.exchange
+
+import kotlinx.serialization.Serializable
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Base32Crockford
+import net.taler.wallet.kotlin.Timestamp
+
+/**
+ * Structure that the exchange gives us in /keys.
+ */
+@Serializable
+internal data class Keys(
+ /**
+ * List of offered denominations.
+ */
+ val denoms: List<Denomination>,
+
+ /**
+ * The exchange's master public key.
+ */
+ val master_public_key: String,
+
+ /**
+ * The list of auditors (partially) auditing the exchange.
+ */
+ val auditors: List<Auditor>,
+
+ /**
+ * Timestamp when this response was issued.
+ */
+ val list_issue_date: Timestamp,
+
+ /**
+ * List of revoked denominations.
+ */
+ val recoup: List<Recoup>?,
+
+ /**
+ * Short-lived signing keys used to sign online
+ * responses.
+ */
+ val signkeys: List<SigningKey>,
+
+ /**
+ * Protocol version.
+ */
+ val version: String
+)
+
+/**
+ * Structure of one exchange signing key in the /keys response.
+ */
+@Serializable
+data class SigningKey(
+ val stamp_start: Timestamp,
+ val stamp_expire: Timestamp,
+ val stamp_end: Timestamp,
+ val key: String,
+ val master_sig: String
+)
+
+/**
+ * Denomination as found in the /keys response from the exchange.
+ */
+@Serializable
+internal data class Denomination(
+ /**
+ * Value of one coin of the denomination.
+ */
+ val value: Amount,
+
+ /**
+ * Public signing key of the denomination.
+ */
+ val denom_pub: String,
+
+ /**
+ * Fee for withdrawing.
+ */
+ val fee_withdraw: Amount,
+
+ /**
+ * Fee for depositing.
+ */
+ val fee_deposit: Amount,
+
+ /**
+ * Fee for refreshing.
+ */
+ val fee_refresh: Amount,
+
+ /**
+ * Fee for refunding.
+ */
+ val fee_refund: Amount,
+
+ /**
+ * Start date from which withdraw is allowed.
+ */
+ val stamp_start: Timestamp,
+
+ /**
+ * End date for withdrawing.
+ */
+ val stamp_expire_withdraw: Timestamp,
+
+ /**
+ * Expiration date after which the exchange can forget about
+ * the currency.
+ */
+ val stamp_expire_legal: Timestamp,
+
+ /**
+ * Date after which the coins of this denomination can't be
+ * deposited anymore.
+ */
+ val stamp_expire_deposit: Timestamp,
+
+ /**
+ * Signature over the denomination information by the exchange's master
+ * signing key.
+ */
+ val master_sig: String
+) {
+ fun toDenominationRecord(
+ baseUrl: String,
+ denomPubHash: ByteArray,
+ isOffered: Boolean,
+ isRevoked: Boolean,
+ status: DenominationStatus
+ ): DenominationRecord = DenominationRecord(
+ denomPub = denom_pub,
+ denomPubHash = Base32Crockford.encode(denomPubHash),
+ exchangeBaseUrl = baseUrl,
+ feeDeposit = fee_deposit,
+ feeRefresh = fee_refresh,
+ feeRefund = fee_refund,
+ feeWithdraw = fee_withdraw,
+ isOffered = isOffered,
+ isRevoked = isRevoked,
+ masterSig = master_sig,
+ stampExpireDeposit = stamp_expire_deposit,
+ stampExpireLegal = stamp_expire_legal,
+ stampExpireWithdraw = stamp_expire_withdraw,
+ stampStart = stamp_start,
+ status = status,
+ value = value
+ )
+}
+
+/**
+ * Element of the payback list that the
+ * exchange gives us in /keys.
+ */
+@Serializable
+data class Recoup(
+ /**
+ * The hash of the denomination public key for which the payback is
offered.
+ */
+ val h_denom_pub: String
+)
diff --git
a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
index b002e32..f7064bf 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
@@ -17,17 +17,14 @@
package net.taler.wallet.kotlin.operations
import io.ktor.client.HttpClient
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.request.get
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.taler.wallet.kotlin.Amount
import net.taler.wallet.kotlin.TalerUri.parseWithdrawUri
+import net.taler.wallet.kotlin.getDefaultHttpClient
-class Withdraw(
- private val httpClient: HttpClient = HttpClient { install(JsonFeature) {
serializer = KotlinxSerializer() } }
-) {
+class Withdraw(private val httpClient: HttpClient = getDefaultHttpClient()) {
data class BankDetails(
val amount: Amount,
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
new file mode 100644
index 0000000..7acc2a5
--- /dev/null
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
@@ -0,0 +1,67 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin
+
+import net.taler.wallet.kotlin.exchange.ExchangeRecord
+import net.taler.wallet.kotlin.exchange.ExchangeUpdateReason.Initial
+import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchKeys
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class DbTest {
+
+ private val dbFactory = DbFactory()
+
+ private val exchange1 = ExchangeRecord(
+ baseUrl = "https://example1.org/",
+ timestampAdded = Timestamp.now(),
+ updateStatus = FetchKeys,
+ updateStarted = Timestamp.now(),
+ updateReason = Initial
+ )
+ private val exchange2 = ExchangeRecord(
+ baseUrl = "https://example2.org/",
+ timestampAdded = Timestamp.now(),
+ updateStatus = FetchKeys,
+ updateStarted = Timestamp.now(),
+ updateReason = Initial
+ )
+
+ @Test
+ fun test() = runCoroutine {
+ val db = dbFactory.openDb()
+ var exchanges = db.listExchanges()
+ assertEquals(0, exchanges.size)
+
+ db.put(exchange1)
+ exchanges = db.listExchanges()
+ assertEquals(1, exchanges.size)
+ assertEquals(exchange1, exchanges[0])
+
+ db.put(exchange2)
+ exchanges = db.listExchanges()
+ assertEquals(2, exchanges.size)
+ assertEquals(exchange1, db.getExchangeByBaseUrl(exchange1.baseUrl))
+ assertEquals(exchange2, db.getExchangeByBaseUrl(exchange2.baseUrl))
+
+ db.deleteExchangeByBaseUrl(exchange1.baseUrl)
+ exchanges = db.listExchanges()
+ assertEquals(1, exchanges.size)
+ assertEquals(exchange2, exchanges[0])
+ }
+
+}
\ No newline at end of file
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
index 857c56c..72e4b4b 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
@@ -22,6 +22,7 @@ import io.ktor.client.engine.mock.MockEngineConfig
import io.ktor.client.engine.mock.respond
import io.ktor.client.engine.mock.respondError
import io.ktor.client.features.json.JsonFeature
+import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.http.ContentType.Application
import io.ktor.http.HttpStatusCode.Companion.InternalServerError
import io.ktor.http.Url
@@ -29,14 +30,25 @@ import io.ktor.http.fullPath
import io.ktor.http.headersOf
import io.ktor.http.hostWithPort
import kotlinx.coroutines.CoroutineScope
+import kotlinx.serialization.json.Json
+
+enum class PlatformTarget {
+ ANDROID,
+ JS,
+ NATIVE_LINUX,
+}
/**
* Workaround to use suspending functions in unit tests
*/
expect fun runCoroutine(block: suspend (scope: CoroutineScope) -> Unit)
+expect fun getPlatformTarget(): PlatformTarget
+
fun getMockHttpClient(): HttpClient = HttpClient(MockEngine) {
- install(JsonFeature)
+ install(JsonFeature) {
+ serializer = KotlinxSerializer(Json(getJsonConfiguration()))
+ }
engine {
addHandler { error("No test handler added") }
}
diff --git
a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt
index 48cbc8d..02d9b1d 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt
@@ -18,12 +18,12 @@ package net.taler.wallet.kotlin.crypto
import net.taler.wallet.kotlin.Amount
import net.taler.wallet.kotlin.Base32Crockford
-import net.taler.wallet.kotlin.DenominationRecord
-import net.taler.wallet.kotlin.DenominationStatus.Unverified
-import net.taler.wallet.kotlin.DenominationStatus.VerifiedBad
import net.taler.wallet.kotlin.Timestamp
import net.taler.wallet.kotlin.WireFee
import net.taler.wallet.kotlin.crypto.Signature.PurposeBuilder
+import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git
a/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt
new file mode 100644
index 0000000..e7a2c48
--- /dev/null
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt
@@ -0,0 +1,317 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin.exchange
+
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.getMockHttpClient
+import net.taler.wallet.kotlin.giveJsonResponse
+import net.taler.wallet.kotlin.runCoroutine
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ExchangeTest {
+
+ private val httpClient = getMockHttpClient()
+ private val exchange = Exchange(httpClient = httpClient)
+
+ @Test
+ fun testFetchKeys() {
+ val expectedKeys = Keys(
+ denoms = listOf(
+ Denomination(
+ value = Amount.fromJSONString("TESTKUDOS:5"),
+ denom_pub =
"040000Z9TH9RPTA1BXF6Z89HM7JGXTPD5G8NNBWQWF7RWQGNAATN84QBWME1TGSWZ79WPQ62S2W2VHG2XBH66JDJ0KM8Q2FQ3FGBZQGNJVFNA9F66E6S3P36KTMWMKWDXWM9EX1YHSHQ841AHRR8JVDY96CZ13AJF6JW95K59AE8CSTH5ZS9NVS0102X92GK8JW2QX2S4EE25QNHK6XMXH3944QMXPFS7SFCMV623BM62VNPVX8JM424YXPJ09TXZAH2CF3QM5HDVRSTDRDGVBF6KZVRFM852TMVMYPVGFA9YQF6HWNJ8H5VCQ3Z9WWNMQ3T76X4F1P6W2J266K8B3W9HKW2WJNK3XHRAVC4GCF07TC0ZNAT0EDAAKV429YAXWSK952BPTY98GVP5XZQG2SE0Q5CF3PV04002",
+ fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"),
+ stamp_start = Timestamp(1582484881000),
+ stamp_expire_withdraw = Timestamp(1677092881000),
+ stamp_expire_legal = Timestamp(1897844881000),
+ stamp_expire_deposit = Timestamp(1740164881000),
+ master_sig =
"2SDD44VVBD52XEV0A9R878BC60J51VKK0H5ZS6CPJ7Z738A8V4KPXCF70KFZAY2567400C2GEWNNVXF6PYD7HKX3D2M63WCNPJSE010"
+ ),
+ Denomination(
+ value = Amount.fromJSONString("TESTKUDOS:2"),
+ denom_pub =
"040000XV91V0M7H906Y7R371YX2XAK1V5B2TRFD8ZM9WYJ495TP08NCVEDNFXS2KZBJR4808VZ52PNNQSYVQ2T3J7867MZQY1QZ9N8YQWQWCKSYAY8A07E5SYAK0G0KRTCN5VZ7JXE2YCNT7Q3RT9TGAZBSK5V1ZRRK6HX4C1YFKPWWP4TBVJ8DJMS43WKR4CR4S9T02YXVGR6GSDMR7GHBD89JHCEQ8V2K58Y5XVDGRRQYNBG9Q5XWDMV7GKN24JCPCEKSZZP5XYPXYJX2Z2JZ179M9FQV0PEWFJ4DP7AP14XE54FH97YP9398KA31ECVDY7PHMKMZ6E79Z9FSCXH3WSCXMHBWFGWPRG5ZG1P6HR71VKH4F0Y998JNE4G40JH2VSXP3035AR7HAMJGB56CYHH60EWD904002",
+ fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"),
+ stamp_start = Timestamp(1582484881000),
+ stamp_expire_withdraw = Timestamp(1677092881000),
+ stamp_expire_legal = Timestamp(1897844881000),
+ stamp_expire_deposit = Timestamp(1740164881000),
+ master_sig =
"WX1RDTW1W1K7FPFQQ9MCJ8V5CJP8ZX2SGHQG2YS34BY8AMSR3YQ92HE2HT1JMP4W06J63RZ0BR2MKDBX54GV6QKD4R00N9HXN2S283R"
+ ),
+ Denomination(
+ value = Amount.fromJSONString("TESTKUDOS:1"),
+ denom_pub =
"040000XCBJE9TDDZATYSDR51D0DKMY5NW8FMJ8YQ1Y4F40SPPTKFMD3FWH38NSQZ1YB621TCFH5RBN5J3SFR5SG4789G27FA90E605GG9AXYTXXPJ9HYPAVAVS6V4XCSC17HKX2M2NSX5D0PPETDGKQD04G498VS36YY4WTB5SYG4SV9MKPVZ5WG2WNP3MA77TFZSHK5HBHZBEW0S1TRKGSCDNBRHYB240M84YM1Y7EJ7BXKJK4GRR1GS16DJ2RA1YEQ1AAXH0GP6RRAEJE8D2JFSH05P3KR1GB97NMX6VD8DCAX45416F888EYQR4M6R820FJVZ6FYV9CCMZ3M10B64N6G4QFNKFNAV2ENPVVG4A3R0AAA6STJ7E5Z05GEKW35SHM14HY9CEGM7D1ZEKHZJYA9P6WH504002",
+ fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"),
+ stamp_start = Timestamp(1582484881000),
+ stamp_expire_withdraw = Timestamp(1677092881000),
+ stamp_expire_legal = Timestamp(1897844881000),
+ stamp_expire_deposit = Timestamp(1740164881000),
+ master_sig =
"65G9FWQPA4YKJEM7D37079D4MY81D47KD1280RG7BRH85XZQ2N13FJPV9N8AEASK9CTGNX1HKX0GTRBJ5C49H4YRY0E4CYVPNH06W18"
+ ),
+ Denomination(
+ value = Amount.fromJSONString("TESTKUDOS:10"),
+ denom_pub =
"040000XZDZK4BPPPXR7MYKK2AF4WF95EH3VF8WEX7WDX4HEWXSB5XX10N4V5RHFSK0TSBKNC9CRNVGK3WJ42S3Z9SB4Q3M4DQQ7DKCGKED6WBKENHT8JX51K1VR5JKCMAFBNM6DR5MNRGKFC2MDRQ0Y4BCXHKEMRD65C6JPBKYW9HJH66FGT22WMBV0AV7P60CKR13MQG6FKWW3TZW3XXHVY2VX9MJN6VQFPS6NQGGTNXZV2SK2X5MJAJME7RN9BNZ5ZBTW1CYMVCHBSVGBFPRC68W78PW44VP402VD12KG2AWKPD4DRBAA85HM1DN1KADYQ498QHYGEB3T3HH990HRV8PSNBGYCHB87JTVYMJ4N2PSP2FCX0H6FRTW1FQY05EB7D8BFXM95DNRCHVQSHBZ9RP7NZFA304002",
+ fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"),
+ stamp_start = Timestamp(1582484881000),
+ stamp_expire_withdraw = Timestamp(1677092881000),
+ stamp_expire_legal = Timestamp(1897844881000),
+ stamp_expire_deposit = Timestamp(1740164881000),
+ master_sig =
"M423J7CJPACTPBYCFVR87B44JAJKAB2ME8C263WGHJSA8V8444SX428MVC9NF4GD08CKS9HY0WB4B8SEZ3HJFWKXNSH80RBJXQC822G"
+ ),
+ Denomination(
+ value = Amount.fromJSONString("TESTKUDOS:0.1"),
+ denom_pub =
"040000YKYFF6GX979JS10MEZ16BQ7TT6XBTE0TBX6VJ9KSG7K4D91SWJVDETNKQJXAFK9SAB3S31FZFA0Y0X22TKRXKCT7Z4GZCCRJJ12T1A5M4DWRTZDFRD3FE495NXHVPFM96KXMKH1HABTDDFZN0NWQ3NBJ6GNXD40NJ95E955X948JHBDJZWM3TEAK4XFJX8056XFDHVNXSF4VN14RR1WD1J5K7JPS61SKRNF3HT6NZA823PZW2KPV2KVBMMP615A922ZNJGVQDTW5TYWTK5DCBGG1YEKQRYF39NX9X722FZK98BTMHHH6WZFCKBT096G9BKSHSJW3VE8KKPCN8XGWYYPD3158HRKSA28BJQ9XJVVB6FDCGZ154WWGGSGW82BDYDH7ZHJBMS046AG0ND4ZCVR2JQ04002",
+ fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"),
+ stamp_start = Timestamp(1582484881000),
+ stamp_expire_withdraw = Timestamp(1677092881000),
+ stamp_expire_legal = Timestamp(1897844881000),
+ stamp_expire_deposit = Timestamp(1740164881000),
+ master_sig =
"RKZKGR0KYKY0VZ26339DZKV8EZJ2HRRQMFSAJDHBG3YHEQNZFHKM8WPYCH9WHXTWBB10GQN9QJKFDJJF2H6D5FT801GF87G153PTJ18"
+ ),
+ Denomination(
+ value = Amount.fromJSONString("TESTKUDOS:1000"),
+ denom_pub =
"040000Y9PBY1HPBDD4KSK9PBA86TRY13JQ4284T4E0G4SEREQ8YM88PZHKW1ACKT1RTWVTBXX83G54NFVYRJQX9PTDXDJ1CXSS42G8NYMW97NA6NNNASV69W1JX39NTS1NVKXPW4WMBASATSNBTXHRT92FFN2NAJFGK876BNN3TPTH57C76ADAQV43VFF7CYAWWNYZAYGQQ1XY1NK34FJD778VFGYCZ1G9J8XPNB92ZKJBZEZKSNBRNH27GM5A736AFSGP7B4JSCGD0F4FMD1PDVB26MM9ZK8C1TDKXQ5DJ09AQQ55P7Q3A133ASPGBH6SCJTJYH8C9A451B0SP4GDX2ZFRSX5FP93PY4VKEB36KCAQ5E2MRZNWFB6T0JK0W7Z7NXP5FW2VQ4PNV7B2NQ3WFMCVRSDSV04002",
+ fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"),
+ fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"),
+ stamp_start = Timestamp(1582484881000),
+ stamp_expire_withdraw = Timestamp(1677092881000),
+ stamp_expire_legal = Timestamp(1897844881000),
+ stamp_expire_deposit = Timestamp(1740164881000),
+ master_sig =
"FJPQKECRKVQSTB9Y983KDGD65Z1JHQKNSCC6YPMBN3Z4VW0AGC5MQM9BPB0YYD1SCMETPD6QB4X80HWE0ZDGWNZB1KND5TP567T4G3G"
+ )
+ ),
+ master_public_key =
"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG",
+ auditors = emptyList(),
+ list_issue_date = Timestamp(1592161681000),
+ recoup = emptyList(),
+ signkeys = listOf(
+ SigningKey(
+ stamp_start = Timestamp(1592161681000),
+ stamp_expire = Timestamp(1594580881000),
+ stamp_end = Timestamp(1655233681000),
+ key =
"0FMRBH8FZYYMSQ2RHTYYGK2BV33JVSW6MTYCV7Y833GVNXFDYK10",
+ master_sig =
"368HV41Z4FNDXQ7EP6TNAMBSKP44PJAZW27EPH7XJNVG2A6HZQM7ZPMCB6B30HG50S95YD1K2BAJVPEYMGF2DR7EEY0NFBQZZ1B8P1G"
+ ),
+ SigningKey(
+ stamp_start = Timestamp(1594580881000),
+ stamp_expire = Timestamp(1597000081000),
+ stamp_end = Timestamp(1657652881000),
+ key =
"XMNYM62DQW0XDQACCYDMFTM5GY7SZST60NH7XS9GY18H8Q9N7QN0",
+ master_sig =
"4HRJN36VVJ87ZC2HZXP7QDSZN30YQE8FCNWZS3RCA1HGNY9Q0JPMVJZ79RDHKS4GYXV29PM27DGCN0VB0BCZFF2FC6FMF3A6ZNKC238"
+ )
+ ),
+ version = "7:0:0"
+ )
+
+ httpClient.giveJsonResponse("https://exchange.test.taler.net/keys") {
+ """{
+ "version": "7:0:0",
+ "master_public_key":
"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG",
+ "reserve_closing_delay": {
+ "d_ms": 2419200000
+ },
+ "signkeys": [
+ {
+ "stamp_start": {
+ "t_ms": 1592161681000
+ },
+ "stamp_expire": {
+ "t_ms": 1594580881000
+ },
+ "stamp_end": {
+ "t_ms": 1655233681000
+ },
+ "master_sig":
"368HV41Z4FNDXQ7EP6TNAMBSKP44PJAZW27EPH7XJNVG2A6HZQM7ZPMCB6B30HG50S95YD1K2BAJVPEYMGF2DR7EEY0NFBQZZ1B8P1G",
+ "key": "0FMRBH8FZYYMSQ2RHTYYGK2BV33JVSW6MTYCV7Y833GVNXFDYK10"
+ },
+ {
+ "stamp_start": {
+ "t_ms": 1594580881000
+ },
+ "stamp_expire": {
+ "t_ms": 1597000081000
+ },
+ "stamp_end": {
+ "t_ms": 1657652881000
+ },
+ "master_sig":
"4HRJN36VVJ87ZC2HZXP7QDSZN30YQE8FCNWZS3RCA1HGNY9Q0JPMVJZ79RDHKS4GYXV29PM27DGCN0VB0BCZFF2FC6FMF3A6ZNKC238",
+ "key": "XMNYM62DQW0XDQACCYDMFTM5GY7SZST60NH7XS9GY18H8Q9N7QN0"
+ }
+ ],
+ "recoup": [],
+ "denoms": [
+ {
+ "master_sig":
"2SDD44VVBD52XEV0A9R878BC60J51VKK0H5ZS6CPJ7Z738A8V4KPXCF70KFZAY2567400C2GEWNNVXF6PYD7HKX3D2M63WCNPJSE010",
+ "stamp_start": {
+ "t_ms": 1582484881000
+ },
+ "stamp_expire_withdraw": {
+ "t_ms": 1677092881000
+ },
+ "stamp_expire_deposit": {
+ "t_ms": 1740164881000
+ },
+ "stamp_expire_legal": {
+ "t_ms": 1897844881000
+ },
+ "denom_pub":
"040000Z9TH9RPTA1BXF6Z89HM7JGXTPD5G8NNBWQWF7RWQGNAATN84QBWME1TGSWZ79WPQ62S2W2VHG2XBH66JDJ0KM8Q2FQ3FGBZQGNJVFNA9F66E6S3P36KTMWMKWDXWM9EX1YHSHQ841AHRR8JVDY96CZ13AJF6JW95K59AE8CSTH5ZS9NVS0102X92GK8JW2QX2S4EE25QNHK6XMXH3944QMXPFS7SFCMV623BM62VNPVX8JM424YXPJ09TXZAH2CF3QM5HDVRSTDRDGVBF6KZVRFM852TMVMYPVGFA9YQF6HWNJ8H5VCQ3Z9WWNMQ3T76X4F1P6W2J266K8B3W9HKW2WJNK3XHRAVC4GCF07TC0ZNAT0EDAAKV429YAXWSK952BPTY98GVP5XZQG2SE0Q5CF3PV04002",
+ "value": "TESTKUDOS:5",
+ "fee_withdraw": "TESTKUDOS:0.01",
+ "fee_deposit": "TESTKUDOS:0.01",
+ "fee_refresh": "TESTKUDOS:0.01",
+ "fee_refund": "TESTKUDOS:0.01"
+ },
+ {
+ "master_sig":
"WX1RDTW1W1K7FPFQQ9MCJ8V5CJP8ZX2SGHQG2YS34BY8AMSR3YQ92HE2HT1JMP4W06J63RZ0BR2MKDBX54GV6QKD4R00N9HXN2S283R",
+ "stamp_start": {
+ "t_ms": 1582484881000
+ },
+ "stamp_expire_withdraw": {
+ "t_ms": 1677092881000
+ },
+ "stamp_expire_deposit": {
+ "t_ms": 1740164881000
+ },
+ "stamp_expire_legal": {
+ "t_ms": 1897844881000
+ },
+ "denom_pub":
"040000XV91V0M7H906Y7R371YX2XAK1V5B2TRFD8ZM9WYJ495TP08NCVEDNFXS2KZBJR4808VZ52PNNQSYVQ2T3J7867MZQY1QZ9N8YQWQWCKSYAY8A07E5SYAK0G0KRTCN5VZ7JXE2YCNT7Q3RT9TGAZBSK5V1ZRRK6HX4C1YFKPWWP4TBVJ8DJMS43WKR4CR4S9T02YXVGR6GSDMR7GHBD89JHCEQ8V2K58Y5XVDGRRQYNBG9Q5XWDMV7GKN24JCPCEKSZZP5XYPXYJX2Z2JZ179M9FQV0PEWFJ4DP7AP14XE54FH97YP9398KA31ECVDY7PHMKMZ6E79Z9FSCXH3WSCXMHBWFGWPRG5ZG1P6HR71VKH4F0Y998JNE4G40JH2VSXP3035AR7HAMJGB56CYHH60EWD904002",
+ "value": "TESTKUDOS:2",
+ "fee_withdraw": "TESTKUDOS:0.01",
+ "fee_deposit": "TESTKUDOS:0.01",
+ "fee_refresh": "TESTKUDOS:0.01",
+ "fee_refund": "TESTKUDOS:0.01"
+ },
+ {
+ "master_sig":
"65G9FWQPA4YKJEM7D37079D4MY81D47KD1280RG7BRH85XZQ2N13FJPV9N8AEASK9CTGNX1HKX0GTRBJ5C49H4YRY0E4CYVPNH06W18",
+ "stamp_start": {
+ "t_ms": 1582484881000
+ },
+ "stamp_expire_withdraw": {
+ "t_ms": 1677092881000
+ },
+ "stamp_expire_deposit": {
+ "t_ms": 1740164881000
+ },
+ "stamp_expire_legal": {
+ "t_ms": 1897844881000
+ },
+ "denom_pub":
"040000XCBJE9TDDZATYSDR51D0DKMY5NW8FMJ8YQ1Y4F40SPPTKFMD3FWH38NSQZ1YB621TCFH5RBN5J3SFR5SG4789G27FA90E605GG9AXYTXXPJ9HYPAVAVS6V4XCSC17HKX2M2NSX5D0PPETDGKQD04G498VS36YY4WTB5SYG4SV9MKPVZ5WG2WNP3MA77TFZSHK5HBHZBEW0S1TRKGSCDNBRHYB240M84YM1Y7EJ7BXKJK4GRR1GS16DJ2RA1YEQ1AAXH0GP6RRAEJE8D2JFSH05P3KR1GB97NMX6VD8DCAX45416F888EYQR4M6R820FJVZ6FYV9CCMZ3M10B64N6G4QFNKFNAV2ENPVVG4A3R0AAA6STJ7E5Z05GEKW35SHM14HY9CEGM7D1ZEKHZJYA9P6WH504002",
+ "value": "TESTKUDOS:1",
+ "fee_withdraw": "TESTKUDOS:0.01",
+ "fee_deposit": "TESTKUDOS:0.01",
+ "fee_refresh": "TESTKUDOS:0.01",
+ "fee_refund": "TESTKUDOS:0.01"
+ },
+ {
+ "master_sig":
"M423J7CJPACTPBYCFVR87B44JAJKAB2ME8C263WGHJSA8V8444SX428MVC9NF4GD08CKS9HY0WB4B8SEZ3HJFWKXNSH80RBJXQC822G",
+ "stamp_start": {
+ "t_ms": 1582484881000
+ },
+ "stamp_expire_withdraw": {
+ "t_ms": 1677092881000
+ },
+ "stamp_expire_deposit": {
+ "t_ms": 1740164881000
+ },
+ "stamp_expire_legal": {
+ "t_ms": 1897844881000
+ },
+ "denom_pub":
"040000XZDZK4BPPPXR7MYKK2AF4WF95EH3VF8WEX7WDX4HEWXSB5XX10N4V5RHFSK0TSBKNC9CRNVGK3WJ42S3Z9SB4Q3M4DQQ7DKCGKED6WBKENHT8JX51K1VR5JKCMAFBNM6DR5MNRGKFC2MDRQ0Y4BCXHKEMRD65C6JPBKYW9HJH66FGT22WMBV0AV7P60CKR13MQG6FKWW3TZW3XXHVY2VX9MJN6VQFPS6NQGGTNXZV2SK2X5MJAJME7RN9BNZ5ZBTW1CYMVCHBSVGBFPRC68W78PW44VP402VD12KG2AWKPD4DRBAA85HM1DN1KADYQ498QHYGEB3T3HH990HRV8PSNBGYCHB87JTVYMJ4N2PSP2FCX0H6FRTW1FQY05EB7D8BFXM95DNRCHVQSHBZ9RP7NZFA304002",
+ "value": "TESTKUDOS:10",
+ "fee_withdraw": "TESTKUDOS:0.01",
+ "fee_deposit": "TESTKUDOS:0.01",
+ "fee_refresh": "TESTKUDOS:0.01",
+ "fee_refund": "TESTKUDOS:0.01"
+ },
+ {
+ "master_sig":
"RKZKGR0KYKY0VZ26339DZKV8EZJ2HRRQMFSAJDHBG3YHEQNZFHKM8WPYCH9WHXTWBB10GQN9QJKFDJJF2H6D5FT801GF87G153PTJ18",
+ "stamp_start": {
+ "t_ms": 1582484881000
+ },
+ "stamp_expire_withdraw": {
+ "t_ms": 1677092881000
+ },
+ "stamp_expire_deposit": {
+ "t_ms": 1740164881000
+ },
+ "stamp_expire_legal": {
+ "t_ms": 1897844881000
+ },
+ "denom_pub":
"040000YKYFF6GX979JS10MEZ16BQ7TT6XBTE0TBX6VJ9KSG7K4D91SWJVDETNKQJXAFK9SAB3S31FZFA0Y0X22TKRXKCT7Z4GZCCRJJ12T1A5M4DWRTZDFRD3FE495NXHVPFM96KXMKH1HABTDDFZN0NWQ3NBJ6GNXD40NJ95E955X948JHBDJZWM3TEAK4XFJX8056XFDHVNXSF4VN14RR1WD1J5K7JPS61SKRNF3HT6NZA823PZW2KPV2KVBMMP615A922ZNJGVQDTW5TYWTK5DCBGG1YEKQRYF39NX9X722FZK98BTMHHH6WZFCKBT096G9BKSHSJW3VE8KKPCN8XGWYYPD3158HRKSA28BJQ9XJVVB6FDCGZ154WWGGSGW82BDYDH7ZHJBMS046AG0ND4ZCVR2JQ04002",
+ "value": "TESTKUDOS:0.1",
+ "fee_withdraw": "TESTKUDOS:0.01",
+ "fee_deposit": "TESTKUDOS:0.01",
+ "fee_refresh": "TESTKUDOS:0.01",
+ "fee_refund": "TESTKUDOS:0.01"
+ },
+ {
+ "master_sig":
"FJPQKECRKVQSTB9Y983KDGD65Z1JHQKNSCC6YPMBN3Z4VW0AGC5MQM9BPB0YYD1SCMETPD6QB4X80HWE0ZDGWNZB1KND5TP567T4G3G",
+ "stamp_start": {
+ "t_ms": 1582484881000
+ },
+ "stamp_expire_withdraw": {
+ "t_ms": 1677092881000
+ },
+ "stamp_expire_deposit": {
+ "t_ms": 1740164881000
+ },
+ "stamp_expire_legal": {
+ "t_ms": 1897844881000
+ },
+ "denom_pub":
"040000Y9PBY1HPBDD4KSK9PBA86TRY13JQ4284T4E0G4SEREQ8YM88PZHKW1ACKT1RTWVTBXX83G54NFVYRJQX9PTDXDJ1CXSS42G8NYMW97NA6NNNASV69W1JX39NTS1NVKXPW4WMBASATSNBTXHRT92FFN2NAJFGK876BNN3TPTH57C76ADAQV43VFF7CYAWWNYZAYGQQ1XY1NK34FJD778VFGYCZ1G9J8XPNB92ZKJBZEZKSNBRNH27GM5A736AFSGP7B4JSCGD0F4FMD1PDVB26MM9ZK8C1TDKXQ5DJ09AQQ55P7Q3A133ASPGBH6SCJTJYH8C9A451B0SP4GDX2ZFRSX5FP93PY4VKEB36KCAQ5E2MRZNWFB6T0JK0W7Z7NXP5FW2VQ4PNV7B2NQ3WFMCVRSDSV04002",
+ "value": "TESTKUDOS:1000",
+ "fee_withdraw": "TESTKUDOS:0.01",
+ "fee_deposit": "TESTKUDOS:0.01",
+ "fee_refresh": "TESTKUDOS:0.01",
+ "fee_refund": "TESTKUDOS:0.01"
+ }
+ ],
+ "auditors": [],
+ "list_issue_date": {
+ "t_ms": 1592161681000
+ },
+ "eddsa_pub":
"0FMRBH8FZYYMSQ2RHTYYGK2BV33JVSW6MTYCV7Y833GVNXFDYK10",
+ "eddsa_sig":
"2GB384567SZM9CM7RJT51N04D2ZK7NAHWZRT6BA0FFNXTAB71D4T1WVQTXZEPDM07X1MJ46ZBC189SCM4EG4V8TQJRP2WAZCKPAJJ2R"
+ }""".trimIndent()
+ }
+ runCoroutine {
+ val keys = exchange.fetchKeys("https://exchange.test.taler.net/")
+ assertEquals(expectedKeys, keys)
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
b/src/jsMain/kotlin/net/taler/wallet/kotlin/Db.kt
similarity index 80%
copy from src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
copy to src/jsMain/kotlin/net/taler/wallet/kotlin/Db.kt
index 524da15..45cbfc3 100644
--- a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
+++ b/src/jsMain/kotlin/net/taler/wallet/kotlin/Db.kt
@@ -16,7 +16,8 @@
package net.taler.wallet.kotlin
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.runBlocking
-
-actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit) =
runBlocking { block(this) }
+internal actual class DbFactory {
+ actual fun openDb(): Db {
+ return FakeDb()
+ }
+}
diff --git a/src/jsTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
b/src/jsTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
similarity index 93%
rename from src/jsTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
rename to src/jsTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
index da5a183..49466e0 100644
--- a/src/jsTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
+++ b/src/jsTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
@@ -21,3 +21,5 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.promise
actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit):
dynamic = GlobalScope.promise { block(this) }
+
+actual fun getPlatformTarget(): PlatformTarget = PlatformTarget.JS
diff --git a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
b/src/linuxMain/kotlin/net/taler/wallet/kotlin/Db.kt
similarity index 80%
rename from src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
rename to src/linuxMain/kotlin/net/taler/wallet/kotlin/Db.kt
index 524da15..45cbfc3 100644
--- a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
+++ b/src/linuxMain/kotlin/net/taler/wallet/kotlin/Db.kt
@@ -16,7 +16,8 @@
package net.taler.wallet.kotlin
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.runBlocking
-
-actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit) =
runBlocking { block(this) }
+internal actual class DbFactory {
+ actual fun openDb(): Db {
+ return FakeDb()
+ }
+}
diff --git a/src/androidTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
b/src/linuxTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
similarity index 92%
rename from src/androidTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
rename to src/linuxTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
index 524da15..162ce4e 100644
--- a/src/androidTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt
+++ b/src/linuxTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt
@@ -20,3 +20,5 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit) =
runBlocking { block(this) }
+
+actual fun getPlatformTarget(): PlatformTarget = PlatformTarget.NATIVE_LINUX
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.