-
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
1131055
commit 96a6e7f
Showing
20 changed files
with
366 additions
and
85 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
15 changes: 0 additions & 15 deletions
15
blockchain/src/main/java/com/tangem/blockchain/blockchains/factorn/Fact0rnAddressService.kt
This file was deleted.
Oops, something went wrong.
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.
185 changes: 183 additions & 2 deletions
185
.../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,194 @@ | ||
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.Fact0rnMainNetParams | ||
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 com.tangem.common.extensions.toHexString | ||
import kotlinx.coroutines.async | ||
import kotlinx.coroutines.awaitAll | ||
import kotlinx.coroutines.coroutineScope | ||
import org.bitcoinj.core.SegwitAddress | ||
import org.bitcoinj.core.Sha256Hash | ||
import org.bitcoinj.script.Script | ||
import org.bitcoinj.script.ScriptBuilder | ||
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 { | ||
fun feeDeferred(blocks: Int) = async { | ||
multiProvider.performRequest { getEstimateFee(blocks) } | ||
.map { feeResponse -> | ||
feeResponse.feeInCoinsPer1000Bytes | ||
?.divide(BigDecimal(BYTES_IN_KB)) | ||
?.movePointLeft(blockchain.decimals()) | ||
?: BigDecimal.ZERO | ||
} | ||
} | ||
|
||
val minimalFee = feeDeferred(MINIMAL_NUMBER_OF_CONFIRMATION_BLOCKS).await() | ||
.successOr { return@coroutineScope it } | ||
val normalFee = feeDeferred(NORMAL_NUMBER_OF_CONFIRMATION_BLOCKS).await() | ||
.successOr { return@coroutineScope it } | ||
val priorityFee = feeDeferred(PRIORITY_NUMBER_OF_CONFIRMATION_BLOCKS).await() | ||
.successOr { return@coroutineScope it } | ||
|
||
Result.Success( | ||
BitcoinFee( | ||
minimalPerKb = minimalFee, | ||
normalPerKb = normalFee, | ||
priorityPerKb = priorityFee, | ||
), | ||
) | ||
} | ||
|
||
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, | ||
) | ||
} | ||
} | ||
|
||
private fun addressToScript(address: String): Script = | ||
ScriptBuilder.createOutputScript(SegwitAddress.fromBech32(Fact0rnMainNetParams(), address)) | ||
|
||
private fun addressToScriptHash(address: String): String { | ||
val p2pkhScript = addressToScript(address) | ||
val sha256Hash = Sha256Hash.hash(p2pkhScript.program) | ||
return sha256Hash.reversedArray().toHexString() | ||
} | ||
|
||
private companion object { | ||
const val MINIMAL_NUMBER_OF_CONFIRMATION_BLOCKS = 25 | ||
const val NORMAL_NUMBER_OF_CONFIRMATION_BLOCKS = 10 | ||
const val PRIORITY_NUMBER_OF_CONFIRMATION_BLOCKS = 5 | ||
|
||
/** | ||
* 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.