libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 54fee83cebba3194fb637efef9456cad784c4c20
parent b89fb9552dc20d9f03a855f26d64aee3003a2e43
Author: Antoine A <>
Date:   Wed,  8 Oct 2025 15:08:47 +0200

common: optimize and clean taler timestamps logic

Diffstat:
Dbank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt | 77-----------------------------------------------------------------------------
Mbank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 14+++++++-------
Mbank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt | 4++--
Mbank/src/main/kotlin/tech/libeufin/bank/api/WireGatewayApi.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt | 6+++---
Mbank/src/test/kotlin/CoreBankApiTest.kt | 2+-
Mbank/src/test/kotlin/JsonTest.kt | 20++++++++++----------
Mcommon/src/main/kotlin/TalerCommon.kt | 126++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mcommon/src/main/kotlin/TalerMessage.kt | 20++++++++++----------
Mcommon/src/main/kotlin/db/types.kt | 4++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt | 2+-
13 files changed, 132 insertions(+), 149 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt @@ -1,76 +0,0 @@ -/* - * This file is part of LibEuFin. - * Copyright (C) 2023-2024 Taler Systems S.A. - - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - - * LibEuFin 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 Affero General - * Public License for more details. - - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -package tech.libeufin.bank - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.longOrNull -import tech.libeufin.common.badRequest -import java.time.Duration -import java.time.temporal.ChronoUnit -import java.util.concurrent.TimeUnit - -/** - * Internal representation of relative times. The - * "forever" case is represented with Long.MAX_VALUE. - */ -@Serializable -data class RelativeTime( - @Serializable(with = Serializer::class) - val d_us: Duration -) { - internal object Serializer : KSerializer<Duration> { - override fun serialize(encoder: Encoder, value: Duration) { - if (value == ChronoUnit.FOREVER.duration) { - encoder.encodeString("forever") - } else { - encoder.encodeLong(TimeUnit.MICROSECONDS.convert(value)) - } - } - - override fun deserialize(decoder: Decoder): Duration { - val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") - val maybeDUs = jsonInput.decodeJsonElement().jsonPrimitive - if (maybeDUs.isString) { - if (maybeDUs.content != "forever") throw badRequest("Only 'forever' allowed for d_us as string, but '${maybeDUs.content}' was found") - return ChronoUnit.FOREVER.duration - } - val dUs: Long = maybeDUs.longOrNull - ?: throw badRequest("Could not convert d_us: '${maybeDUs.content}' to a number") - when { - dUs < 0 -> throw badRequest("Negative duration specified.") - dUs > MAX_SAFE_INTEGER -> throw badRequest("d_us value $dUs exceed cap (2^53-1)") - else -> return Duration.of(dUs, ChronoUnit.MICROS) - } - } - - override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor - } - - companion object { - const val MAX_SAFE_INTEGER = 9007199254740991L // 2^53 - 1 - } -} -\ No newline at end of file diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -158,7 +158,7 @@ data class TanTransmission( @Serializable data class TokenSuccessResponse( val access_token: String, - val expiration: TalerProtocolTimestamp + val expiration: TalerTimestamp ) @@ -322,12 +322,12 @@ data class BearerToken( @Serializable data class TokenInfo( - val creation_time: TalerProtocolTimestamp, - val expiration: TalerProtocolTimestamp, + val creation_time: TalerTimestamp, + val expiration: TalerTimestamp, val scope: TokenScope, val isRefreshable: Boolean, val description: String? = null, - val last_access: TalerProtocolTimestamp, + val last_access: TalerTimestamp, val row_id: Long, val token_id: Long ) @@ -460,7 +460,7 @@ data class BankAccountTransactionInfo( val direction: TransactionDirection, val subject: String, val row_id: Long, // is T_ID - val date: TalerProtocolTimestamp + val date: TalerTimestamp ) // Response type for histories, namely GET /transactions @@ -593,8 +593,8 @@ data class CashoutStatusResponse( val amount_debit: TalerAmount, val amount_credit: TalerAmount, val subject: String, - val creation_time: TalerProtocolTimestamp, - val confirmation_time: TalerProtocolTimestamp? = null, + val creation_time: TalerTimestamp, + val confirmation_time: TalerTimestamp? = null, val tan_channel: TanChannel? = null, val tan_info: String? = null ) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt @@ -106,7 +106,7 @@ private fun Routing.coreBankTokenApi(db: Database, cfg: BankConfig) { ) } val token = Base32Crockford32B.secureRand() - val tokenDuration: Duration = req.duration?.d_us ?: TOKEN_DEFAULT_DURATION + val tokenDuration: Duration = req.duration?.duration ?: TOKEN_DEFAULT_DURATION val creationTime = Instant.now() val expirationTimestamp = @@ -133,7 +133,7 @@ private fun Routing.coreBankTokenApi(db: Database, cfg: BankConfig) { TokenCreationResult.Success -> call.respond( TokenSuccessResponse( access_token = "$TOKEN_PREFIX$token", - expiration = TalerProtocolTimestamp(t_s = expirationTimestamp) + expiration = TalerTimestamp(expirationTimestamp) ) ) } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/WireGatewayApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/WireGatewayApi.kt @@ -193,7 +193,7 @@ fun Routing.wireGatewayApi(db: Database, cfg: BankConfig) { ) is AddIncomingResult.Success -> this.respond( AddIncomingResponse( - timestamp = TalerProtocolTimestamp(timestamp), + timestamp = TalerTimestamp(timestamp), row_id = res.id ) ) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt @@ -119,7 +119,7 @@ class CashoutDAO(private val db: Database) { creation_time = it.getTalerTimestamp("creation_time"), confirmation_time = when (val timestamp = it.getLong("confirmation_date")) { 0L -> null - else -> TalerProtocolTimestamp(timestamp.asInstant()) + else -> TalerTimestamp(timestamp.asInstant()) }, tan_channel = it.getOptEnum<TanChannel>("tan_channel"), tan_info = it.getString("tan_info"), diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt @@ -99,7 +99,7 @@ class ExchangeDAO(private val db: Database) { /** Result of taler transfer transaction creation */ sealed interface TransferResult { /** Transaction [id] and wire transfer [timestamp] */ - data class Success(val id: Long, val timestamp: TalerProtocolTimestamp): TransferResult + data class Success(val id: Long, val timestamp: TalerTimestamp): TransferResult data object NotAnExchange: TransferResult data object UnknownExchange: TransferResult data object BothPartyAreExchange: TransferResult @@ -240,7 +240,7 @@ class ExchangeDAO(private val db: Database) { /** Result of taler add incoming transaction creation */ sealed interface AddIncomingResult { /** Transaction [id] and wire transfer [timestamp] */ - data class Success(val id: Long, val timestamp: TalerProtocolTimestamp): AddIncomingResult + data class Success(val id: Long, val timestamp: TalerTimestamp): AddIncomingResult data object NotAnExchange: AddIncomingResult data object UnknownExchange: AddIncomingResult data object UnknownDebtor: AddIncomingResult @@ -292,7 +292,7 @@ class ExchangeDAO(private val db: Database) { it.getBoolean("out_reserve_pub_reuse") -> AddIncomingResult.ReservePubReuse else -> AddIncomingResult.Success( id = it.getLong("out_tx_row_id"), - timestamp = TalerProtocolTimestamp(timestamp) + timestamp = TalerTimestamp(timestamp) ) } } diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -193,7 +193,7 @@ class CoreBankTokenApiTest { } } }.assertOkJson<TokenSuccessResponse> { - assertEquals(Instant.MAX, it.expiration.t_s) + assertEquals(Instant.MAX, it.expiration.instant) } // Check too big or invalid durations diff --git a/bank/src/test/kotlin/JsonTest.kt b/bank/src/test/kotlin/JsonTest.kt @@ -22,9 +22,9 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.junit.Test import tech.libeufin.bank.CreditDebitInfo -import tech.libeufin.bank.RelativeTime +import tech.libeufin.common.RelativeTime import tech.libeufin.common.TalerAmount -import tech.libeufin.common.TalerProtocolTimestamp +import tech.libeufin.common.TalerTimestamp import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit @@ -55,18 +55,18 @@ class JsonTest { @Test fun timeSerializers() { // from JSON to time types - assert(Json.decodeFromString<RelativeTime>("{\"d_us\": 3}").d_us.toNanos() == 3000L) - assert(Json.decodeFromString<RelativeTime>("{\"d_us\": \"forever\"}").d_us == ChronoUnit.FOREVER.duration) - assert(Json.decodeFromString<TalerProtocolTimestamp>("{\"t_s\": 3}").t_s == Instant.ofEpochSecond(3)) - assert(Json.decodeFromString<TalerProtocolTimestamp>("{\"t_s\": \"never\"}").t_s == Instant.MAX) + assert(Json.decodeFromString<RelativeTime>("{\"d_us\": 3}").duration.toNanos() == 3000L) + assert(Json.decodeFromString<RelativeTime>("{\"d_us\": \"forever\"}").duration == ChronoUnit.FOREVER.duration) + assert(Json.decodeFromString<TalerTimestamp>("{\"t_s\": 3}").instant == Instant.ofEpochSecond(3)) + assert(Json.decodeFromString<TalerTimestamp>("{\"t_s\": \"never\"}").instant == Instant.MAX) // from time types to JSON - val oneDay = RelativeTime(d_us = Duration.of(1, ChronoUnit.DAYS)) + val oneDay = RelativeTime(Duration.of(1, ChronoUnit.DAYS)) val oneDaySerial = Json.encodeToString(oneDay) - assert(Json.decodeFromString<RelativeTime>(oneDaySerial).d_us == oneDay.d_us) - val forever = RelativeTime(d_us = ChronoUnit.FOREVER.duration) + assert(Json.decodeFromString<RelativeTime>(oneDaySerial).duration == oneDay.duration) + val forever = RelativeTime(ChronoUnit.FOREVER.duration) val foreverSerial = Json.encodeToString(forever) - assert(Json.decodeFromString<RelativeTime>(foreverSerial).d_us == forever.d_us) + assert(Json.decodeFromString<RelativeTime>(foreverSerial).duration == forever.duration) } @Test diff --git a/common/src/main/kotlin/TalerCommon.kt b/common/src/main/kotlin/TalerCommon.kt @@ -21,21 +21,16 @@ package tech.libeufin.common import io.ktor.http.* import io.ktor.server.plugins.* -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.longOrNull +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.* import java.net.URI import java.net.URL import java.time.Instant +import java.time.Duration import java.time.temporal.ChronoUnit +import java.util.concurrent.TimeUnit import org.bouncycastle.math.ec.rfc8032.Ed25519 sealed class CommonError(msg: String): Exception(msg) { @@ -44,39 +39,104 @@ sealed class CommonError(msg: String): Exception(msg) { class Payto(msg: String): CommonError(msg) } +/** + * Internal representation of relative times. The + * "forever" case is represented with Long.MAX_VALUE. + */ +@JvmInline +@Serializable(with = RelativeTime.Serializer::class) +value class RelativeTime(val duration: Duration) { + private object Serializer : KSerializer<RelativeTime> { + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("RelativeTime") { + element<JsonElement>("d_us") + } + + override fun serialize(encoder: Encoder, value: RelativeTime) { + val composite = encoder.beginStructure(descriptor) + if (value.duration == ChronoUnit.FOREVER.duration) { + composite.encodeStringElement(descriptor, 0, "forever") + } else { + composite.encodeLongElement(descriptor, 0, TimeUnit.MICROSECONDS.convert(value.duration)) + } + composite.endStructure(descriptor) + } + + override fun deserialize(decoder: Decoder): RelativeTime { + val dec = decoder.beginStructure(descriptor) + val jsonInput = dec as? JsonDecoder ?: error("Can be deserialized only by JSON") + lateinit var maybeDUs: JsonPrimitive + loop@ while (true) { + when (val index = dec.decodeElementIndex(descriptor)) { + 0 -> maybeDUs = jsonInput.decodeJsonElement().jsonPrimitive + CompositeDecoder.DECODE_DONE -> break@loop + else -> throw SerializationException("Unexpected index: $index") + } + } + dec.endStructure(descriptor) + if (maybeDUs.isString) { + if (maybeDUs.content != "forever") throw badRequest("Only 'forever' allowed for d_us as string, but '${maybeDUs.content}' was found") + return RelativeTime(ChronoUnit.FOREVER.duration) + } + val dUs: Long = maybeDUs.longOrNull + ?: throw badRequest("Could not convert d_us: '${maybeDUs.content}' to a number") + when { + dUs < 0 -> throw badRequest("Negative duration specified.") + dUs > MAX_SAFE_INTEGER -> throw badRequest("d_us value $dUs exceed cap (2^53-1)") + else -> return RelativeTime(Duration.of(dUs, ChronoUnit.MICROS)) + } + } + } + + companion object { + const val MAX_SAFE_INTEGER = 9007199254740991L // 2^53 - 1 + } +} /** Timestamp containing the number of seconds since epoch */ -@Serializable -data class TalerProtocolTimestamp( - @Serializable(with = Serializer::class) - val t_s: Instant, -) { - internal object Serializer : KSerializer<Instant> { - override fun serialize(encoder: Encoder, value: Instant) { - if (value == Instant.MAX) { - encoder.encodeString("never") +@JvmInline +@Serializable(with = TalerTimestamp.Serializer::class) +value class TalerTimestamp constructor(val instant: Instant) { + private object Serializer : KSerializer<TalerTimestamp> { + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Timestamp") { + element<JsonElement>("t_s") + } + + override fun serialize(encoder: Encoder, value: TalerTimestamp) { + val composite = encoder.beginStructure(descriptor) + if (value.instant == Instant.MAX) { + composite.encodeStringElement(descriptor, 0, "never") } else { - encoder.encodeLong(value.epochSecond) + composite.encodeLongElement(descriptor, 0, value.instant.epochSecond) } + composite.endStructure(descriptor) } - override fun deserialize(decoder: Decoder): Instant { - val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") - val maybeTs = jsonInput.decodeJsonElement().jsonPrimitive + override fun deserialize(decoder: Decoder): TalerTimestamp { + val dec = decoder.beginStructure(descriptor) + val jsonInput = dec as? JsonDecoder ?: error("Can be deserialized only by JSON") + lateinit var maybeTs: JsonPrimitive + loop@ while (true) { + when (val index = dec.decodeElementIndex(descriptor)) { + 0 -> maybeTs = jsonInput.decodeJsonElement().jsonPrimitive + CompositeDecoder.DECODE_DONE -> break@loop + else -> throw SerializationException("Unexpected index: $index") + } + } + dec.endStructure(descriptor) if (maybeTs.isString) { if (maybeTs.content != "never") throw badRequest("Only 'never' allowed for t_s as string, but '${maybeTs.content}' was found") - return Instant.MAX + return TalerTimestamp(Instant.MAX) } val ts: Long = maybeTs.longOrNull ?: throw badRequest("Could not convert t_s '${maybeTs.content}' to a number") when { ts < 0 -> throw badRequest("Negative timestamp not allowed") ts > Instant.MAX.epochSecond -> throw badRequest("Timestamp $ts too big to be represented in Kotlin") - else -> return Instant.ofEpochSecond(ts) + else -> return TalerTimestamp(Instant.ofEpochSecond(ts)) } } - - override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor } } @@ -103,7 +163,7 @@ value class BaseURL private constructor(val url: URL) { override fun toString(): String = url.toString() - internal object Serializer : KSerializer<BaseURL> { + private object Serializer : KSerializer<BaseURL> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BaseURL", PrimitiveKind.STRING) @@ -164,7 +224,7 @@ class DecimalNumber { } } - internal object Serializer : KSerializer<DecimalNumber> { + private object Serializer : KSerializer<DecimalNumber> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DecimalNumber", PrimitiveKind.STRING) @@ -277,7 +337,7 @@ class TalerAmount { return TalerAmount(value - decrement.value, frac - decrement.frac, currency).normalize() } - internal object Serializer : KSerializer<TalerAmount> { + private object Serializer : KSerializer<TalerAmount> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("TalerAmount", PrimitiveKind.STRING) @@ -346,7 +406,7 @@ sealed class Payto { return this.parsed == other.parsed } - internal object Serializer : KSerializer<Payto> { + private object Serializer : KSerializer<Payto> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Payto", PrimitiveKind.STRING) @@ -580,7 +640,7 @@ class Base32Crockford64B { override fun equals(other: Any?) = (other is Base32Crockford64B) && raw.contentEquals(other.raw) - internal object Serializer : KSerializer<Base32Crockford64B> { + private object Serializer : KSerializer<Base32Crockford64B> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Base32Crockford64B", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: Base32Crockford64B) { diff --git a/common/src/main/kotlin/TalerMessage.kt b/common/src/main/kotlin/TalerMessage.kt @@ -57,7 +57,7 @@ data class TransferRequest( /** Response POST /taler-wire-gateway/transfer */ @Serializable data class TransferResponse( - val timestamp: TalerProtocolTimestamp, + val timestamp: TalerTimestamp, val row_id: Long ) @@ -73,7 +73,7 @@ data class TransferListStatus( val status: TransferStatusState, val amount: TalerAmount, val credit_account: String, - val timestamp: TalerProtocolTimestamp + val timestamp: TalerTimestamp ) /** Request GET /taler-wire-gateway/transfers/{ROW_iD} */ @@ -85,7 +85,7 @@ data class TransferStatus( val origin_exchange_url: String, val wtid: ShortHashCode, val credit_account: String, - val timestamp: TalerProtocolTimestamp + val timestamp: TalerTimestamp ) /** Request POST /taler-wire-gateway/admin/add-incoming */ @@ -99,7 +99,7 @@ data class AddIncomingRequest( /** Response POST /taler-wire-gateway/admin/add-incoming */ @Serializable data class AddIncomingResponse( - val timestamp: TalerProtocolTimestamp, + val timestamp: TalerTimestamp, val row_id: Long ) @@ -122,7 +122,7 @@ data class IncomingHistory( @Serializable sealed interface IncomingBankTransaction { val row_id: Long - val date: TalerProtocolTimestamp + val date: TalerTimestamp val amount: TalerAmount val debit_account: String val credit_fee: TalerAmount? @@ -132,7 +132,7 @@ sealed interface IncomingBankTransaction { @SerialName("KYCAUTH") data class IncomingKycAuthTransaction( override val row_id: Long, - override val date: TalerProtocolTimestamp, + override val date: TalerTimestamp, override val amount: TalerAmount, override val credit_fee: TalerAmount? = null, override val debit_account: String, @@ -142,7 +142,7 @@ data class IncomingKycAuthTransaction( @SerialName("RESERVE") data class IncomingReserveTransaction( override val row_id: Long, - override val date: TalerProtocolTimestamp, + override val date: TalerTimestamp, override val amount: TalerAmount, override val credit_fee: TalerAmount? = null, override val debit_account: String, @@ -152,7 +152,7 @@ data class IncomingReserveTransaction( @SerialName("WAD") data class IncomingWadTransaction( override val row_id: Long, - override val date: TalerProtocolTimestamp, + override val date: TalerTimestamp, override val amount: TalerAmount, override val credit_fee: TalerAmount? = null, override val debit_account: String, @@ -171,7 +171,7 @@ data class OutgoingHistory( @Serializable data class OutgoingTransaction( val row_id: Long, // DB row ID of the payment. - val date: TalerProtocolTimestamp, + val date: TalerTimestamp, val amount: TalerAmount, val credit_account: String, val wtid: ShortHashCode, @@ -202,7 +202,7 @@ data class RevenueIncomingHistory( @Serializable data class RevenueIncomingBankTransaction( val row_id: Long, - val date: TalerProtocolTimestamp, + val date: TalerTimestamp, val amount: TalerAmount, val credit_fee: TalerAmount? = null, val debit_account: String, diff --git a/common/src/main/kotlin/db/types.kt b/common/src/main/kotlin/db/types.kt @@ -81,8 +81,8 @@ fun ResultSet.getOptDecimal(name: String): DecimalNumber? { return amount } -fun ResultSet.getTalerTimestamp(name: String): TalerProtocolTimestamp{ - return TalerProtocolTimestamp(getLong(name).asInstant()) +fun ResultSet.getTalerTimestamp(name: String): TalerTimestamp{ + return TalerTimestamp(getLong(name).asInstant()) } fun ResultSet.getBankPayto(payto: String, name: String?, ctx: BankPaytoCtx): String { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt @@ -134,7 +134,7 @@ fun Routing.wireGatewayApi(db: Database, cfg: NexusConfig) = conditional(cfg.wir ) is IncomingRegistrationResult.Success -> respond( AddIncomingResponse( - timestamp = TalerProtocolTimestamp(timestamp), + timestamp = TalerTimestamp(timestamp), row_id = res.id ) ) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt @@ -96,7 +96,7 @@ class ExchangeDAO(private val db: Database) { /** Result of taler transfer transaction creation */ sealed interface TransferResult { /** Transaction [id] and wire transfer [timestamp] */ - data class Success(val id: Long, val timestamp: TalerProtocolTimestamp): TransferResult + data class Success(val id: Long, val timestamp: TalerTimestamp): TransferResult data object RequestUidReuse: TransferResult data object WtidReuse: TransferResult }