Skip to content

Commit 3be0d53

Browse files
committed
Merge branch 'multisig/05-data-models' into multisig/06-account-pages-support
* multisig/05-data-models: Address PR #515 comments for data models Fix detekt issues without @Suppress Fix pre-existing detekt issues Use Go SDK for transaction signing and return signature directly fix: Address remaining PR #514 review comments fix: Address PR #514 review comments fix: Address PR #514 feedback docs: Add refactoring restrictions to prevent unwanted changes
2 parents a6f9d9f + dd5c54e commit 3be0d53

File tree

40 files changed

+820
-1863
lines changed

40 files changed

+820
-1863
lines changed

.cursorrules

Lines changed: 197 additions & 1505 deletions
Large diffs are not rendered by default.

app/src/main/kotlin/com/algorand/android/MainActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ class MainActivity :
356356
private val transactionManagerResultObserver = Observer<Event<TransactionManagerResult>?> {
357357
it?.consume()?.let { result ->
358358
when (result) {
359-
is TransactionManagerResult.Success -> {
359+
is TransactionManagerResult.Success.SignedTransaction -> {
360360
hideLedgerLoadingDialog()
361361
val signedTransactionDetail = result.signedTransactionDetail
362362
if (signedTransactionDetail is SignedTransactionDetail.AssetOperation) {
@@ -390,7 +390,7 @@ class MainActivity :
390390
navToLedgerConnectionIssueBottomSheet()
391391
}
392392

393-
is TransactionManagerResult.OnTransactionRequestSigned -> {
393+
is TransactionManagerResult.Success.TransactionRequestSigned -> {
394394
hideProgress()
395395
hideLedgerLoadingDialog()
396396
TODO("Implement this")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2022-2025 Pera Wallet, LDA
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
* Unless required by applicable law or agreed to in writing, software
7+
* distributed under the License is distributed on an "AS IS" BASIS,
8+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
* See the License for the specific language governing permissions and
10+
* limitations under the License
11+
*/
12+
13+
package com.algorand.android.core.transaction
14+
15+
import com.algorand.wallet.account.core.domain.usecase.GetAccountMinBalance
16+
import com.algorand.wallet.account.info.domain.usecase.GetAccountAlgoBalance
17+
import com.algorand.wallet.account.info.domain.usecase.GetAccountAssetHoldingAmount
18+
import java.math.BigInteger
19+
import javax.inject.Inject
20+
21+
class AccountBalanceProvider @Inject constructor(
22+
private val getAccountAlgoBalance: GetAccountAlgoBalance,
23+
private val getAccountAssetHoldingAmount: GetAccountAssetHoldingAmount,
24+
private val getAccountMinBalance: GetAccountMinBalance
25+
) {
26+
27+
suspend fun getAlgoBalance(address: String): BigInteger? {
28+
return getAccountAlgoBalance(address)
29+
}
30+
31+
suspend fun getAssetHoldingAmount(address: String, assetId: Long): BigInteger? {
32+
return getAccountAssetHoldingAmount(address, assetId)
33+
}
34+
35+
suspend fun getMinBalance(address: String): BigInteger {
36+
return getAccountMinBalance(address)
37+
}
38+
}

app/src/main/kotlin/com/algorand/android/core/transaction/JointAccountTransactionSignHelper.kt

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,11 @@
1212

1313
package com.algorand.android.core.transaction
1414

15-
import com.algorand.algosdk.transaction.SignedTransaction
16-
import com.algorand.algosdk.util.Encoder
1715
import com.algorand.android.models.TransactionSignData
1816
import com.algorand.android.utils.extensions.encodeBase64
19-
import com.algorand.android.utils.signTx
2017
import com.algorand.wallet.account.info.domain.usecase.GetAccountRekeyAdminAddress
2118
import com.algorand.wallet.account.local.domain.model.LocalAccount
22-
import com.algorand.wallet.account.local.domain.usecase.GetAlgo25SecretKey
23-
import com.algorand.wallet.account.local.domain.usecase.GetHdSeed
2419
import com.algorand.wallet.account.local.domain.usecase.GetLocalAccount
25-
import com.algorand.wallet.account.local.domain.usecase.GetLocalAccounts
26-
import com.algorand.wallet.algosdk.transaction.sdk.SignHdKeyTransaction
27-
import com.algorand.wallet.encryption.domain.utils.clearFromMemory
2820
import com.algorand.wallet.foundation.PeraResult
2921
import com.algorand.wallet.jointaccount.domain.usecase.GetJointAccountProposerAddress
3022
import com.algorand.wallet.jointaccount.transaction.domain.model.JointSignRequest
@@ -35,26 +27,18 @@ private const val JOINT_SIGN_REQUEST_TYPE_ASYNC = "async"
3527

3628
class JointAccountTransactionSignHelper @Inject constructor(
3729
private val getLocalAccount: GetLocalAccount,
38-
private val getLocalAccounts: GetLocalAccounts,
39-
private val getAlgo25SecretKey: GetAlgo25SecretKey,
40-
private val getHdSeed: GetHdSeed,
41-
private val signHdKeyTransaction: SignHdKeyTransaction,
30+
private val localAccountSigningHelper: LocalAccountSigningHelper,
4231
private val proposeJointSignRequest: ProposeJointSignRequest,
4332
private val getJointAccountProposerAddress: GetJointAccountProposerAddress,
4433
private val getAccountRekeyAdminAddress: GetAccountRekeyAdminAddress
4534
) {
4635

47-
data class JointSignResult(
48-
val isSuccess: Boolean,
49-
val signRequestId: String? = null
50-
)
51-
5236
suspend fun handleJointAccountTransaction(
5337
jointAccountAddress: String,
5438
transactionDataList: List<TransactionSignData>
5539
): JointSignResult {
5640
val preparedData = prepareJointAccountData(jointAccountAddress, transactionDataList)
57-
?: return JointSignResult(isSuccess = false)
41+
?: return JointSignResult.Error
5842

5943
val result = proposeJointSignRequest(
6044
jointAccountAddress = preparedData.jointAccount.algoAddress,
@@ -91,27 +75,20 @@ class JointAccountTransactionSignHelper @Inject constructor(
9175
preparedData: PreparedJointAccountData
9276
): JointSignResult {
9377
if (result !is PeraResult.Success) {
94-
return JointSignResult(isSuccess = false)
78+
return JointSignResult.Error
9579
}
9680

9781
val signRequestId = (result.data as? JointSignRequest)?.id
98-
?.takeIf { it.isNotBlank() } ?: return JointSignResult(isSuccess = false)
82+
?.takeIf { it.isNotBlank() } ?: return JointSignResult.Error
9983

10084
autoSignWithLocalAccounts(
10185
signRequestId = signRequestId,
10286
jointAccount = preparedData.jointAccount,
10387
rawTransactions = preparedData.rawTransactionLists.flatten()
10488
)
105-
return JointSignResult(isSuccess = true, signRequestId = signRequestId)
89+
return JointSignResult.Success(signRequestId)
10690
}
10791

108-
private data class PreparedJointAccountData(
109-
val jointAccount: LocalAccount.Joint,
110-
val proposerAddress: String,
111-
val rawTransactionLists: List<List<String>>,
112-
val transactionSignatureLists: List<List<String?>>
113-
)
114-
11592
private suspend fun autoSignWithLocalAccounts(
11693
signRequestId: String,
11794
jointAccount: LocalAccount.Joint,
@@ -129,10 +106,6 @@ class JointAccountTransactionSignHelper @Inject constructor(
129106
TODO("Implement auto sign with local accounts")
130107
}
131108

132-
companion object {
133-
private const val TAG = "JointAcctTxnSignHelper"
134-
}
135-
136109
private fun prepareRawTransactionLists(transactionDataList: List<TransactionSignData>): List<List<String>>? {
137110
val rawTransactions = transactionDataList.mapNotNull { transactionData ->
138111
transactionData.transactionByteArray?.encodeBase64()
@@ -166,36 +139,22 @@ class JointAccountTransactionSignHelper @Inject constructor(
166139
}
167140

168141
private suspend fun signWithAlgo25Account(transactionBytes: ByteArray, signerAddress: String): ByteArray? {
169-
val secretKey = getAlgo25SecretKey(signerAddress) ?: return null
170-
return try {
171-
val signedTransaction = runCatching { transactionBytes.signTx(secretKey) }.getOrNull()
172-
?.takeIf { it.isNotEmpty() } ?: return null
173-
extractSignatureFromSignedTransaction(signedTransaction)
174-
} finally {
175-
secretKey.clearFromMemory()
176-
}
142+
return localAccountSigningHelper.signWithAlgo25AccountReturnSignature(transactionBytes, signerAddress)
177143
}
178144

179145
private suspend fun signWithHdKeyAccount(transactionBytes: ByteArray, hdKey: LocalAccount.HdKey): ByteArray? {
180-
val seed = getHdSeed(seedId = hdKey.seedId) ?: return null
181-
return try {
182-
signHdKeyTransaction.signTransactionSignatureOnly(
183-
transactionBytes,
184-
seed,
185-
hdKey.account,
186-
hdKey.change,
187-
hdKey.keyIndex
188-
)
189-
} finally {
190-
seed.clearFromMemory()
191-
}
146+
return localAccountSigningHelper.signWithHdKeyAccountReturnSignature(transactionBytes, hdKey)
192147
}
193148

194-
private fun extractSignatureFromSignedTransaction(signedTransactionBytes: ByteArray): ByteArray? {
195-
if (signedTransactionBytes.isEmpty()) return null
196-
return runCatching {
197-
val signedTransaction = Encoder.decodeFromMsgPack(signedTransactionBytes, SignedTransaction::class.java)
198-
signedTransaction.sig?.bytes?.takeIf { it.isNotEmpty() }
199-
}.getOrNull()
149+
private data class PreparedJointAccountData(
150+
val jointAccount: LocalAccount.Joint,
151+
val proposerAddress: String,
152+
val rawTransactionLists: List<List<String>>,
153+
val transactionSignatureLists: List<List<String?>>
154+
)
155+
156+
sealed interface JointSignResult {
157+
data class Success(val signRequestId: String) : JointSignResult
158+
data object Error : JointSignResult
200159
}
201160
}

app/src/main/kotlin/com/algorand/android/core/transaction/LocalAccountSigningHelper.kt

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,43 @@
1212

1313
package com.algorand.android.core.transaction
1414

15+
import app.perawallet.gomobilesdk.sdk.Sdk
1516
import com.algorand.android.utils.signTx
1617
import com.algorand.wallet.account.local.domain.model.LocalAccount
1718
import com.algorand.wallet.account.local.domain.usecase.GetAlgo25SecretKey
1819
import com.algorand.wallet.account.local.domain.usecase.GetHdSeed
19-
import com.algorand.wallet.account.local.domain.usecase.GetLocalAccount
2020
import com.algorand.wallet.algosdk.transaction.sdk.SignHdKeyTransaction
2121
import com.algorand.wallet.encryption.domain.utils.clearFromMemory
2222
import javax.inject.Inject
2323

2424
class LocalAccountSigningHelper @Inject constructor(
2525
private val getAlgo25SecretKey: GetAlgo25SecretKey,
2626
private val getHdSeed: GetHdSeed,
27-
private val getLocalAccount: GetLocalAccount,
2827
private val signHdKeyTransaction: SignHdKeyTransaction
2928
) {
3029

31-
suspend fun getLocalAccount(address: String): LocalAccount? {
32-
return getLocalAccount.invoke(address)
30+
suspend fun signWithAlgo25Account(transactionData: ByteArray, senderAddress: String): ByteArray? {
31+
val secretKey = getAlgo25SecretKey(senderAddress) ?: return null
32+
return try {
33+
runCatching { transactionData.signTx(secretKey) }.getOrNull()
34+
} finally {
35+
secretKey.clearFromMemory()
36+
}
3337
}
3438

35-
suspend fun signWithAlgo25(transactionData: ByteArray, senderAddress: String): ByteArray? {
39+
suspend fun signWithAlgo25AccountReturnSignature(
40+
transactionData: ByteArray,
41+
senderAddress: String
42+
): ByteArray? {
3643
val secretKey = getAlgo25SecretKey(senderAddress) ?: return null
3744
return try {
38-
runCatching { transactionData.signTx(secretKey) }.getOrNull()
45+
runCatching { Sdk.signTransactionReturnSignature(secretKey, transactionData) }.getOrNull()
3946
} finally {
4047
secretKey.clearFromMemory()
4148
}
4249
}
4350

44-
suspend fun signWithHdKey(transactionData: ByteArray, hdKey: LocalAccount.HdKey): ByteArray? {
51+
suspend fun signWithHdKeyAccount(transactionData: ByteArray, hdKey: LocalAccount.HdKey): ByteArray? {
4552
val seed = getHdSeed(seedId = hdKey.seedId) ?: return null
4653
return try {
4754
signHdKeyTransaction.signTransaction(
@@ -55,4 +62,19 @@ class LocalAccountSigningHelper @Inject constructor(
5562
seed.clearFromMemory()
5663
}
5764
}
65+
66+
suspend fun signWithHdKeyAccountReturnSignature(transactionData: ByteArray, hdKey: LocalAccount.HdKey): ByteArray? {
67+
val seed = getHdSeed(seedId = hdKey.seedId) ?: return null
68+
return try {
69+
signHdKeyTransaction.signTransactionReturnSignature(
70+
transactionData,
71+
seed,
72+
hdKey.account,
73+
hdKey.change,
74+
hdKey.keyIndex
75+
)
76+
} finally {
77+
seed.clearFromMemory()
78+
}
79+
}
5880
}

app/src/main/kotlin/com/algorand/android/core/transaction/TransactionSignBaseFragment.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ abstract class TransactionSignBaseFragment(
5959
private val transactionManagerObserver = Observer<Event<TransactionManagerResult>?> { event ->
6060
event?.consume()?.run {
6161
when (this) {
62-
is TransactionManagerResult.Success -> {
62+
is TransactionManagerResult.Success.SignedTransaction -> {
6363
hideLoading()
6464
transactionFragmentListener?.onSignTransactionFinished(this.signedTransactionDetail)
6565
}
@@ -93,7 +93,7 @@ abstract class TransactionSignBaseFragment(
9393
onSignTransactionCancelledByLedger()
9494
}
9595

96-
is TransactionManagerResult.OnTransactionRequestSigned -> {
96+
is TransactionManagerResult.Success.TransactionRequestSigned -> {
9797
hideLoading()
9898
onJointAccountSignRequestCreated(signRequestId)
9999
}

0 commit comments

Comments
 (0)