Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PWN-8904 - Striga API. get_user_wallets #1883

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import org.p2p.wallet.striga.wallet.api.request.StrigaAddWhitelistedAddressReque
import org.p2p.wallet.striga.wallet.api.request.StrigaEnrichAccountRequest
import org.p2p.wallet.striga.wallet.api.request.StrigaGetWhitelistedAddressesRequest
import org.p2p.wallet.striga.wallet.api.request.StrigaInitiateOnchainWithdrawalRequest
import org.p2p.wallet.striga.wallet.api.request.StrigaUserWalletsRequest
import org.p2p.wallet.striga.wallet.api.response.StrigaEnrichFiatAccountResponse
import org.p2p.wallet.striga.wallet.api.response.StrigaInitiateOnchainWithdrawalResponse
import org.p2p.wallet.striga.wallet.api.response.StrigaUserWalletsResponse
import org.p2p.wallet.striga.wallet.api.response.StrigaWhitelistedAddressItemResponse
import org.p2p.wallet.striga.wallet.api.response.StrigaWhitelistedAddressesResponse

Expand All @@ -34,4 +36,7 @@ interface StrigaWalletApi {
*/
@POST("v1/wallets/account/enrich")
suspend fun enrichFiatAccount(@Body body: StrigaEnrichAccountRequest): StrigaEnrichFiatAccountResponse

@POST("v1/wallets/get/all")
suspend fun getUserWallets(@Body body: StrigaUserWalletsRequest): StrigaUserWalletsResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.p2p.wallet.striga.wallet.api.request

import com.google.gson.annotations.SerializedName
import org.p2p.core.utils.MillisSinceEpoch

class StrigaUserWalletsRequest(
@SerializedName("userId")
val userId: String,
@SerializedName("startDate")
val startDate: MillisSinceEpoch,
@SerializedName("endDate")
val endDate: MillisSinceEpoch,
@SerializedName("page")
val page: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.p2p.wallet.striga.wallet.api.response

import com.google.gson.annotations.SerializedName
import java.math.BigInteger

data class StrigaUserWalletAccountResponse(
@SerializedName("accountId")
val accountId: String,
@SerializedName("parentWalletId")
val parentWalletId: String,
@SerializedName("currency")
val currency: String,
@SerializedName("ownerId")
val ownerId: String,
@SerializedName("rootFiatCurrency")
val rootFiatCurrency: String,
@SerializedName("ownerType")
val ownerType: String,
@SerializedName("createdAt")
val createdAt: String,
@SerializedName("availableBalance")
val availableBalance: AvailableBalanceResponse,
@SerializedName("linkedCardId")
val linkedCardId: String,
@SerializedName("linkedBankAccountId")
val linkedBankAccountId: String,
@SerializedName("status")
val status: String
) {
/**
* @param currencyUnits can be cents, or satoshis or smth else
*/
data class AvailableBalanceResponse(
@SerializedName("amount")
val amount: BigInteger,
@SerializedName("currency")
val currencyUnits: String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.p2p.wallet.striga.wallet.api.response

import com.google.gson.annotations.SerializedName
import com.google.gson.internal.LinkedTreeMap

/**
* @param createdAt - example 2022-07-22T12:39:56.835Z
*/
data class StrigaUserWalletDetailsResponse(
@SerializedName("walletId")
val walletId: String,
@SerializedName("accounts")
val accountCurrencyToDetails: LinkedTreeMap<String, StrigaUserWalletAccountResponse>,
@SerializedName("rootFiatCurrency")
val rootFiatCurrency: String,
@SerializedName("syncedOwnerId")
val syncedOwnerId: String,
@SerializedName("ownerType")
val ownerType: String,
@SerializedName("createdAt")
val createdAt: String,
@SerializedName("comment")
val comment: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.p2p.wallet.striga.wallet.api.response

import com.google.gson.annotations.SerializedName

class StrigaUserWalletsResponse(
@SerializedName("wallets")
val wallets: List<StrigaUserWalletDetailsResponse>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.p2p.wallet.striga.wallet.models

import org.p2p.core.utils.isNotZero
import org.p2p.wallet.striga.wallet.models.ids.StrigaWalletId

/**
* A wallet on the Striga platform contains accounts.
* Accounts are the lowest divisible unit of value storage and are each represented in one currency only.
* When creating a wallet, accounts for each of your configured currencies are created and linked under that wallet.
*/
data class StrigaUserWallet(
val walletId: StrigaWalletId,
val userId: String,
val accounts: List<StrigaUserWalletAccount>
) {
val hasAvailableBalance: Boolean
get() = accounts.any { it.availableBalance.isNotZero() }

val eurAccount: StrigaUserWalletAccount?
get() = accounts.firstOrNull { it.accountCurrency == StrigaWalletAccountCurrency.EUR }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.p2p.wallet.striga.wallet.models

import java.math.BigInteger
import org.p2p.wallet.striga.wallet.models.ids.StrigaAccountId

class StrigaUserWalletAccount(
val accountId: StrigaAccountId,
// todo (leave comment on PR if you see this): convert to enum when all statuses are found
val accountStatus: String,
val accountCurrency: StrigaWalletAccountCurrency,
val parentWalletId: String,
val ownerId: String,
val rootFiatCurrency: String,
val ownerType: String,
val availableBalance: BigInteger,
val balanceUnit: String,
val linkedBankAccount: StrigaWalletAccountBankLink,
) {
fun availableBalanceWithUnits(): String = "$availableBalance $balanceUnit"
}

sealed interface StrigaWalletAccountBankLink {
data class Linked(val value: String) : StrigaWalletAccountBankLink
object Unlinked : StrigaWalletAccountBankLink
}

/**
* Striga provides only 1 wallet with two accounts - USDC and EUR
*/
enum class StrigaWalletAccountCurrency(val currencyName: String) {
USDC("USDC"), EUR("EUR"), OTHER("OTHER")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.p2p.wallet.striga.wallet.repository

import org.p2p.wallet.striga.wallet.api.response.StrigaUserWalletAccountResponse
import org.p2p.wallet.striga.wallet.api.response.StrigaUserWalletDetailsResponse
import org.p2p.wallet.striga.wallet.api.response.StrigaUserWalletsResponse
import org.p2p.wallet.striga.wallet.models.StrigaUserWallet
import org.p2p.wallet.striga.wallet.models.StrigaUserWalletAccount
import org.p2p.wallet.striga.wallet.models.StrigaWalletAccountBankLink
import org.p2p.wallet.striga.wallet.models.StrigaWalletAccountCurrency
import org.p2p.wallet.striga.wallet.models.ids.StrigaAccountId
import org.p2p.wallet.striga.wallet.models.ids.StrigaWalletId

class StrigaUserWalletsMapper {
private companion object {
private const val EUR_ACCOUNT_NAME = "EUR"
private const val USDC_ACCOUNT_NAME = "USDC"
private const val UNLINKED_BANK_ACCOUNT_VALUE = "UNLINKED"
}

fun fromNetwork(userId: String, response: StrigaUserWalletsResponse): StrigaUserWallet {
require(response.wallets.isNotEmpty()) {
"Wallets should be not empty: they are created when user $userId is created"
}
val activeWallet: StrigaUserWalletDetailsResponse = response.wallets.first()

return StrigaUserWallet(
userId = userId,
walletId = StrigaWalletId(activeWallet.walletId),
// no support for multiple wallets so we get first
accounts = activeWallet.accountCurrencyToDetails.map(::toDomain)
)
}

private fun toDomain(entry: Map.Entry<String, StrigaUserWalletAccountResponse>): StrigaUserWalletAccount {
val (accountCurrency, accountDetails) = entry
return StrigaUserWalletAccount(
accountId = StrigaAccountId(accountDetails.accountId),
accountStatus = accountDetails.status,
accountCurrency = mapAccountCurrency(accountCurrency),
parentWalletId = accountDetails.parentWalletId,
ownerId = accountDetails.ownerId,
rootFiatCurrency = accountDetails.rootFiatCurrency,
ownerType = accountDetails.ownerType,
availableBalance = accountDetails.availableBalance.amount,
balanceUnit = accountDetails.availableBalance.currencyUnits,
linkedBankAccount = mapLinkedBankAccount(accountDetails.linkedBankAccountId)
)
}

private fun mapAccountCurrency(currency: String): StrigaWalletAccountCurrency {
return when (currency.uppercase()) {
EUR_ACCOUNT_NAME -> StrigaWalletAccountCurrency.EUR
USDC_ACCOUNT_NAME -> StrigaWalletAccountCurrency.USDC
else -> StrigaWalletAccountCurrency.OTHER
}
}

private fun mapLinkedBankAccount(linkedBankAccountId: String?): StrigaWalletAccountBankLink {
return if (linkedBankAccountId == null || linkedBankAccountId == UNLINKED_BANK_ACCOUNT_VALUE) {
StrigaWalletAccountBankLink.Unlinked
} else {
StrigaWalletAccountBankLink.Linked(linkedBankAccountId)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.p2p.wallet.striga.wallet.repository

import java.math.BigInteger
import java.util.Calendar
import org.p2p.wallet.striga.StrigaUserIdProvider
import org.p2p.wallet.striga.model.StrigaDataLayerError
import org.p2p.wallet.striga.model.StrigaDataLayerResult
import org.p2p.wallet.striga.model.toSuccessResult
Expand All @@ -9,16 +11,20 @@ import org.p2p.wallet.striga.wallet.api.request.StrigaAddWhitelistedAddressReque
import org.p2p.wallet.striga.wallet.api.request.StrigaEnrichAccountRequest
import org.p2p.wallet.striga.wallet.api.request.StrigaGetWhitelistedAddressesRequest
import org.p2p.wallet.striga.wallet.api.request.StrigaInitiateOnchainWithdrawalRequest
import org.p2p.wallet.striga.wallet.api.request.StrigaUserWalletsRequest
import org.p2p.wallet.striga.wallet.models.StrigaFiatAccountDetails
import org.p2p.wallet.striga.wallet.models.StrigaInitiateOnchainWithdrawalDetails
import org.p2p.wallet.striga.wallet.models.StrigaNetworkCurrency
import org.p2p.wallet.striga.wallet.models.StrigaUserWallet
import org.p2p.wallet.striga.wallet.models.StrigaWhitelistedAddressItem
import org.p2p.wallet.striga.wallet.models.ids.StrigaAccountId
import org.p2p.wallet.striga.wallet.models.ids.StrigaWhitelistedAddressId

class StrigaWalletRemoteRepository(
private val api: StrigaWalletApi,
private val mapper: StrigaWalletRepositoryMapper,
private val walletsMapper: StrigaUserWalletsMapper,
private val strigaUserIdProvider: StrigaUserIdProvider
) : StrigaWalletRepository {

override suspend fun initiateOnchainWithdrawal(
Expand Down Expand Up @@ -105,4 +111,27 @@ class StrigaWalletRemoteRepository(
)
}
}

override suspend fun getUserWallet(): StrigaDataLayerResult<StrigaUserWallet> {
return try {
val hardcodedStartDate = Calendar.getInstance().apply { set(2023, 6, 26) }.timeInMillis
val request = StrigaUserWalletsRequest(
userId = strigaUserIdProvider.getUserIdOrThrow(),
startDate = hardcodedStartDate,
endDate = System.currentTimeMillis(),
page = 1
)
val response = api.getUserWallets(request)

walletsMapper.fromNetwork(
userId = strigaUserIdProvider.getUserIdOrThrow(),
response = response
).toSuccessResult()
} catch (error: Throwable) {
StrigaDataLayerError.from(
error = error,
default = StrigaDataLayerError.InternalError(error)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package org.p2p.wallet.striga.wallet.repository
import java.math.BigInteger
import org.p2p.wallet.striga.model.StrigaDataLayerResult
import org.p2p.wallet.striga.wallet.models.StrigaFiatAccountDetails
import org.p2p.wallet.striga.wallet.models.ids.StrigaAccountId
import org.p2p.wallet.striga.wallet.models.StrigaInitiateOnchainWithdrawalDetails
import org.p2p.wallet.striga.wallet.models.StrigaNetworkCurrency
import org.p2p.wallet.striga.wallet.models.ids.StrigaWhitelistedAddressId
import org.p2p.wallet.striga.wallet.models.StrigaUserWallet
import org.p2p.wallet.striga.wallet.models.StrigaWhitelistedAddressItem
import org.p2p.wallet.striga.wallet.models.ids.StrigaAccountId
import org.p2p.wallet.striga.wallet.models.ids.StrigaWhitelistedAddressId

interface StrigaWalletRepository {

Expand Down Expand Up @@ -68,4 +69,6 @@ interface StrigaWalletRepository {
userId: String,
accountId: StrigaAccountId
): StrigaDataLayerResult<StrigaFiatAccountDetails>

suspend fun getUserWallet(): StrigaDataLayerResult<StrigaUserWallet>
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.p2p.wallet.striga.wallet.models.ids.StrigaWhitelistedAddressId
import org.p2p.wallet.utils.fromJsonReified

@OptIn(ExperimentalCoroutinesApi::class)
class StrigaWalletRepositoryTest {
class StrigaWalletRepositoryEnrichAccountTest {

private val gson = Gson()
private val api: StrigaWalletApi = mockk()
Expand Down Expand Up @@ -232,6 +232,8 @@ class StrigaWalletRepositoryTest {
return StrigaWalletRemoteRepository(
api = api,
mapper = StrigaWalletRepositoryMapper(),
walletsMapper = mockk(),
strigaUserIdProvider = mockk()
)
}
}
Loading
Loading