Skip to content

Commit

Permalink
AND-5816 Add AptosNetworkProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
Mama1emon committed Jan 16, 2024
1 parent b788994 commit 9d532c0
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.tangem.blockchain.blockchains.aptos.models

import java.math.BigDecimal

internal data class AptosAccountInfo(val sequenceNumber: Long, val balance: BigDecimal)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.tangem.blockchain.blockchains.aptos.models

/**
* Aptos transaction info
*
* @property sequenceNumber sequence number
* @property publicKey wallet public key
* @property sourceAddress source address
* @property destinationAddress destination address
* @property amount amount
* @property gasUnitPrice gas unit price
* @property maxGasAmount max gas amount
* @property expirationTimestamp expiration timestamp in seconds
* @property hash hash of transaction
*
* @author Andrew Khokhlov on 16/01/2024
*/
data class AptosTransactionInfo(
val sequenceNumber: Long,
val publicKey: String,
val sourceAddress: String,
val destinationAddress: String,
val amount: Long,
val gasUnitPrice: Long,
val maxGasAmount: Long,
val expirationTimestamp: Long,
val hash: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.tangem.blockchain.blockchains.aptos.network

import com.tangem.blockchain.blockchains.aptos.models.AptosAccountInfo
import com.tangem.blockchain.blockchains.aptos.models.AptosTransactionInfo
import com.tangem.blockchain.common.NetworkProvider
import com.tangem.blockchain.extensions.Result

/**
* Aptos network provider
*
* @author Andrew Khokhlov on 11/01/2024
*/
internal interface AptosNetworkProvider : NetworkProvider {

/** Get account information by [address] */
suspend fun getAccountInfo(address: String): Result<AptosAccountInfo>

/**
* Get normal gas price.
* Prioritizing transactions isn't supports due to difficult scheme of fee calculating.
*/
suspend fun getGasUnitPrice(): Result<Long>

/**
* Calculate gas price unit that required to send transaction
*
* @param transaction unsigned transaction
*/
suspend fun calculateUsedGasPriceUnit(transaction: AptosTransactionInfo): Result<Long>

/**
* Encode transaction in BCS
*
* @param transaction unsigned transaction
*/
suspend fun encodeTransaction(transaction: AptosTransactionInfo): Result<String>

/** Submit signed transaction [transaction] */
suspend fun submitTransaction(transaction: AptosTransactionInfo): Result<String>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.tangem.blockchain.blockchains.aptos.network

import com.tangem.blockchain.blockchains.aptos.models.AptosAccountInfo
import com.tangem.blockchain.blockchains.aptos.models.AptosTransactionInfo
import com.tangem.blockchain.common.toBlockchainSdkError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.successOr
import com.tangem.blockchain.network.MultiNetworkProvider

/**
* Aptos network service.
* Implementation of [AptosNetworkProvider] that wrap network requests in [MultiNetworkProvider].
*
* @param providers list of [AptosNetworkProvider]
*
* @author Andrew Khokhlov on 11/01/2024
*/
internal class AptosNetworkService(providers: List<AptosNetworkProvider>) : AptosNetworkProvider {

override val baseUrl: String
get() = multiJsonRpcProvider.currentProvider.baseUrl

private val multiJsonRpcProvider = MultiNetworkProvider(providers)

override suspend fun getAccountInfo(address: String): Result<AptosAccountInfo> {
return try {
val accountInfo = multiJsonRpcProvider.performRequest(AptosNetworkProvider::getAccountInfo, address)
.successOr { return it }

Result.Success(accountInfo)
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

override suspend fun getGasUnitPrice(): Result<Long> {
return try {
val gasUnitPrice = multiJsonRpcProvider.performRequest(AptosNetworkProvider::getGasUnitPrice)
.successOr { return it }

Result.Success(gasUnitPrice)
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

override suspend fun calculateUsedGasPriceUnit(transaction: AptosTransactionInfo): Result<Long> {
return try {
val usedGasPriceUnit = multiJsonRpcProvider.performRequest(
request = AptosNetworkProvider::calculateUsedGasPriceUnit,
data = transaction,
)
.successOr { return it }

Result.Success(usedGasPriceUnit)
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

override suspend fun encodeTransaction(transaction: AptosTransactionInfo): Result<String> {
return try {
val hash = multiJsonRpcProvider.performRequest(AptosNetworkProvider::encodeTransaction, transaction)
.successOr { return it }

Result.Success(hash)
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

override suspend fun submitTransaction(transaction: AptosTransactionInfo): Result<String> {
return try {
val hash = multiJsonRpcProvider.performRequest(AptosNetworkProvider::submitTransaction, transaction)
.successOr { return it }

Result.Success(hash)
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.tangem.blockchain.blockchains.aptos.network.converter

import com.tangem.blockchain.blockchains.aptos.models.AptosTransactionInfo
import com.tangem.blockchain.blockchains.aptos.network.request.AptosTransactionBody

/**
* Converter from [AptosTransactionInfo] to [AptosTransactionBody]
*
* @author Andrew Khokhlov on 16/01/2024
*/
internal object AptosTransactionConverter {

private const val TRANSFER_PAYLOAD_TYPE = "entry_function_payload"
private const val TRANSFER_PAYLOAD_FUNCTION = "0x1::aptos_account::transfer"
private const val SIGNATURE_TYPE = "ed25519_signature"

fun convert(from: AptosTransactionInfo): AptosTransactionBody {
return AptosTransactionBody(
sender = from.sourceAddress,
sequenceNumber = from.sequenceNumber.toString(),
expirationTimestamp = from.expirationTimestamp.toString(),
gasUnitPrice = from.gasUnitPrice.toString(),
maxGasAmount = from.maxGasAmount.toString(),
payload = createTransferPayload(from.destinationAddress, from.amount),
signature = from.hash?.let { createSignature(publicKey = from.publicKey, hash = it) },
)
}

private fun createTransferPayload(destination: String, amount: Long): AptosTransactionBody.Payload {
return AptosTransactionBody.Payload(
type = TRANSFER_PAYLOAD_TYPE,
function = TRANSFER_PAYLOAD_FUNCTION,
argumentTypes = listOf(),
arguments = listOf(destination, amount.toString()),
)
}

private fun createSignature(publicKey: String, hash: String): AptosTransactionBody.Signature {
return AptosTransactionBody.Signature(
type = SIGNATURE_TYPE,
publicKey = publicKey,
signature = hash,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.tangem.blockchain.blockchains.aptos.network.provider

import com.tangem.blockchain.blockchains.aptos.models.AptosAccountInfo
import com.tangem.blockchain.blockchains.aptos.models.AptosTransactionInfo
import com.tangem.blockchain.blockchains.aptos.network.AptosApi
import com.tangem.blockchain.blockchains.aptos.network.AptosNetworkProvider
import com.tangem.blockchain.blockchains.aptos.network.converter.AptosTransactionConverter
import com.tangem.blockchain.blockchains.aptos.network.response.AptosResourceBody
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.toBlockchainSdkError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.network.createRetrofitInstance
import okhttp3.Interceptor

internal class AptosJsonRpcNetworkProvider(
override val baseUrl: String,
headerInterceptors: List<Interceptor>,
private val decimals: Int,
) : AptosNetworkProvider {

private val api = createRetrofitInstance(baseUrl = baseUrl, headerInterceptors = headerInterceptors)
.create(AptosApi::class.java)

override suspend fun getAccountInfo(address: String): Result<AptosAccountInfo> {
return try {
val resources = api.getAccountResources(address)

val accountResource = resources.getResource<AptosResourceBody.AccountResource>()?.account
val coinResource = resources.getResource<AptosResourceBody.CoinResource>()?.coinData

if (accountResource != null && coinResource != null) {
Result.Success(
AptosAccountInfo(
sequenceNumber = accountResource.sequenceNumber.toLong(),
balance = coinResource.coin.value.toBigDecimal().movePointLeft(decimals),
),
)
} else {
Result.Failure(BlockchainSdkError.AccountNotFound)
}
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

override suspend fun getGasUnitPrice(): Result<Long> {
return try {
val response = api.estimateGasPrice()

Result.Success(response.normalGasUnitPrice)
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

override suspend fun calculateUsedGasPriceUnit(transaction: AptosTransactionInfo): Result<Long> {
return try {
val requestBody = AptosTransactionConverter.convert(transaction)
val response = api.simulateTransaction(requestBody).firstOrNull()

val usedGasUnit = response?.usedGasUnit?.toLongOrNull()
if (response != null && usedGasUnit != null && response.isSuccess) {
Result.Success(usedGasUnit)
} else {
Result.Failure(BlockchainSdkError.FailedToLoadFee)
}
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

override suspend fun encodeTransaction(transaction: AptosTransactionInfo): Result<String> {
return try {
val requestBody = AptosTransactionConverter.convert(transaction)
val hash = api.encodeSubmission(requestBody)

if (hash.isNotBlank()) {
Result.Success(hash)
} else {
Result.Failure(BlockchainSdkError.FailedToSendException)
}

Result.Success(hash)
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

override suspend fun submitTransaction(transaction: AptosTransactionInfo): Result<String> {
return try {
val requestBody = AptosTransactionConverter.convert(transaction)
val response = api.submitTransaction(requestBody)

if (response.hash.isNotBlank()) {
Result.Success(response.hash)
} else {
Result.Failure(BlockchainSdkError.FailedToSendException)
}
} catch (e: Exception) {
Result.Failure(e.toBlockchainSdkError())
}
}

private inline fun <reified T : AptosResourceBody> List<AptosResourceBody>.getResource(): T? {
return firstNotNullOfOrNull { it as? T }
}
}

0 comments on commit 9d532c0

Please sign in to comment.