Updated on 2026-08-14

This commit is contained in:
Tangem 2020-03-05 12:00:35 +03:00
commit 5ceab317b0
45 changed files with 888 additions and 114 deletions

1
blockchain-demo/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,63 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.blockchain_demo"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
packagingOptions {
exclude 'lib/x86_64/darwin/libscrypt.dylib'
exclude 'lib/x86_64/freebsd/libscrypt.so'
exclude 'lib/x86_64/linux/libscrypt.so'
}
viewBinding {
enabled = true
}
}
dependencies {
implementation project(':tangem-core')
implementation project(':tangem-sdk')
implementation project(':blockchain')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

21
blockchain-demo/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,22 @@
package com.tangem.blockchain_demo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.blockchain_demo", appContext.packageName)
}
}

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tangem.blockchain_demo">
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.tangem.blockchain_demo.BlockchainDemoActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="www.tangem.com"
android:scheme="http" />
<data
android:host="www.tangem.com"
android:scheme="https" />
<data
android:host="tangem.com"
android:scheme="http" />
<data
android:host="tangem.com"
android:scheme="https" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</activity>
</application>
</manifest>

View file

@ -0,0 +1,188 @@
package com.tangem.blockchain_demo
import android.os.Bundle
import android.text.Editable
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.tangem.CardManager
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.Signer
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain_demo.databinding.ActivityBlockchainDemoBinding
import com.tangem.commands.Card
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskError
import com.tangem.tasks.TaskEvent
import kotlinx.coroutines.*
import java.math.BigDecimal
import kotlin.coroutines.CoroutineContext
class BlockchainDemoActivity : AppCompatActivity() {
private lateinit var cardManager: CardManager
private lateinit var signer: TransactionSigner
private lateinit var card: Card
private lateinit var walletManager: WalletManager
private var issuerDataCounter: Int = 1
private lateinit var fee: BigDecimal
private lateinit var binding: ActivityBlockchainDemoBinding
private val parentJob = Job()
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
handleError(throwable.localizedMessage)
}
private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.IO + exceptionHandler
private val scope = CoroutineScope(coroutineContext)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityBlockchainDemoBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
cardManager = CardManager.init(this)
signer = Signer(cardManager)
binding.btnScan.setOnClickListener { scan() }
binding.btnCheckFee.setOnClickListener { requestFee() }
binding.btnSend.setOnClickListener { send() }
}
private fun scan() {
cardManager.scanCard { taskEvent ->
when (taskEvent) {
is TaskEvent.Event -> {
when (taskEvent.data) {
is ScanEvent.OnReadEvent -> {
card = (taskEvent.data as ScanEvent.OnReadEvent).card
walletManager = WalletManagerFactory.makeWalletManager(card)!!
getInfo()
}
}
}
is TaskEvent.Completion -> {
if (taskEvent.error != null) {
if (taskEvent.error !is TaskError.UserCancelled) {
handleError(taskEvent.error.toString())
}
}
}
}
}
}
private fun getInfo() {
scope.launch {
walletManager.update()
withContext(Dispatchers.Main) {
binding.tvBalance.text =
"${walletManager.wallet.balances[AmountType.Coin]?.value
?: "error"} ${walletManager.blockchain.currency}"
val token = walletManager.wallet.balances[AmountType.Token]
if (token != null) {
binding.tvBalance.text = token.currencySymbol + " " + token.value
binding.etSumToSend.text = Editable.Factory.getInstance().newEditable(
token.value.toString()
)
} else {
binding.etSumToSend.text =
Editable.Factory.getInstance().newEditable(
walletManager.wallet.balances[AmountType.Coin]?.value.toString()
)
}
binding.btnCheckFee.isEnabled = true
}
}
}
private fun requestFee() {
if (binding.etReceiverAddress.text.isBlank()) {
Toast.makeText(this, "Please enter receiver address", Toast.LENGTH_LONG).show()
return
} else if (binding.etSumToSend.text.isBlank()) {
Toast.makeText(this, "Choose sum to send", Toast.LENGTH_LONG).show()
return
}
scope.launch {
val feeResult = (walletManager as FeeProvider).getFee(
walletManager.wallet.balances[AmountType.Token]
?: walletManager.wallet.balances[AmountType.Coin]!!,
binding.etReceiverAddress.text.toString())
withContext(Dispatchers.Main) {
when (feeResult) {
is Result.Failure -> {
handleError(feeResult.error?.localizedMessage ?: "Error")
}
is Result.Success -> {
binding.btnSend.isEnabled = true
val fees = feeResult.data
if (fees.size == 1) {
binding.tvFee.text = fees[0].value.toString()
fee = fees[0].value ?: BigDecimal(0)
} else {
binding.tvFee.text = fees[0].value.toString() + "\n" +
fees[1].value.toString() + "\n" +
fees[2].value.toString()
fee = fees[1].value ?: BigDecimal(0)
}
}
}
}
}
}
private fun send() {
scope.launch {
val result = (walletManager as TransactionSender).send(
formTransactionData(),
signer)
withContext(Dispatchers.Main) {
when (result) {
is SimpleResult.Failure -> {
handleError(result.error?.localizedMessage ?: "Error")
}
is SimpleResult.Success -> {
binding.tvFee.text = "Success"
}
}
}
}
}
private fun formTransactionData(): TransactionData {
val amount = if (walletManager.wallet.balances[AmountType.Token] != null) {
walletManager.wallet.balances[AmountType.Token]!!
} else {
walletManager.wallet.balances[AmountType.Coin]!!.copy(
value = binding.etSumToSend.text.toString().toBigDecimal() - fee
)
}
return TransactionData(
amount,
walletManager.wallet.balances[AmountType.Coin]!!.copy(value = fee),
walletManager.wallet.balances[AmountType.Coin]!!.address!!,
binding.etReceiverAddress.text.toString()
)
}
private fun handleError(error: String?) {
Toast.makeText(this, error, Toast.LENGTH_LONG).show()
}
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
}

