Skip to content

Commit 73f185c

Browse files
authored
feat: MOBILE-389 - add multi-chain support in wallet connection api (#716)
1 parent 9f3a934 commit 73f185c

File tree

12 files changed

+197
-72
lines changed

12 files changed

+197
-72
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.newm.server.database.migration
2+
3+
import org.flywaydb.core.api.migration.BaseJavaMigration
4+
import org.flywaydb.core.api.migration.Context
5+
import org.jetbrains.exposed.sql.transactions.transaction
6+
7+
@Suppress("unused")
8+
class V85__WalletConnectionsUpdates : BaseJavaMigration() {
9+
override fun migrate(context: Context?) {
10+
transaction {
11+
execInBatch(
12+
listOf(
13+
"ALTER TABLE wallet_connections RENAME COLUMN stake_address TO address",
14+
"ALTER TABLE wallet_connections ADD COLUMN IF NOT EXISTS chain int",
15+
"UPDATE wallet_connections SET chain = 0 WHERE chain IS NULL",
16+
"ALTER TABLE wallet_connections ALTER COLUMN chain SET NOT NULL",
17+
"ALTER TABLE wallet_connections ADD COLUMN IF NOT EXISTS name TEXT",
18+
"UPDATE wallet_connections SET name = 'Cardano-' || right(address, 6) WHERE name IS NULL",
19+
"ALTER TABLE wallet_connections ALTER COLUMN name SET NOT NULL"
20+
)
21+
)
22+
}
23+
}
24+
}

newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepositoryImpl.kt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ import io.newm.server.config.repo.ConfigRepository.Companion.CONFIG_KEY_NFTCDN_E
5454
import io.newm.server.features.cardano.database.KeyEntity
5555
import io.newm.server.features.cardano.database.KeyTable
5656
import io.newm.server.features.cardano.database.ScriptAddressWhitelistEntity
57+
import io.newm.server.features.cardano.model.CardanoNftSong
5758
import io.newm.server.features.cardano.model.EncryptionRequest
5859
import io.newm.server.features.cardano.model.GetWalletSongsResponse
5960
import io.newm.server.features.cardano.model.Key
60-
import io.newm.server.features.cardano.model.CardanoNftSong
6161
import io.newm.server.features.cardano.model.WalletSong
6262
import io.newm.server.features.cardano.parser.toNFTSongs
6363
import io.newm.server.features.cardano.parser.toResourceUrl
@@ -80,6 +80,7 @@ import io.newm.server.features.song.model.Song
8080
import io.newm.server.features.song.model.SongFilters
8181
import io.newm.server.features.song.repo.SongRepository
8282
import io.newm.server.features.walletconnection.database.WalletConnectionEntity
83+
import io.newm.server.features.walletconnection.model.WalletChain
8384
import io.newm.server.ktx.cborHexToUtxo
8485
import io.newm.server.ktx.sign
8586
import io.newm.server.model.FilterCriteria
@@ -91,12 +92,6 @@ import io.newm.txbuilder.ktx.fingerprint
9192
import io.newm.txbuilder.ktx.mergeAmounts
9293
import io.newm.txbuilder.ktx.toCborObject
9394
import io.newm.txbuilder.ktx.toNativeAssetMap
94-
import java.time.Duration
95-
import java.util.UUID
96-
import kotlin.coroutines.resume
97-
import kotlin.coroutines.resumeWithException
98-
import kotlin.coroutines.suspendCoroutine
99-
import kotlin.time.Duration.Companion.minutes
10095
import kotlinx.coroutines.flow.Flow
10196
import kotlinx.coroutines.sync.Mutex
10297
import kotlinx.coroutines.sync.withLock
@@ -107,6 +102,12 @@ import software.amazon.awssdk.core.SdkBytes
107102
import software.amazon.awssdk.services.kms.KmsAsyncClient
108103
import software.amazon.awssdk.services.kms.model.DecryptRequest
109104
import software.amazon.awssdk.services.kms.model.EncryptRequest
105+
import java.time.Duration
106+
import java.util.UUID
107+
import kotlin.coroutines.resume
108+
import kotlin.coroutines.resumeWithException
109+
import kotlin.coroutines.suspendCoroutine
110+
import kotlin.time.Duration.Companion.minutes
110111

111112
internal class CardanoRepositoryImpl(
112113
private val client: NewmChainCoroutineStub,
@@ -623,7 +624,7 @@ internal class CardanoRepositoryImpl(
623624

624625
private fun getStakeAddressesByUserId(userId: UserId): List<String> =
625626
transaction {
626-
WalletConnectionEntity.getAllByUserId(userId).map { it.stakeAddress }
627+
WalletConnectionEntity.getAllByUserIdAndWalletChain(userId, WalletChain.Cardano).map { it.address }
627628
}
628629

629630
private suspend fun getWalletAssets(userId: UserId): List<NativeAsset> =

newm-server/src/main/kotlin/io/newm/server/features/ethereum/repo/EthereumRepositoryImpl.kt

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import io.newm.server.features.ethereum.model.EthereumNft
1212
import io.newm.server.features.ethereum.model.EthereumNftSong
1313
import io.newm.server.features.ethereum.model.GetNftsByOwnerResponse
1414
import io.newm.server.features.ethereum.parser.parseSong
15-
import io.newm.server.features.user.database.UserEntity
15+
import io.newm.server.features.walletconnection.database.WalletConnectionEntity
16+
import io.newm.server.features.walletconnection.model.WalletChain
1617
import io.newm.server.ktx.checkedBody
1718
import io.newm.server.ktx.getSecureConfigString
1819
import io.newm.server.typealiases.UserId
@@ -28,23 +29,24 @@ internal class EthereumRepositoryImpl(
2829
override suspend fun getWalletNftSongs(userId: UserId): List<EthereumNftSong> {
2930
logger.debug { "getWalletNftSongs: userId = $userId" }
3031

31-
// Temporary test hook until Ethereum wallet connections are implemented
32-
val email = transaction { UserEntity[userId].email }
33-
if (!email.endsWith("@newm.io")) return emptyList()
34-
val address = "0x89fd6e1e7a737293dc9ecaee118753b7abdf5e37"
35-
32+
val addresses = transaction {
33+
WalletConnectionEntity.getAllByUserIdAndWalletChain(userId, WalletChain.Ethereum).map { it.address }
34+
}
3635
val apiUrl = environment.getConfigString("alchemy.apiUrl")
3736
val apiKey = environment.getSecureConfigString("alchemy.apiKey")
38-
return client
39-
.get("$apiUrl/nft/v3/$apiKey/getNFTsForOwner") {
40-
retry {
41-
maxRetries = 2
42-
delayMillis { 500L }
43-
}
44-
accept(ContentType.Application.Json)
45-
parameter("owner", address)
46-
}.checkedBody<GetNftsByOwnerResponse>()
47-
.ownedNfts
48-
.mapNotNull(EthereumNft::parseSong)
37+
val endpointUrl = "$apiUrl/nft/v3/$apiKey/getNFTsForOwner"
38+
return addresses.flatMap { address ->
39+
client
40+
.get(endpointUrl) {
41+
retry {
42+
maxRetries = 2
43+
delayMillis { 500L }
44+
}
45+
accept(ContentType.Application.Json)
46+
parameter("owner", address)
47+
}.checkedBody<GetNftsByOwnerResponse>()
48+
.ownedNfts
49+
.mapNotNull(EthereumNft::parseSong)
50+
}
4951
}
5052
}

newm-server/src/main/kotlin/io/newm/server/features/walletconnection/WalletConnectionRoutes.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.newm.server.ktx.myUserId
1515
import io.newm.server.recaptcha.repo.RecaptchaRepository
1616
import io.newm.shared.ktx.delete
1717
import io.newm.shared.ktx.get
18+
import io.newm.shared.ktx.patch
1819
import io.newm.shared.ktx.post
1920
import org.koin.ktor.ext.inject
2021

@@ -56,6 +57,12 @@ fun Routing.createWalletConnectionRoutes() {
5657
}
5758
respond(walletConnectionRepository.connect(connId, myUserId))
5859
}
60+
61+
patch {
62+
walletConnectionRepository.updateUserConnection(connectionId, myUserId, receive())
63+
respond(HttpStatusCode.NoContent)
64+
}
65+
5966
delete {
6067
walletConnectionRepository.disconnect(connectionId, myUserId)
6168
respond(HttpStatusCode.NoContent)

newm-server/src/main/kotlin/io/newm/server/features/walletconnection/database/WalletConnectionEntity.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.newm.server.features.walletconnection.database
22

3+
import io.newm.server.features.walletconnection.model.WalletChain
34
import io.newm.server.features.walletconnection.model.WalletConnection
45
import io.newm.server.typealiases.UserId
56
import org.jetbrains.exposed.dao.UUIDEntity
@@ -18,14 +19,19 @@ class WalletConnectionEntity(
1819
id: EntityID<UUID>
1920
) : UUIDEntity(id) {
2021
val createdAt: LocalDateTime by WalletConnectionTable.createdAt
21-
var stakeAddress: String by WalletConnectionTable.stakeAddress
22+
var address: String by WalletConnectionTable.address
23+
var chain: WalletChain by WalletConnectionTable.chain
24+
var name: String by WalletConnectionTable.name
2225
var userId: EntityID<UserId>? by WalletConnectionTable.userId
2326

2427
fun toModel(): WalletConnection =
2528
WalletConnection(
2629
id = id.value,
2730
createdAt = createdAt,
28-
stakeAddress = stakeAddress
31+
address = address,
32+
chain = chain,
33+
name = name,
34+
stakeAddress = address // to be removed in future versions
2935
)
3036

3137
companion object : UUIDEntityClass<WalletConnectionEntity>(WalletConnectionTable) {
@@ -37,13 +43,21 @@ class WalletConnectionEntity(
3743

3844
fun deleteAllDuplicates(entity: WalletConnectionEntity) {
3945
WalletConnectionTable.deleteWhere {
40-
(id neq entity.id) and (userId eq entity.userId) and (stakeAddress eq entity.stakeAddress)
46+
(id neq entity.id) and (userId eq entity.userId) and (address eq entity.address)
4147
}
4248
}
4349

4450
fun getAllByUserId(userId: UserId): SizedIterable<WalletConnectionEntity> =
4551
find {
4652
WalletConnectionTable.userId eq userId
4753
}
54+
55+
fun getAllByUserIdAndWalletChain(
56+
userId: UserId,
57+
chain: WalletChain
58+
): SizedIterable<WalletConnectionEntity> =
59+
find {
60+
(WalletConnectionTable.userId eq userId) and (WalletConnectionTable.chain eq chain)
61+
}
4862
}
4963
}

newm-server/src/main/kotlin/io/newm/server/features/walletconnection/database/WalletConnectionTable.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.newm.server.features.walletconnection.database
22

33
import io.newm.server.features.user.database.UserTable
4+
import io.newm.server.features.walletconnection.model.WalletChain
45
import io.newm.server.typealiases.UserId
56
import org.jetbrains.exposed.dao.id.EntityID
67
import org.jetbrains.exposed.dao.id.UUIDTable
@@ -12,6 +13,8 @@ import java.time.LocalDateTime
1213

1314
object WalletConnectionTable : UUIDTable(name = "wallet_connections") {
1415
val createdAt: Column<LocalDateTime> = datetime("created_at").defaultExpression(CurrentDateTime)
15-
val stakeAddress: Column<String> = text("stake_address")
16+
val chain: Column<WalletChain> = enumeration("chain", WalletChain::class)
17+
val address: Column<String> = text("address")
18+
val name: Column<String> = text("name")
1619
val userId: Column<EntityID<UserId>?> = reference("user_id", UserTable, onDelete = ReferenceOption.NO_ACTION).nullable()
1720
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.newm.server.features.walletconnection.model
2+
3+
enum class WalletChain {
4+
Cardano,
5+
Ethereum
6+
}

newm-server/src/main/kotlin/io/newm/server/features/walletconnection/model/WalletConnection.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ data class WalletConnection(
1111
val id: UUID,
1212
@Contextual
1313
val createdAt: LocalDateTime,
14+
val address: String,
15+
val chain: WalletChain,
16+
val name: String,
17+
@Deprecated("use address field instead, will be removed in future versions")
1418
val stakeAddress: String
1519
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.newm.server.features.walletconnection.model
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class WalletConnectionUpdateRequest(
7+
val name: String
8+
)

newm-server/src/main/kotlin/io/newm/server/features/walletconnection/repo/WalletConnectionRepository.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package io.newm.server.features.walletconnection.repo
22

33
import io.newm.server.features.walletconnection.model.AnswerChallengeRequest
44
import io.newm.server.features.walletconnection.model.AnswerChallengeResponse
5-
import io.newm.server.features.walletconnection.model.WalletConnection
65
import io.newm.server.features.walletconnection.model.GenerateChallengeRequest
76
import io.newm.server.features.walletconnection.model.GenerateChallengeResponse
7+
import io.newm.server.features.walletconnection.model.WalletConnection
8+
import io.newm.server.features.walletconnection.model.WalletConnectionUpdateRequest
89
import io.newm.server.typealiases.UserId
910
import java.util.UUID
1011

@@ -26,4 +27,10 @@ interface WalletConnectionRepository {
2627
)
2728

2829
suspend fun getUserConnections(userId: UserId): List<WalletConnection>
30+
31+
suspend fun updateUserConnection(
32+
connectionId: UUID,
33+
userId: UserId,
34+
request: WalletConnectionUpdateRequest
35+
)
2936
}

0 commit comments

Comments
 (0)