-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AND-9157 Fact0rn based on Bitcoin wallet
- Loading branch information
1 parent
ff0b2fa
commit 84ccf34
Showing
21 changed files
with
440 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 23 additions & 2 deletions
25
blockchain/src/main/java/com/tangem/blockchain/blockchains/factorn/Fact0rnAddressService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,36 @@ | ||
package com.tangem.blockchain.blockchains.factorn | ||
|
||
import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressService | ||
import com.tangem.blockchain.common.Blockchain | ||
import com.tangem.blockchain.common.address.AddressService | ||
import com.tangem.common.card.EllipticCurve | ||
import com.tangem.common.extensions.toHexString | ||
import org.bitcoinj.core.SegwitAddress | ||
import org.bitcoinj.core.Sha256Hash | ||
import org.bitcoinj.script.Script | ||
import org.bitcoinj.script.ScriptBuilder | ||
|
||
internal class Fact0rnAddressService : AddressService() { | ||
|
||
private val bitcoinAddressService = BitcoinAddressService(Blockchain.Fact0rn) | ||
|
||
override fun makeAddress(walletPublicKey: ByteArray, curve: EllipticCurve?): String { | ||
TODO("Not yet implemented") | ||
return bitcoinAddressService.makeSegwitAddress(walletPublicKey).value | ||
} | ||
|
||
override fun validate(address: String): Boolean { | ||
TODO("Not yet implemented") | ||
return bitcoinAddressService.validateSegwitAddress(address) | ||
} | ||
|
||
companion object { | ||
|
||
internal fun addressToScript(address: String): Script = | ||
ScriptBuilder.createOutputScript(SegwitAddress.fromBech32(Fact0rnMainNetParams(), address)) | ||
|
||
internal fun addressToScriptHash(address: String): String { | ||
val p2pkhScript = addressToScript(address) | ||
val sha256Hash = Sha256Hash.hash(p2pkhScript.program) | ||
return sha256Hash.reversedArray().toHexString() | ||
} | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
blockchain/src/main/java/com/tangem/blockchain/blockchains/factorn/Fact0rnMainNetParams.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.tangem.blockchain.blockchains.factorn | ||
|
||
import org.bitcoinj.params.MainNetParams | ||
|
||
internal class Fact0rnMainNetParams : MainNetParams() { | ||
|
||
init { | ||
segwitAddressHrp = "fact" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 0 additions & 33 deletions
33
blockchain/src/main/java/com/tangem/blockchain/blockchains/factorn/Fact0rnWalletManager.kt
This file was deleted.
Oops, something went wrong.
168 changes: 166 additions & 2 deletions
168
.../src/main/java/com/tangem/blockchain/blockchains/factorn/network/Fact0rnNetworkService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,177 @@ | ||
package com.tangem.blockchain.blockchains.factorn.network | ||
|
||
import com.tangem.blockchain.common.NetworkProvider | ||
import com.tangem.blockchain.blockchains.bitcoin.BitcoinUnspentOutput | ||
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinAddressInfo | ||
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinFee | ||
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinNetworkProvider | ||
import com.tangem.blockchain.blockchains.factorn.Fact0rnAddressService.Companion.addressToScript | ||
import com.tangem.blockchain.blockchains.factorn.Fact0rnAddressService.Companion.addressToScriptHash | ||
import com.tangem.blockchain.common.BasicTransactionData | ||
import com.tangem.blockchain.common.Blockchain | ||
import com.tangem.blockchain.extensions.* | ||
import com.tangem.blockchain.network.MultiNetworkProvider | ||
import com.tangem.blockchain.network.electrum.ElectrumNetworkProvider | ||
import com.tangem.blockchain.network.electrum.ElectrumUnspentUTXORecord | ||
import com.tangem.blockchain.network.electrum.api.ElectrumResponse | ||
import com.tangem.common.extensions.hexToBytes | ||
import kotlinx.coroutines.async | ||
import kotlinx.coroutines.awaitAll | ||
import kotlinx.coroutines.coroutineScope | ||
import java.math.BigDecimal | ||
import java.util.Calendar | ||
|
||
internal class Fact0rnNetworkService(providers: List<ElectrumNetworkProvider>) : NetworkProvider { | ||
internal class Fact0rnNetworkService( | ||
private val blockchain: Blockchain, | ||
providers: List<ElectrumNetworkProvider>, | ||
) : BitcoinNetworkProvider { | ||
|
||
override val baseUrl: String | ||
get() = multiProvider.currentProvider.baseUrl | ||
|
||
private val multiProvider = MultiNetworkProvider(providers) | ||
|
||
override suspend fun getInfo(address: String): Result<BitcoinAddressInfo> = coroutineScope { | ||
val scriptHash = addressToScriptHash(address) | ||
val balanceDeferred = | ||
async { multiProvider.performRequest(ElectrumNetworkProvider::getAccountBalance, scriptHash) } | ||
val unspentsDeferred = | ||
async { multiProvider.performRequest(ElectrumNetworkProvider::getUnspentUTXOs, scriptHash) } | ||
val balance = balanceDeferred.await().successOr { return@coroutineScope it } | ||
val unspentsUTXOs = unspentsDeferred.await().successOr { return@coroutineScope it } | ||
|
||
val info = BitcoinAddressInfo( | ||
balance = balance.confirmedAmount, | ||
unspentOutputs = createUnspentOutputs( | ||
getUtxoResponseItems = unspentsUTXOs, | ||
address = address, | ||
), | ||
recentTransactions = createRecentTransactions( | ||
utxoResponseItems = unspentsUTXOs, | ||
address = address, | ||
), | ||
hasUnconfirmed = balance.unconfirmedAmount != BigDecimal.ZERO, | ||
) | ||
Result.Success(info) | ||
} | ||
|
||
override suspend fun getFee(): Result<BitcoinFee> = coroutineScope { | ||
val minimalFeeDeferred = async { requestFee(MINIMAL_FEE_BLOCK_AMOUNT) } | ||
val normalFeeDeferred = async { requestFee(NORMAL_FEE_BLOCK_AMOUNT) } | ||
val priorityFeeDeferred = async { requestFee(PRIORITY_FEE_BLOCK_AMOUNT) } | ||
Result.Success( | ||
BitcoinFee( | ||
minimalPerKb = minimalFeeDeferred.await().successOr { return@coroutineScope it }, | ||
normalPerKb = normalFeeDeferred.await().successOr { return@coroutineScope it }, | ||
priorityPerKb = priorityFeeDeferred.await().successOr { return@coroutineScope it }, | ||
), | ||
) | ||
} | ||
|
||
private suspend fun requestFee(blockAmount: Int) = multiProvider | ||
.performRequest { getEstimateFee(blockAmount) } | ||
.map { feeResponse -> | ||
feeResponse.feeInCoinsPer1000Bytes | ||
?.divide(BigDecimal(BYTES_IN_KB)) | ||
?.movePointLeft(blockchain.decimals()) | ||
?: BigDecimal.ZERO | ||
} | ||
|
||
override suspend fun sendTransaction(transaction: String): SimpleResult { | ||
return multiProvider.performRequest(ElectrumNetworkProvider::broadcastTransaction, transaction.hexToBytes()) | ||
.map { SimpleResult.Success } | ||
.successOr { it.toSimpleFailure() } | ||
} | ||
|
||
override suspend fun getSignatureCount(address: String): Result<Int> { | ||
return multiProvider.performRequest( | ||
ElectrumNetworkProvider::getTransactionHistory, | ||
addressToScriptHash(address), | ||
) | ||
.map { Result.Success(it.count()) } | ||
.successOr { it } | ||
} | ||
|
||
private fun createUnspentOutputs( | ||
getUtxoResponseItems: List<ElectrumUnspentUTXORecord>, | ||
address: String, | ||
): List<BitcoinUnspentOutput> = getUtxoResponseItems.map { | ||
val amount = it.value | ||
BitcoinUnspentOutput( | ||
amount = amount, | ||
outputIndex = it.txPos, | ||
transactionHash = it.txHash.hexToBytes(), | ||
outputScript = addressToScript(address).program, | ||
) | ||
} | ||
|
||
private suspend fun createRecentTransactions( | ||
utxoResponseItems: List<ElectrumUnspentUTXORecord>, | ||
address: String, | ||
): List<BasicTransactionData> = coroutineScope { | ||
utxoResponseItems | ||
.filter { !it.isConfirmed } | ||
.map { utxo -> async { multiProvider.performRequest { getTransactionInfo(utxo.txHash) } } } | ||
.awaitAll() | ||
.filterIsInstance<Result.Success<ElectrumResponse.Transaction>>() | ||
.map { result -> | ||
val transaction: ElectrumResponse.Transaction = result.data | ||
val vin = transaction.vin ?: listOf() | ||
val vout = transaction.vout ?: listOf() | ||
val isIncoming = vin.any { it.addresses?.contains(address) == false } | ||
var source = "unknown" | ||
var destination = "unknown" | ||
val amount = if (isIncoming) { | ||
destination = address | ||
vin.firstOrNull() | ||
?.addresses | ||
?.firstOrNull() | ||
?.let { source = it } | ||
val outputs = vout | ||
.find { it.scriptPublicKey?.addresses?.contains(address) == true } | ||
?.value?.toBigDecimal() ?: BigDecimal.ZERO | ||
val inputs = vin | ||
.find { it.addresses?.contains(address) == true } | ||
?.value?.toBigDecimal() ?: BigDecimal.ZERO | ||
outputs - inputs | ||
} else { | ||
source = address | ||
vout.firstOrNull() | ||
?.scriptPublicKey | ||
?.addresses | ||
?.firstOrNull() | ||
?.let { destination = it } | ||
val outputs = vout | ||
.asSequence() | ||
.filter { it.scriptPublicKey?.addresses?.contains(address) == false } | ||
.map { it.value.toBigDecimal() } | ||
.sumOf { it } | ||
val fee = transaction.fee?.toBigDecimal() ?: BigDecimal.ZERO | ||
val feeSatoshi = transaction.feeSatoshi?.toBigDecimal() ?: BigDecimal.ZERO | ||
outputs + fee + feeSatoshi | ||
}.movePointLeft(blockchain.decimals()) | ||
|
||
BasicTransactionData( | ||
balanceDif = if (isIncoming) amount else amount.negate(), | ||
hash = transaction.txid, | ||
date = Calendar.getInstance().apply { | ||
timeInMillis = transaction.blockTime | ||
}, | ||
isConfirmed = false, | ||
destination = destination, | ||
source = source, | ||
) | ||
} | ||
} | ||
|
||
companion object { | ||
const val SUPPORTED_SERVER_VERSION = "1.4" | ||
private const val MINIMAL_FEE_BLOCK_AMOUNT = 8 | ||
private const val NORMAL_FEE_BLOCK_AMOUNT = 4 | ||
private const val PRIORITY_FEE_BLOCK_AMOUNT = 1 | ||
|
||
/** | ||
* We use 1000, because Electrum node return fee for per 1000 bytes. | ||
*/ | ||
const val BYTES_IN_KB = 1000 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 19 additions & 5 deletions
24
.../src/main/java/com/tangem/blockchain/common/assembly/impl/Fact0rnWalletManagerAssembly.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.