View file

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View file

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlockchainDemoActivity">
<TextView
android:id="@+id/tv_balance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:layout_marginBottom="80dp"
android:layout_weight="3"
android:padding="16dp"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="@android:color/black"
android:textSize="26sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="BTC 0.500000034" />
<TextView
android:id="@+id/tv_fee"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:padding="16dp"
android:paddingBottom="48dp"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBottom_toTopOf="@id/et_receiver_address"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:text="MinFee: 200, Fee: 300, Priority: 400" />
<EditText
android:id="@+id/et_receiver_address"
android:layout_width="250dp"
android:layout_height="60dp"
android:hint="Send to:"
app:layout_constraintBottom_toTopOf="@id/et_sum_to_send"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<EditText
android:id="@+id/et_sum_to_send"
android:layout_width="250dp"
android:layout_height="60dp"
android:layout_marginBottom="32dp"
android:hint="Enter amount to send"
app:layout_constraintBottom_toTopOf="@id/btn_scan"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<Button
android:id="@+id/btn_scan"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="Scan card and get info"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6" />
<Button
android:id="@+id/btn_check_fee"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:enabled="false"
android:text="Check fee"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_scan" />
<Button
android:id="@+id/btn_send"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:enabled="false"
android:text="Send transaction"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_check_fee" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#527Aff</color>
<color name="colorPrimaryDark">#027AFF</color>
<color name="colorAccent">#027AFF</color>
<color name="fab">#4f98c0</color>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Blockchain Demo</string>
</resources>

View file

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
</resources>

View file

@ -0,0 +1,16 @@
package com.tangem.blockchain_demo
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View file

