mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-22 02: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 @@
|
||||
/build
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
android {
|
||||
namespace 'io.novafoundation.nova.core'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation coroutinesDep
|
||||
implementation substrateSdkDep
|
||||
|
||||
api web3jDep
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.core.ethereum
|
||||
|
||||
import io.novafoundation.nova.core.ethereum.log.Topic
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.web3j.protocol.Web3j
|
||||
import org.web3j.protocol.websocket.events.LogNotification
|
||||
import org.web3j.protocol.websocket.events.NewHeadsNotification
|
||||
|
||||
interface Web3Api : Web3j {
|
||||
|
||||
fun newHeadsFlow(): Flow<NewHeadsNotification>
|
||||
|
||||
fun logsNotifications(addresses: List<String>, topics: List<Topic>): Flow<LogNotification>
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.core.ethereum.log
|
||||
|
||||
sealed class Topic {
|
||||
|
||||
object Any : Topic()
|
||||
|
||||
data class Single(val value: String) : Topic()
|
||||
|
||||
data class AnyOf(val values: List<String>) : Topic() {
|
||||
|
||||
constructor(vararg values: String) : this(values.toList())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.core.model
|
||||
|
||||
enum class CryptoType {
|
||||
SR25519,
|
||||
ED25519,
|
||||
ECDSA
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.core.model
|
||||
|
||||
data class Language(
|
||||
val iso639Code: String,
|
||||
val name: String? = null
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.core.model
|
||||
|
||||
data class Network(
|
||||
val type: Node.NetworkType
|
||||
) {
|
||||
val name = type.readableName
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
|
||||
|
||||
package io.novafoundation.nova.core.model
|
||||
|
||||
data class Node(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val networkType: NetworkType,
|
||||
val link: String,
|
||||
val isActive: Boolean,
|
||||
val isDefault: Boolean,
|
||||
) {
|
||||
enum class NetworkType(
|
||||
val readableName: String,
|
||||
val runtimeConfiguration: RuntimeConfiguration,
|
||||
) {
|
||||
KUSAMA(
|
||||
"Kusama",
|
||||
RuntimeConfiguration(
|
||||
addressByte = 2,
|
||||
genesisHash = "b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe",
|
||||
erasPerDay = 4
|
||||
)
|
||||
),
|
||||
POLKADOT(
|
||||
"Polkadot",
|
||||
RuntimeConfiguration(
|
||||
addressByte = 0,
|
||||
genesisHash = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3",
|
||||
erasPerDay = 1,
|
||||
)
|
||||
),
|
||||
WESTEND(
|
||||
"Westend",
|
||||
RuntimeConfiguration(
|
||||
addressByte = 42,
|
||||
genesisHash = "e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e",
|
||||
erasPerDay = 4
|
||||
)
|
||||
),
|
||||
|
||||
ROCOCO(
|
||||
"Rococo",
|
||||
RuntimeConfiguration(
|
||||
addressByte = 43, // TODO wrong address type, actual is 42, but it will conflict with Westend
|
||||
genesisHash = "0x1ab7fbd1d7c3532386268ec23fe4ff69f5bb6b3e3697947df3a2ec2786424de3",
|
||||
erasPerDay = 4
|
||||
)
|
||||
);
|
||||
|
||||
companion object {
|
||||
fun <T> find(value: T, extractor: (NetworkType) -> T): NetworkType? {
|
||||
return values().find { extractor(it) == value }
|
||||
}
|
||||
|
||||
fun findByAddressByte(addressByte: Short) = find(addressByte) { it.runtimeConfiguration.addressByte }
|
||||
|
||||
fun findByGenesis(genesis: String) = find(genesis.removePrefix("0x")) { it.runtimeConfiguration.genesisHash }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Node.NetworkType.chainId
|
||||
get() = runtimeConfiguration.genesisHash
|
||||
@@ -0,0 +1,9 @@
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
|
||||
|
||||
package io.novafoundation.nova.core.model
|
||||
|
||||
class RuntimeConfiguration(
|
||||
val genesisHash: String,
|
||||
val erasPerDay: Int,
|
||||
val addressByte: Short,
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
package io.novafoundation.nova.core.model
|
||||
|
||||
import io.novasama.substrate_sdk_android.encrypt.keypair.Keypair
|
||||
|
||||
sealed class SecuritySource(
|
||||
val keypair: Keypair
|
||||
) {
|
||||
|
||||
open class Specified(
|
||||
final override val seed: ByteArray?,
|
||||
keypair: Keypair
|
||||
) : SecuritySource(keypair), WithJson, WithSeed {
|
||||
|
||||
override fun jsonFormer() = jsonFormer(seed)
|
||||
|
||||
class Create(
|
||||
seed: ByteArray?,
|
||||
keypair: Keypair,
|
||||
override val mnemonic: String,
|
||||
override val derivationPath: String?
|
||||
) : Specified(seed, keypair), WithMnemonic, WithDerivationPath
|
||||
|
||||
class Seed(
|
||||
seed: ByteArray?,
|
||||
keypair: Keypair,
|
||||
override val derivationPath: String?
|
||||
) : Specified(seed, keypair), WithDerivationPath
|
||||
|
||||
class Mnemonic(
|
||||
seed: ByteArray?,
|
||||
keypair: Keypair,
|
||||
override val mnemonic: String,
|
||||
override val derivationPath: String?
|
||||
) : Specified(seed, keypair), WithMnemonic, WithDerivationPath
|
||||
|
||||
class Json(
|
||||
seed: ByteArray?,
|
||||
keypair: Keypair
|
||||
) : Specified(seed, keypair)
|
||||
}
|
||||
|
||||
open class Unspecified(
|
||||
keypair: Keypair
|
||||
) : SecuritySource(keypair)
|
||||
}
|
||||
|
||||
interface WithMnemonic {
|
||||
val mnemonic: String
|
||||
|
||||
fun mnemonicWords() = mnemonic.split(" ")
|
||||
}
|
||||
|
||||
interface WithSeed {
|
||||
val seed: ByteArray?
|
||||
}
|
||||
|
||||
interface WithJson {
|
||||
fun jsonFormer(): JsonFormer
|
||||
}
|
||||
|
||||
interface WithDerivationPath {
|
||||
val derivationPath: String?
|
||||
}
|
||||
|
||||
sealed class JsonFormer {
|
||||
object KeyPair : JsonFormer()
|
||||
|
||||
class Seed(val seed: ByteArray) : JsonFormer()
|
||||
}
|
||||
|
||||
fun jsonFormer(seed: ByteArray?): JsonFormer {
|
||||
return if (seed != null) {
|
||||
JsonFormer.Seed(seed)
|
||||
} else {
|
||||
JsonFormer.KeyPair
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package io.novafoundation.nova.core.model
|
||||
|
||||
data class StorageChange(val block: String, val key: String, val value: String?)
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.core.model
|
||||
|
||||
class StorageEntry(
|
||||
val storageKey: String,
|
||||
val content: String?,
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.novafoundation.nova.core.storage
|
||||
|
||||
import io.novafoundation.nova.core.model.StorageEntry
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StorageCache {
|
||||
|
||||
suspend fun isPrefixInCache(prefixKey: String, chainId: String): Boolean
|
||||
|
||||
suspend fun isFullKeyInCache(fullKey: String, chainId: String): Boolean
|
||||
|
||||
suspend fun insert(entry: StorageEntry, chainId: String)
|
||||
|
||||
suspend fun insert(entries: List<StorageEntry>, chainId: String)
|
||||
|
||||
suspend fun insertPrefixEntries(entries: List<StorageEntry>, prefixKey: String, chainId: String)
|
||||
|
||||
suspend fun removeByPrefix(prefixKey: String, chainId: String)
|
||||
suspend fun removeByPrefixExcept(
|
||||
prefixKey: String,
|
||||
fullKeyExceptions: List<String>,
|
||||
chainId: String
|
||||
)
|
||||
|
||||
fun observeEntry(key: String, chainId: String): Flow<StorageEntry>
|
||||
|
||||
/**
|
||||
* First result will be emitted when all keys are found in the cache
|
||||
* Thus, result.size == fullKeys.size
|
||||
*/
|
||||
fun observeEntries(keys: List<String>, chainId: String): Flow<List<StorageEntry>>
|
||||
|
||||
suspend fun observeEntries(keyPrefix: String, chainId: String): Flow<List<StorageEntry>>
|
||||
|
||||
/**
|
||||
* Should suspend until any matched result found
|
||||
*/
|
||||
suspend fun getEntry(key: String, chainId: String): StorageEntry
|
||||
|
||||
suspend fun filterKeysInCache(keys: List<String>, chainId: String): List<String>
|
||||
|
||||
suspend fun getKeys(keyPrefix: String, chainId: String): List<String>
|
||||
|
||||
/**
|
||||
* Should suspend until all keys will be found
|
||||
* Thus, result.size == fullKeys.size
|
||||
*/
|
||||
suspend fun getEntries(fullKeys: List<String>, chainId: String): List<StorageEntry>
|
||||
}
|
||||
|
||||
suspend fun StorageCache.insert(entries: Map<String, String?>, chainId: String) {
|
||||
val changes = entries.map { (key, value) -> StorageEntry(key, value) }
|
||||
|
||||
insert(changes, chainId)
|
||||
}
|
||||
|
||||
suspend fun StorageCache.insertPrefixEntries(entries: Map<String, String?>, prefix: String, chainId: String) {
|
||||
val changes = entries.map { (key, value) -> StorageEntry(key, value) }
|
||||
|
||||
insertPrefixEntries(changes, prefixKey = prefix, chainId = chainId)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.novafoundation.nova.core.updater
|
||||
|
||||
import io.novafoundation.nova.core.ethereum.Web3Api
|
||||
import io.novafoundation.nova.core.ethereum.log.Topic
|
||||
import io.novafoundation.nova.core.model.StorageChange
|
||||
import io.novasama.substrate_sdk_android.wsrpc.SocketService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.web3j.protocol.core.Request
|
||||
import org.web3j.protocol.core.Response
|
||||
import org.web3j.protocol.websocket.events.LogNotification
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
interface SubstrateSubscriptionBuilder {
|
||||
|
||||
val socketService: SocketService?
|
||||
|
||||
fun subscribe(key: String): Flow<StorageChange>
|
||||
}
|
||||
|
||||
interface EthereumSharedRequestsBuilder {
|
||||
|
||||
val callApi: Web3Api?
|
||||
|
||||
val subscriptionApi: Web3Api?
|
||||
|
||||
fun <S, T : Response<*>> ethBatchRequestAsync(batchId: String, request: Request<S, T>): CompletableFuture<T>
|
||||
|
||||
fun subscribeEthLogs(address: String, topics: List<Topic>): Flow<LogNotification>
|
||||
}
|
||||
|
||||
val EthereumSharedRequestsBuilder.callApiOrThrow: Web3Api
|
||||
get() = requireNotNull(callApi) {
|
||||
"Chain doesn't have any ethereum apis available"
|
||||
}
|
||||
|
||||
interface SharedRequestsBuilder : SubstrateSubscriptionBuilder, EthereumSharedRequestsBuilder
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.novafoundation.nova.core.updater
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.transform
|
||||
|
||||
/**
|
||||
* We do not want this extension to be visible outside of update system
|
||||
* So, we put it into marker interface, which will allow to reach it in consumers code
|
||||
*/
|
||||
interface SideEffectScope {
|
||||
|
||||
fun <T> Flow<T>.noSideAffects(): Flow<Updater.SideEffect> = transform { }
|
||||
}
|
||||
|
||||
interface UpdateScope<S> {
|
||||
|
||||
fun invalidationFlow(): Flow<S?>
|
||||
}
|
||||
|
||||
object GlobalScope : UpdateScope<Unit> {
|
||||
|
||||
override fun invalidationFlow() = flowOf(Unit)
|
||||
}
|
||||
|
||||
class EmptyScope<T> : UpdateScope<T> {
|
||||
|
||||
override fun invalidationFlow() = emptyFlow<T>()
|
||||
}
|
||||
|
||||
interface GlobalScopeUpdater : Updater<Unit> {
|
||||
|
||||
override val scope
|
||||
get() = GlobalScope
|
||||
}
|
||||
|
||||
interface Updater<V> : SideEffectScope {
|
||||
|
||||
@Deprecated(
|
||||
"This feature is not flexible enough" +
|
||||
"Updaters should check presense of relevant modules themselves and fallback to no-op in case module is not found"
|
||||
)
|
||||
val requiredModules: List<String>
|
||||
get() = emptyList()
|
||||
|
||||
val scope: UpdateScope<V>
|
||||
|
||||
/**
|
||||
* Implementations should be aware of cancellation
|
||||
*/
|
||||
suspend fun listenForUpdates(
|
||||
storageSubscriptionBuilder: SharedRequestsBuilder,
|
||||
scopeValue: V,
|
||||
): Flow<SideEffect>
|
||||
|
||||
interface SideEffect
|
||||
}
|
||||
|
||||
interface UpdateSystem {
|
||||
|
||||
fun start(): Flow<Updater.SideEffect>
|
||||
}
|
||||
Reference in New Issue
Block a user