diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ba7612e..b9753b9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "com.lobstr.stellar.vault" minSdk = 24 targetSdk = 36 - versionCode = 57 - versionName = "3.6.4" + versionCode = 58 + versionName = "3.6.5" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -186,7 +186,7 @@ val androidx_legacy_support_v4 by extra("1.0.0") val junit by extra("4.13.2") val runner by extra("1.3.0") val espresso_core by extra("3.7.0") -val stellar_sdk by extra("3.0.0") +val stellar_sdk by extra("4.0.0") val mnemonic by extra("0.1.1") val work_manager by extra("2.11.2") val biometric by extra("1.1.0") diff --git a/tsmapper/build.gradle.kts b/tsmapper/build.gradle.kts index 9e418d9..907c6b9 100644 --- a/tsmapper/build.gradle.kts +++ b/tsmapper/build.gradle.kts @@ -37,7 +37,7 @@ android { namespace = "com.lobstr.stellar.tsmapper" } -val stellar_sdk by extra("3.0.0") +val stellar_sdk by extra("4.0.0") val gson by extra("2.14.0") val firebase_bom by extra("34.12.0") diff --git a/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/data/soroban/host_function/InvokeHostFunctionMapper.kt b/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/data/soroban/host_function/InvokeHostFunctionMapper.kt index 91666bf..07a0da1 100644 --- a/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/data/soroban/host_function/InvokeHostFunctionMapper.kt +++ b/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/data/soroban/host_function/InvokeHostFunctionMapper.kt @@ -8,6 +8,7 @@ import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.s import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth.SorobanAuthorizedFunction import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth.SorobanAuthorizedInvocation import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth.SorobanCredentials +import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth.SorobanDelegateSignature import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.contract.ContractExecutable import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.contract.ContractIDPreimage import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.contract.CreateContractArgs @@ -109,8 +110,33 @@ class InvokeHostFunctionMapper(val assetMapper: AssetMapper = AssetMapper(), val credentials.address.signatureExpirationLedger.uint32.number.toString(), scMapper.mapScVal(credentials.address.signature) ) + // CAP-71 (Protocol 27): same SorobanAddressCredentials payload as ADDRESS. + org.stellar.sdk.xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_ADDRESS_V2 -> SorobanCredentials.AddressV2( + scMapper.mapScAddress(Address.fromSCAddress(credentials.addressV2.address)), + credentials.addressV2.nonce.int64.toString(), + credentials.addressV2.signatureExpirationLedger.uint32.number.toString(), + scMapper.mapScVal(credentials.addressV2.signature) + ) + // CAP-71 (Protocol 27): base address credentials + recursive delegate-signature tree. + org.stellar.sdk.xdr.SorobanCredentialsType.SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES -> { + val addressCredentials = credentials.addressWithDelegates.addressCredentials + SorobanCredentials.AddressWithDelegates( + scMapper.mapScAddress(Address.fromSCAddress(addressCredentials.address)), + addressCredentials.nonce.int64.toString(), + addressCredentials.signatureExpirationLedger.uint32.number.toString(), + scMapper.mapScVal(addressCredentials.signature), + credentials.addressWithDelegates.delegates.map { mapSorobanDelegateSignature(it) } + ) + } } + private fun mapSorobanDelegateSignature(delegate: org.stellar.sdk.xdr.SorobanDelegateSignature): SorobanDelegateSignature = + SorobanDelegateSignature( + scMapper.mapScAddress(Address.fromSCAddress(delegate.address)), + scMapper.mapScVal(delegate.signature), + delegate.nestedDelegates.map { mapSorobanDelegateSignature(it) } + ) + private fun mapSorobanAuthorizedInvocation(invocation: org.stellar.sdk.xdr.SorobanAuthorizedInvocation) : SorobanAuthorizedInvocation = SorobanAuthorizedInvocation(mapSorobanAuthorizationFunction(invocation.function), mutableListOf().apply { invocation.subInvocations.forEach { diff --git a/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/InvokeHostFunctionOperation.kt b/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/InvokeHostFunctionOperation.kt index 2a83d34..81d0e9e 100644 --- a/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/InvokeHostFunctionOperation.kt +++ b/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/InvokeHostFunctionOperation.kt @@ -9,6 +9,7 @@ import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.s import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth.SorobanAuthorizedFunction import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth.SorobanAuthorizedInvocation import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth.SorobanCredentials +import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth.SorobanDelegateSignature import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.contract.ContractExecutable import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.contract.ContractIDPreimage import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.contract.CreateContractArgs @@ -432,31 +433,74 @@ data class InvokeHostFunctionOperation( ) ) - is SorobanCredentials.Address -> { - fields.add(OperationField( - context.getString(R.string.op_field_credentials), - context.getString(R.string.op_value_address) - )) - - getScAddressFields(context, fields, credentials.address) - - fields.add(OperationField( - context.getString(R.string.op_field_nonce), - credentials.nonce - )) + is SorobanCredentials.Address -> addAddressCredentialFields( + context, fields, credentials.address, credentials.nonce, + credentials.signatureExpirationLedger, credentials.signature, amountFormatter + ) - fields.add(OperationField( - context.getString(R.string.op_field_signature_expiration_ledger), - credentials.signatureExpirationLedger - )) + // CAP-71 (Protocol 27): same payload as Address. + is SorobanCredentials.AddressV2 -> addAddressCredentialFields( + context, fields, credentials.address, credentials.nonce, + credentials.signatureExpirationLedger, credentials.signature, amountFormatter + ) - getScValFields(context, fields, credentials.signature, amountFormatter) + // CAP-71 (Protocol 27): base address credentials + recursive delegate-signature tree. + is SorobanCredentials.AddressWithDelegates -> { + addAddressCredentialFields( + context, fields, credentials.address, credentials.nonce, + credentials.signatureExpirationLedger, credentials.signature, amountFormatter + ) + credentials.delegates.forEach { getDelegateFields(context, fields, it, amountFormatter) } } } return fields } + private fun addAddressCredentialFields( + context: Context, + fields: MutableList, + address: SCAddress, + nonce: String, + signatureExpirationLedger: String, + signature: SCVal, + amountFormatter: (value: String) -> String + ): MutableList { + fields.add(OperationField( + context.getString(R.string.op_field_credentials), + context.getString(R.string.op_value_address) + )) + + getScAddressFields(context, fields, address) + + fields.add(OperationField( + context.getString(R.string.op_field_nonce), + nonce + )) + + fields.add(OperationField( + context.getString(R.string.op_field_signature_expiration_ledger), + signatureExpirationLedger + )) + + getScValFields(context, fields, signature, amountFormatter) + + return fields + } + + private fun getDelegateFields( + context: Context, + fields: MutableList, + delegate: SorobanDelegateSignature, + amountFormatter: (value: String) -> String + ): MutableList { + getScAddressFields(context, fields, delegate.address) + getScValFields(context, fields, delegate.signature, amountFormatter) + delegate.nestedDelegates.forEach { getDelegateFields(context, fields, it, amountFormatter) } + + return fields + } + private fun getAuthorizedInvocationFields( context: Context, fields: MutableList, invocation: SorobanAuthorizedInvocation, diff --git a/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/auth/SorobanCredentials.kt b/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/auth/SorobanCredentials.kt index 4f3e80d..cad2fa5 100644 --- a/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/auth/SorobanCredentials.kt +++ b/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/auth/SorobanCredentials.kt @@ -15,4 +15,23 @@ sealed class SorobanCredentials : Parcelable { val signatureExpirationLedger: String, val signature: SCVal ) : SorobanCredentials() + + // CAP-71 (Protocol 27): address-bound signature variant. Same payload shape as [Address]; + // kept as a distinct type so the auth scheme (discriminant) is never conflated with V1. + data class AddressV2( + val address: SCAddress, + val nonce: String, + val signatureExpirationLedger: String, + val signature: SCVal + ) : SorobanCredentials() + + // CAP-71 (Protocol 27): delegated / multi-party signing. Base address credentials plus a + // (recursive) tree of delegate signatures. + data class AddressWithDelegates( + val address: SCAddress, + val nonce: String, + val signatureExpirationLedger: String, + val signature: SCVal, + val delegates: List + ) : SorobanCredentials() } \ No newline at end of file diff --git a/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/auth/SorobanDelegateSignature.kt b/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/auth/SorobanDelegateSignature.kt new file mode 100644 index 0000000..4382f08 --- /dev/null +++ b/tsmapper/src/main/java/com/lobstr/stellar/tsmapper/presentation/entities/transaction/operation/soroban/auth/SorobanDelegateSignature.kt @@ -0,0 +1,15 @@ +package com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.auth + +import android.os.Parcelable +import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.other.SCAddress +import com.lobstr.stellar.tsmapper.presentation.entities.transaction.operation.soroban.other.SCVal +import kotlinx.parcelize.Parcelize + +// CAP-71 (Protocol 27): a single delegate's signature inside an ADDRESS_WITH_DELEGATES +// credential. Delegates form a recursive tree via [nestedDelegates]. +@Parcelize +data class SorobanDelegateSignature( + val address: SCAddress, + val signature: SCVal, + val nestedDelegates: List +) : Parcelable