@ -2,6 +2,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
@ -24,6 +25,13 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
@ -33,11 +41,12 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'com.squareup.retrofit2:retrofit:2.7.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.6.0'
implementation 'com.squareup.moshi:moshi:1.9.2'
implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2")
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
@ -45,21 +54,21 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.3"
implementation 'org.bitcoinj:bitcoinj-core:0.15.2'
implementation 'com.github.stellar:java-stellar-sdk:0.13.0'
implementation 'com.github.stellar:java-stellar-sdk:0.14.0'
implementation "com.madgag.spongycastle:core:1.58.0.0"
implementation "com.madgag.spongycastle:prov:1.58.0.0"
ext.kethereum_version = '0.79.5'
implementation "com.github.walleth.kethereum:functions:$kethereum_version"
ext.kethereum_version = '0.81.2'
implementation "com.github.walleth.kethereum:extensions_kotlin:$kethereum_version"
implementation "com.github.walleth.kethereum:extensions_transactions:$kethereum_version"
implementation "com.github.walleth.kethereum:erc55:$kethereum_version"
implementation "com.github.walleth.kethereum:keccak_shortcut:$kethereum_version"
implementation "com.github.walleth.kethereum:wallet:$kethereum_version"
implementation "com.github.walleth.kethereum:crypto_impl_spongycastle:$kethereum_version"
implementation "com.github.walleth.kethereum:crypto:$kethereum_version"
implementation "com.github.walleth.kethereum:crypto_api:$kethereum_version"
implementation "com.github.walleth.kethereum:model:$kethereum_version"
implementation 'com.github.komputing.khex:core:1.0.0-RC6'
implementation 'com.github.komputing.khex:extensions:1.0.0-RC6'
implementation 'co.nstant.in:cbor:0.8'

View file

@ -2,7 +2,6 @@ package com.tangem.blockchain.bitcoin
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.toCanonicalised
import org.bitcoinj.core.*
import org.bitcoinj.crypto.TransactionSignature
import org.bitcoinj.script.Script

View file

