mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-28 06:07:58 +00:00
Initial commit: Pezkuwi Wallet Android
Security hardened release: - Code obfuscation enabled (minifyEnabled=true, shrinkResources=true) - Sensitive files excluded (google-services.json, keystores) - Branch.io key moved to BuildConfig placeholder - Updated dependencies: OkHttp 4.12.0, Gson 2.10.1, BouncyCastle 1.77 - Comprehensive ProGuard rules for crypto wallet - Navigation 2.7.7, Lifecycle 2.7.0, ConstraintLayout 2.1.4
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.network
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigInteger
|
||||
|
||||
typealias HydraDxAssetId = BigInteger
|
||||
typealias HydraRemoteToLocalMapping = Map<HydraDxAssetId, Chain.Asset>
|
||||
|
||||
interface HydraDxAssetIdConverter {
|
||||
|
||||
val systemAssetId: HydraDxAssetId
|
||||
|
||||
suspend fun toOnChainIdOrNull(chainAsset: Chain.Asset): HydraDxAssetId?
|
||||
|
||||
suspend fun toChainAssetOrNull(chain: Chain, onChainId: HydraDxAssetId): Chain.Asset?
|
||||
|
||||
suspend fun allOnChainIds(chain: Chain): HydraRemoteToLocalMapping
|
||||
}
|
||||
|
||||
fun HydraDxAssetIdConverter.isSystemAsset(assetId: HydraDxAssetId): Boolean {
|
||||
return assetId == systemAssetId
|
||||
}
|
||||
|
||||
suspend fun HydraDxAssetIdConverter.toOnChainIdOrThrow(chainAsset: Chain.Asset): HydraDxAssetId {
|
||||
return requireNotNull(toOnChainIdOrNull(chainAsset))
|
||||
}
|
||||
|
||||
suspend fun HydraDxAssetIdConverter.toChainAssetOrThrow(chain: Chain, onChainId: HydraDxAssetId): Chain.Asset {
|
||||
return requireNotNull(toChainAssetOrNull(chain, onChainId))
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.paths
|
||||
|
||||
import io.novafoundation.nova.common.utils.graph.Path
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.paths.model.PathRoughFeeEstimation
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.paths.model.QuotedEdge
|
||||
|
||||
interface PathFeeEstimator<E> {
|
||||
|
||||
suspend fun roughlyEstimateFee(path: Path<QuotedEdge<E>>): PathRoughFeeEstimation
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.paths
|
||||
|
||||
import io.novafoundation.nova.common.utils.graph.EdgeVisitFilter
|
||||
import io.novafoundation.nova.common.utils.graph.Graph
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.paths.model.BestPathQuote
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.primitive.model.QuotableEdge
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.primitive.model.SwapDirection
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.math.BigInteger
|
||||
|
||||
interface PathQuoter<E : QuotableEdge> {
|
||||
|
||||
interface Factory {
|
||||
|
||||
fun <E : QuotableEdge> create(
|
||||
graphFlow: Flow<Graph<FullChainAssetId, E>>,
|
||||
computationalScope: CoroutineScope,
|
||||
pathFeeEstimation: PathFeeEstimator<E>? = null,
|
||||
filter: EdgeVisitFilter<E>? = null
|
||||
): PathQuoter<E>
|
||||
}
|
||||
|
||||
suspend fun findBestPath(
|
||||
chainAssetIn: Chain.Asset,
|
||||
chainAssetOut: Chain.Asset,
|
||||
amount: BigInteger,
|
||||
swapDirection: SwapDirection,
|
||||
): BestPathQuote<E>
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.paths.model
|
||||
|
||||
class BestPathQuote<E>(
|
||||
val candidates: List<QuotedPath<E>>
|
||||
) {
|
||||
|
||||
val bestPath: QuotedPath<E> = candidates.max()
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.paths.model
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import java.math.BigInteger
|
||||
|
||||
class PathRoughFeeEstimation(val inAssetOut: BalanceOf, val inAssetIn: BalanceOf) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun zero(): PathRoughFeeEstimation {
|
||||
return PathRoughFeeEstimation(BigInteger.ZERO, BigInteger.ZERO)
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.paths.model
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class QuotedEdge<E>(
|
||||
val quotedAmount: BigInteger,
|
||||
val quote: BigInteger,
|
||||
val edge: E
|
||||
)
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.paths.model
|
||||
|
||||
import io.novafoundation.nova.common.utils.graph.Path
|
||||
import io.novafoundation.nova.common.utils.graph.WeightedEdge
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.primitive.model.SwapDirection
|
||||
import java.math.BigInteger
|
||||
|
||||
class QuotedPath<E>(
|
||||
val direction: SwapDirection,
|
||||
val path: Path<QuotedEdge<E>>,
|
||||
val roughFeeEstimation: PathRoughFeeEstimation,
|
||||
) : Comparable<QuotedPath<E>> {
|
||||
|
||||
private val amountOutAfterFees: BigInteger = lastSegmentQuote - roughFeeEstimation.inAssetOut
|
||||
private val amountInAfterFees: BigInteger = firstSegmentQuote + roughFeeEstimation.inAssetIn
|
||||
|
||||
override fun compareTo(other: QuotedPath<E>): Int {
|
||||
return when (direction) {
|
||||
// When we want to sell a token, the bigger the quote - the better
|
||||
SwapDirection.SPECIFIED_IN -> (amountOutAfterFees - other.amountOutAfterFees).signum()
|
||||
// When we want to buy a token, the smaller the quote - the better
|
||||
SwapDirection.SPECIFIED_OUT -> (other.amountInAfterFees - amountInAfterFees).signum()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WeightBreakdown private constructor(
|
||||
val individualWeights: List<Int>,
|
||||
val total: Int
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun <N, E : WeightedEdge<N>> fromQuotedPath(path: QuotedPath<E>): WeightBreakdown {
|
||||
val weightedPath = mutableListOf<E>()
|
||||
val individualWeights = mutableListOf<Int>()
|
||||
var weight = 0
|
||||
|
||||
path.path.forEach { quotedEdge ->
|
||||
val edgeWeight = quotedEdge.edge.weightForAppendingTo(weightedPath)
|
||||
|
||||
weight += edgeWeight
|
||||
weightedPath += quotedEdge.edge
|
||||
individualWeights += edgeWeight
|
||||
}
|
||||
|
||||
return WeightBreakdown(individualWeights, weight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val QuotedPath<*>.quote: BigInteger
|
||||
get() = when (direction) {
|
||||
SwapDirection.SPECIFIED_IN -> lastSegmentQuote
|
||||
SwapDirection.SPECIFIED_OUT -> firstSegmentQuote
|
||||
}
|
||||
|
||||
val QuotedPath<*>.quotedAmount: BigInteger
|
||||
get() = when (direction) {
|
||||
SwapDirection.SPECIFIED_IN -> firstSegmentQuotedAmount
|
||||
SwapDirection.SPECIFIED_OUT -> lastSegmentQuotedAmount
|
||||
}
|
||||
|
||||
val QuotedPath<*>.lastSegmentQuotedAmount: BigInteger
|
||||
get() = path.last().quotedAmount
|
||||
|
||||
val QuotedPath<*>.lastSegmentQuote: BigInteger
|
||||
get() = path.last().quote
|
||||
|
||||
val QuotedPath<*>.firstSegmentQuote: BigInteger
|
||||
get() = path.first().quote
|
||||
|
||||
val QuotedPath<*>.firstSegmentQuotedAmount: BigInteger
|
||||
get() = path.first().quotedAmount
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.primitive
|
||||
|
||||
import io.novafoundation.nova.core.updater.SharedRequestsBuilder
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.primitive.model.QuotableEdge
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SwapQuoting {
|
||||
|
||||
interface QuotingHost {
|
||||
|
||||
val sharedSubscriptions: SwapQuotingSubscriptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform initial data sync needed to later perform [runSubscriptions]
|
||||
* This is separated from [runSubscriptions] since [runSubscriptions] might be io-intense
|
||||
* [sync] should be sufficient for [availableSwapDirections] to work
|
||||
* whereas [runSubscriptions] should enable [QuotableEdge.quote] method to work
|
||||
*/
|
||||
suspend fun sync()
|
||||
|
||||
suspend fun availableSwapDirections(): List<QuotableEdge>
|
||||
|
||||
suspend fun runSubscriptions(
|
||||
userAccountId: AccountId,
|
||||
subscriptionBuilder: SharedRequestsBuilder
|
||||
): Flow<Unit>
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.primitive
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SwapQuotingSubscriptions {
|
||||
|
||||
suspend fun blockNumber(chainId: ChainId): Flow<BlockNumber>
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.primitive.errors
|
||||
|
||||
sealed class SwapQuoteException : Exception() {
|
||||
|
||||
object NotEnoughLiquidity : SwapQuoteException()
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.primitive.model
|
||||
|
||||
import io.novafoundation.nova.common.utils.graph.WeightedEdge
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
|
||||
import java.math.BigInteger
|
||||
|
||||
interface QuotableEdge : WeightedEdge<FullChainAssetId> {
|
||||
companion object {
|
||||
|
||||
// Allow [0..100] precision for smaller weights
|
||||
const val DEFAULT_SEGMENT_WEIGHT = 100
|
||||
}
|
||||
|
||||
suspend fun quote(
|
||||
amount: BigInteger,
|
||||
direction: SwapDirection
|
||||
): BigInteger
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.primitive.model
|
||||
|
||||
enum class SwapDirection {
|
||||
SPECIFIED_IN, SPECIFIED_OUT
|
||||
}
|
||||
|
||||
fun SwapDirection.flip(): SwapDirection {
|
||||
return when (this) {
|
||||
SwapDirection.SPECIFIED_IN -> SwapDirection.SPECIFIED_OUT
|
||||
SwapDirection.SPECIFIED_OUT -> SwapDirection.SPECIFIED_IN
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.types.hydra
|
||||
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.primitive.SwapQuoting
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
interface HydraDxQuoting : SwapQuoting {
|
||||
|
||||
interface Factory {
|
||||
|
||||
fun create(chain: Chain, host: SwapQuoting.QuotingHost): HydraDxQuoting
|
||||
}
|
||||
|
||||
fun getSource(id: String): HydraDxQuotingSource<*>
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.types.hydra
|
||||
|
||||
import io.novafoundation.nova.common.utils.Identifiable
|
||||
import io.novafoundation.nova.core.updater.SharedRequestsBuilder
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.primitive.SwapQuoting
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.primitive.model.QuotableEdge
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface HydraDxQuotingSource<E : QuotableEdge> : Identifiable {
|
||||
|
||||
suspend fun sync()
|
||||
|
||||
suspend fun availableSwapDirections(): Collection<E>
|
||||
|
||||
suspend fun runSubscriptions(
|
||||
userAccountId: AccountId,
|
||||
subscriptionBuilder: SharedRequestsBuilder
|
||||
): Flow<Unit>
|
||||
|
||||
interface Factory<S : HydraDxQuotingSource<*>> {
|
||||
|
||||
fun create(chain: Chain, host: SwapQuoting.QuotingHost): S
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.types.hydra
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainAssetId
|
||||
|
||||
interface HydrationAcceptedFeeCurrenciesFetcher {
|
||||
|
||||
suspend fun fetchAcceptedFeeCurrencies(chain: Chain): Result<Set<ChainAssetId>>
|
||||
|
||||
suspend fun isAcceptedCurrency(chainAsset: Chain.Asset): Result<Boolean>
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.data.types.hydra
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
interface HydrationPriceConversionFallback {
|
||||
|
||||
suspend fun convertNativeAmount(
|
||||
amount: BalanceOf,
|
||||
conversionTarget: Chain.Asset
|
||||
): BalanceOf
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_swap_core_api.di
|
||||
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.network.HydraDxAssetIdConverter
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.paths.PathQuoter
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.types.hydra.HydraDxQuoting
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.types.hydra.HydrationAcceptedFeeCurrenciesFetcher
|
||||
import io.novafoundation.nova.feature_swap_core_api.data.types.hydra.HydrationPriceConversionFallback
|
||||
|
||||
interface SwapCoreApi {
|
||||
|
||||
val hydrationAcceptedFeeCurrenciesFetcher: HydrationAcceptedFeeCurrenciesFetcher
|
||||
|
||||
val hydraDxQuotingFactory: HydraDxQuoting.Factory
|
||||
|
||||
val hydraDxAssetIdConverter: HydraDxAssetIdConverter
|
||||
|
||||
val hydrationPriceConversionFallback: HydrationPriceConversionFallback
|
||||
|
||||
val pathQuoterFactory: PathQuoter.Factory
|
||||
}
|
||||
Reference in New Issue
Block a user