@ -15,7 +15,7 @@ import java.math.BigDecimal
class BitcoinWalletManager(
private val cardId: String,
private val walletPublicKey: ByteArray,
walletConfig: WalletConfig,
override var wallet: CurrencyWallet,
isTestNet: Boolean = false
) : WalletManager,
TransactionSender,
@ -23,11 +23,13 @@ class BitcoinWalletManager(
override val blockchain = if (isTestNet) Blockchain.BitcoinTestnet else Blockchain.Bitcoin
private val address = blockchain.makeAddress(walletPublicKey)
private val currencyWallet = CurrencyWallet(walletConfig, address)
override var wallet: Wallet = currencyWallet
private val transactionBuilder = BitcoinTransactionBuilder(isTestNet)
private val networkManager = BitcoinNetworkManager(isTestNet)
init {
wallet.balances[AmountType.Coin] = Amount(null, blockchain)
}
override suspend fun update() {
val response = networkManager.getInfo(address)
when (response) {
@ -38,23 +40,24 @@ class BitcoinWalletManager(
private fun updateWallet(response: BitcoinAddressResponse) {
Log.d(this::class.java.simpleName, "Balance is ${response.balance}")
currencyWallet.balances[AmountType.Coin]?.value = response.balance
wallet.balances[AmountType.Coin]?.value = response.balance
transactionBuilder.unspentOutputs = response.unspentTransactions
if (response.hasUnconfirmed) {
if (currencyWallet.pendingTransactions.isEmpty()) {
currencyWallet.pendingTransactions.add(TransactionData(
if (wallet.pendingTransactions.isEmpty()) {
wallet.pendingTransactions.add(TransactionData(
Amount(blockchain.currency, decimals = blockchain.decimals),
null,
"unknown",
currencyWallet.address))
wallet.address))
}
} else {
currencyWallet.pendingTransactions.clear()
wallet.pendingTransactions.clear()
}
}
private fun updateError(error: Throwable?) {
Log.e(this::class.java.simpleName, error?.message ?: "")
if (error != null) throw error
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
@ -73,7 +76,7 @@ class BitcoinWalletManager(
}
}
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
when (val result = networkManager.getFee()) {
is Result.Failure -> return result
is Result.Success -> {

View file

@ -1,6 +1,5 @@
package com.tangem.blockchain.cardano
import android.util.Base64
import android.util.Log
import com.tangem.blockchain.cardano.network.CardanoAddressResponse
import com.tangem.blockchain.cardano.network.CardanoNetworkManager
@ -10,20 +9,17 @@ import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.common.extensions.encodeBase64NoWrap
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.tasks.TaskEvent
import java.math.BigDecimal
class CardanoWalletManager(
private val cardId: String,
private val walletPublicKey: ByteArray,
walletConfig: WalletConfig
override var wallet: CurrencyWallet
) : WalletManager,
TransactionSender,
FeeProvider {
override val blockchain = Blockchain.Cardano
private val address = blockchain.makeAddress(walletPublicKey)
private val currencyWallet = CurrencyWallet(walletConfig, address)
override var wallet: Wallet = currencyWallet
private val transactionBuilder = CardanoTransactionBuilder()
private val networkManager = CardanoNetworkManager()
@ -37,13 +33,14 @@ class CardanoWalletManager(
private fun updateWallet(response: CardanoAddressResponse) {
Log.d(this::class.java.simpleName, "Balance is ${response.balance.toString()}")
currencyWallet.balances[AmountType.Coin]?.value =
wallet.balances[AmountType.Coin]?.value =
response.balance.toBigDecimal().movePointLeft(blockchain.decimals.toInt())
transactionBuilder.unspentOutputs = response.unspentOutputs
}
private fun updateError(error: Throwable?) {
Log.e(this::class.java.simpleName, error?.message ?: "")
if (error != null) throw error
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
@ -58,13 +55,13 @@ class CardanoWalletManager(
}
}
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
val a = 0.155381
val b = 0.000043946
val size = transactionBuilder.getEstimateSize(
TransactionData(amount, null, source, destination), walletPublicKey
TransactionData(amount, null, address, destination), walletPublicKey
)
val fee = (a + b * size).toBigDecimal()
return Result.Success(listOf(Amount(blockchain.currency, fee, source, blockchain.decimals)))
return Result.Success(listOf(Amount(blockchain.currency, fee, address, blockchain.decimals)))
}
}

View file

@ -2,13 +2,15 @@ package com.tangem.blockchain.common
import com.tangem.blockchain.bitcoin.BitcoinAddressFactory
import com.tangem.blockchain.bitcoin.BitcoinAddressValidator
import com.tangem.blockchain.ethereum.EthereumAddressFactory
import com.tangem.blockchain.ethereum.EthereumAddressValidator
import com.tangem.blockchain.cardano.CardanoAddressFactory
import com.tangem.blockchain.cardano.CardanoAddressValidator
import com.tangem.blockchain.ethereum.EthereumAddressFactory
import com.tangem.blockchain.ethereum.EthereumAddressValidator
import com.tangem.blockchain.stellar.StellarAddressFactory
import com.tangem.blockchain.xrp.XrpAddressFactory
import com.tangem.blockchain.xrp.XrpAddressValidator
import java.math.BigDecimal
enum class Blockchain(

View file

@ -30,6 +30,9 @@ data class Amount(
address: String? = null,
type: AmountType = AmountType.Coin
) : this(blockchain.currency, value, address, blockchain.decimals, type)
constructor(token: Token, value: BigDecimal? = null) :
this(token.symbol, value, token.contractAddress, token.decimals, AmountType.Token)
}
data class TransactionData(

View file

@ -2,12 +2,12 @@ package com.tangem.blockchain.common
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.commands.SignResponse
import com.tangem.tasks.TaskEvent
import kotlinx.coroutines.flow.Flow
interface WalletManager {
var wallet: Wallet
var wallet: CurrencyWallet
val blockchain: Blockchain
suspend fun update()
@ -22,5 +22,5 @@ interface TransactionSigner {
}
interface FeeProvider {
suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>>
suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>>
}

View file

@ -1,10 +1,12 @@
package com.tangem.blockchain.common
import com.tangem.blockchain.bitcoin.BitcoinWalletManager
import com.tangem.blockchain.cardano.CardanoWalletManager
import com.tangem.blockchain.ethereum.Chain
import com.tangem.blockchain.ethereum.EthereumWalletManager
import com.tangem.blockchain.cardano.CardanoWalletManager
import com.tangem.blockchain.stellar.StellarWalletManager
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.blockchain.xrp.XrpWalletManager
import com.tangem.commands.Card
@ -13,53 +15,56 @@ object WalletManagerFactory {
fun makeWalletManager(card: Card): WalletManager? {
val walletPublicKey: ByteArray = card.walletPublicKey ?: return null
val blockchainName: String = card.cardData?.blockchainName ?: return null
val blockchain = Blockchain.fromId(blockchainName)
when {
blockchainName == Blockchain.Bitcoin.id -> {
val token = getToken(card)
val wallet = CurrencyWallet.newInstance(blockchain, blockchain.makeAddress(walletPublicKey), token)
when (blockchain) {
Blockchain.Bitcoin -> {
return BitcoinWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, true)
wallet = wallet
)
}
blockchainName == Blockchain.BitcoinTestnet.id -> {
Blockchain.BitcoinTestnet -> {
return BitcoinWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, true),
wallet = wallet,
isTestNet = true
)
}
blockchainName == Blockchain.Ethereum.id -> {
Blockchain.Ethereum -> {
val chain = Chain.Mainnet
return EthereumWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, true),
wallet = wallet,
chain = chain
)
}
blockchainName == Blockchain.Stellar.id -> {
val token = getToken(card)
Blockchain.Stellar -> {
return StellarWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, token == null),
token = token
wallet = wallet
)
}
blockchainName == Blockchain.Cardano.id -> {
Blockchain.Cardano -> {
return CardanoWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(false, true)
wallet = wallet
)
}
blockchainName == Blockchain.XRP.id -> {
Blockchain.XRP -> {
return XrpWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, true)
wallet = wallet
)
}
else -> return null

View file

@ -4,12 +4,11 @@ import com.tangem.CardManager
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.commands.SignResponse
import com.tangem.tasks.TaskEvent
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun <T> retryIO(
times: Int = 3,
@ -56,11 +55,11 @@ sealed class SimpleResult {
}
class Signer(private val cardManager: CardManager) : TransactionSigner {
override suspend fun sign(hashes: Array<ByteArray>, cardId: String): TaskEvent<SignResponse> = coroutineScope {
async {
suspendCancellableCoroutine<TaskEvent<SignResponse>> { continuation ->
cardManager.sign(hashes, cardId) { if (continuation.isActive) continuation.resume(it) }
override suspend fun sign(hashes: Array<ByteArray>, cardId: String): TaskEvent<SignResponse> =
suspendCancellableCoroutine { continuation ->
cardManager.sign(hashes, cardId) { result ->
if (continuation.isActive) continuation.resume(result)
}
}
}.await()
}
}

View file

@ -1,5 +1,7 @@
package com.tangem.blockchain.common.network
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.blockchain.BuildConfig
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
@ -18,11 +20,16 @@ private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor {
return logging
}
private val moshi: Moshi by lazy {
Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
}
fun createRetrofitInstance(baseUrl: String): Retrofit =
Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(okHttpClient)
.build()

View file

@ -1,7 +1,7 @@
package com.tangem.blockchain.ethereum
import org.kethereum.crypto.toAddress
import org.kethereum.functions.isValid
import org.kethereum.erc55.isValid
import org.kethereum.model.Address
import org.kethereum.model.PublicKey

View file

@ -1,6 +1,8 @@
package com.tangem.blockchain.ethereum
import android.util.Log
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.ethereum.network.EthereumNetworkManager
@ -12,7 +14,7 @@ import org.kethereum.DEFAULT_GAS_LIMIT
import org.kethereum.crypto.api.ec.ECDSASignature
import org.kethereum.crypto.determineRecId
import org.kethereum.crypto.impl.ec.canonicalise
import org.kethereum.functions.encodeRLP
import org.kethereum.extensions.transactions.encodeRLP
import org.kethereum.keccakshortcut.keccak
import org.kethereum.model.*
import java.math.BigDecimal
@ -22,22 +24,32 @@ class EthereumWalletManager(
private val cardId: String,
private val walletPublicKey: ByteArray,
chain: Chain,
walletConfig: WalletConfig
token: Token? = null,
override var wallet: CurrencyWallet
) : WalletManager,
TransactionSender,
FeeProvider {
override val blockchain: Blockchain = Blockchain.Ethereum
private val address = blockchain.makeAddress(walletPublicKey)
private val currencyWallet = CurrencyWallet(walletConfig, address)
override var wallet: Wallet = currencyWallet
private val builder = EthereumTransactionBuilder(chain)
private val networkManager = EthereumNetworkManager()
private var pendingTxCount = -1L
private var txCount = -1L
init {
if (token != null) wallet.balances[AmountType.Token] =
Amount(
token.symbol,
null,
token.contractAddress,
token.decimals,
AmountType.Token)
wallet.balances[AmountType.Coin] = Amount(null, blockchain)
}
override suspend fun update() {
val result = networkManager.getInfo(address, currencyWallet.balances[AmountType.Token]?.address)
val result = networkManager.getInfo(address, wallet.balances[AmountType.Token]?.address)
when (result) {
is Result.Failure -> updateError(result.error)
is Result.Success -> updateWallet(result.data)
@ -45,23 +57,24 @@ class EthereumWalletManager(
}
private fun updateWallet(data: EthereumResponse) {
currencyWallet.balances[AmountType.Coin]?.value = data.balance
currencyWallet.balances[AmountType.Token]?.value = data.tokenBalance
wallet.balances[AmountType.Coin]?.value = data.balance
wallet.balances[AmountType.Token]?.value = data.tokenBalance
txCount = data.txCount
pendingTxCount = data.pendingTxCount
if (txCount == pendingTxCount) {
currencyWallet.pendingTransactions.forEach { it.status = TransactionStatus.Confirmed }
} else if (currencyWallet.pendingTransactions.isEmpty()) {
currencyWallet.pendingTransactions.add(TransactionData(
wallet.pendingTransactions.forEach { it.status = TransactionStatus.Confirmed }
} else if (wallet.pendingTransactions.isEmpty()) {
wallet.pendingTransactions.add(TransactionData(
Amount(blockchain.currency, decimals = blockchain.decimals),
null,
"unknown",
currencyWallet.address))
wallet.address))
}
}
private fun updateError(error: Throwable?) {
Log.e(this::class.java.simpleName, error?.message ?: "")
if (error != null) throw error
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
@ -76,7 +89,7 @@ class EthereumWalletManager(
}
}
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
val result = networkManager.getFee(getGasLimit(amount).value)
when (result) {
is Result.Success -> {

View file

@ -12,7 +12,6 @@ import kotlinx.coroutines.coroutineScope
import org.kethereum.ETH_IN_WEI
import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
class EthereumNetworkManager {

View file

@ -54,7 +54,7 @@ class StellarNetworkManager(isTestNet: Boolean) {
coroutineScope {
val accountResponseDefered = async(Dispatchers.IO) { stellarServer.accounts().account(accountId) }
val ledgerResponseDeferred = async(Dispatchers.IO) {
val latestLedger: Int = stellarServer.root().coreLatestLedger
val latestLedger: Int = stellarServer.root().historyLatestLedger
stellarServer.ledgers().ledger(latestLedger.toLong())
}
@ -67,7 +67,7 @@ class StellarNetworkManager(isTestNet: Boolean) {
null
} else {
accountResponse.balances
.find { it.assetType != "native" && it.assetCode == assetCode }
.find { it.assetType != "native" && it.assetIssuer == assetCode }
?.balance?.toBigDecimal()
?: return@coroutineScope Result.Failure(Exception("Stellar Balance not found"))
}

View file

@ -1,5 +1,6 @@
package com.tangem.blockchain.stellar
import android.util.Log
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.SimpleResult
@ -11,8 +12,7 @@ import java.util.*
class StellarWalletManager(
private val cardId: String,
walletPublicKey: ByteArray,
walletConfig: WalletConfig,
token: Token? = null,
override var wallet: CurrencyWallet,
isTestNet: Boolean = false
) : WalletManager,
TransactionSender,
@ -20,26 +20,14 @@ class StellarWalletManager(
override val blockchain: Blockchain = Blockchain.Stellar
private val address = blockchain.makeAddress(walletPublicKey)
private val currencyWallet = CurrencyWallet(walletConfig, address)
override var wallet: Wallet = currencyWallet
private val networkManager = StellarNetworkManager(isTestNet)
private val builder = StellarTransactionBuilder(networkManager, walletPublicKey)
private var baseFee = BASE_FEE
private var baseReserve = BASE_RESERVE
private var sequence = 0L
init {
if (token != null) currencyWallet.balances[AmountType.Token] =
Amount(
token.symbol,
null,
token.contractAddress,
token.decimals,
AmountType.Token)
}
override suspend fun update() {
val result = networkManager.getInfo(address, currencyWallet.balances[AmountType.Token]?.address)
val result = networkManager.getInfo(address, wallet.balances[AmountType.Token]?.address)
when (result) {
is Result.Failure -> updateError(result.error)
is Result.Success -> updateWallet(result.data)
@ -47,15 +35,15 @@ class StellarWalletManager(
}
private fun updateWallet(data: StellarResponse) {
currencyWallet.balances[AmountType.Coin]?.value = data.balance
currencyWallet.balances[AmountType.Token]?.value = data.assetBalance
currencyWallet.balances[AmountType.Reserve]?.value = data.baseReserve
wallet.balances[AmountType.Coin]?.value = data.balance
wallet.balances[AmountType.Token]?.value = data.assetBalance
wallet.balances[AmountType.Reserve]?.value = data.baseReserve
sequence = data.sequence
baseFee = data.baseFee
baseReserve = data.baseReserve
val currentTime = Calendar.getInstance().timeInMillis
currencyWallet.pendingTransactions.forEach { transaction ->
wallet.pendingTransactions.forEach { transaction ->
if (transaction.date?.timeInMillis ?: 0 - currentTime > 10) {
transaction.status = TransactionStatus.Confirmed
}
@ -63,7 +51,8 @@ class StellarWalletManager(
}
private fun updateError(error: Throwable?) {
Log.e(this::class.java.simpleName, error?.message ?: "")
if (error != null) throw error
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
@ -77,7 +66,7 @@ class StellarWalletManager(
}
}
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
return Result.Success(listOf(
Amount(baseFee, blockchain)
))

View file

@ -4,17 +4,63 @@ import com.tangem.blockchain.common.*
import java.util.*
class CurrencyWallet(
override val config: WalletConfig,
override val address: String,
override val exploreUrl: String? = null,
override val shareUrl: String? = null,
val pendingTransactions: MutableList<TransactionData> = mutableListOf(),
val balances: MutableMap<AmountType, Amount> = mutableMapOf(),
val isTestnet: Boolean = false
val blockchain: Blockchain,
address: String,
override val config: WalletConfig
) : Wallet, TransactionValidator {
override val exploreUrl: String? = null
override val shareUrl: String? = null
override val address: String
get() = balances[AmountType.Coin]!!.address!!
val pendingTransactions: MutableList<TransactionData> = mutableListOf()
val balances: MutableMap<AmountType, Amount> = mutableMapOf()
init {
addAmount(Amount(null, blockchain, address))
}
override fun validateTransaction(amount: Amount, fee: Amount?): EnumSet<ValidationError> {
TODO("not implemented")
}
fun addAmount(amount: Amount) {
balances[amount.type] = amount
}
fun addPendingTransaction(transaction: TransactionData) {
pendingTransactions.add(transaction.copy(date = Calendar.getInstance()))
}
companion object {
fun newInstance(blockchain: Blockchain, address: String, token: Token?): CurrencyWallet {
return when (blockchain) {
Blockchain.Bitcoin, Blockchain.XRP -> {
val config = WalletConfig(true, true)
CurrencyWallet(blockchain, address, config)
}
Blockchain.Cardano -> {
val config = WalletConfig(false, true)
CurrencyWallet(blockchain, address, config)
}
Blockchain.Ethereum -> {
val config = WalletConfig(true, token == null)
val wallet = CurrencyWallet(blockchain, address, config)
if (token != null) wallet.addAmount(Amount(token))
wallet
}
Blockchain.Stellar -> {
val config = WalletConfig(false, token == null)
val wallet = CurrencyWallet(blockchain, address, config)
if (token != null) wallet.addAmount(Amount(token))
wallet.addAmount(Amount(null, blockchain, address, AmountType.Reserve))
wallet
}
else -> throw Exception("Unsupported blockchain")
}
}
}
}

View file

@ -12,18 +12,21 @@ import com.tangem.tasks.TaskEvent
class XrpWalletManager(
private val cardId: String,
private val walletPublicKey: ByteArray,
walletConfig: WalletConfig
override var wallet: CurrencyWallet
) : WalletManager,
TransactionSender,
FeeProvider {
override val blockchain = Blockchain.XRP
private val address = blockchain.makeAddress(walletPublicKey)
private val currencyWallet = CurrencyWallet(walletConfig, address)
override var wallet: Wallet = currencyWallet
private val transactionBuilder = XrpTransactionBuilder(walletPublicKey)
private val networkManager = XrpNetworkManager()
init {
wallet.balances[AmountType.Coin] = Amount(null, blockchain)
wallet.balances[AmountType.Reserve] = Amount(null, blockchain, type = AmountType.Reserve)
}
override suspend fun update() {
val result = networkManager.getInfo(address)
when (result) {
@ -34,30 +37,31 @@ class XrpWalletManager(
private fun updateWallet(response: XrpInfoResponse) {
Log.d(this::class.java.simpleName, "Balance is ${response.balance}")
currencyWallet.balances[AmountType.Reserve]?.value = response.reserveBase
wallet.balances[AmountType.Reserve]?.value = response.reserveBase
if (!response.accountFound) {
updateError(Exception("Account not found")) //TODO rework, add reserve
return
}
currencyWallet.balances[AmountType.Coin]?.value = response.balance - response.reserveBase
wallet.balances[AmountType.Coin]?.value = response.balance - response.reserveBase
transactionBuilder.sequence = response.sequence
if (response.hasUnconfirmed) {
if (currencyWallet.pendingTransactions.isEmpty()) {
currencyWallet.pendingTransactions.add(TransactionData(
if (wallet.pendingTransactions.isEmpty()) {
wallet.pendingTransactions.add(TransactionData(
Amount(blockchain.currency, decimals = blockchain.decimals),
null,
"unknown",
currencyWallet.address))
wallet.address))
}
} else {
currencyWallet.pendingTransactions.clear()
wallet.pendingTransactions.clear()
}
}
private fun updateError(error: Throwable?) {
Log.e(this::class.java.simpleName, error?.message ?: "")
if (error != null) throw error
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
@ -72,7 +76,7 @@ class XrpWalletManager(
}
}
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
val result = networkManager.getFee()
when (result) {
is Result.Failure -> return result

View file

@ -1,6 +1,5 @@
package com.tangem.blockchain.xrp.network
import com.tangem.blockchain.bitcoin.network.BitcoinFee
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.common.network.API_RIPPLED

View file

@ -8,7 +8,6 @@ import com.tangem.blockchain.xrp.network.XrpFeeResponse
import com.tangem.blockchain.xrp.network.XrpInfoResponse
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import java.math.BigDecimal
class RippledProvider(private val api: RippledApi) {
private val decimals = Blockchain.XRP.decimals.toInt()