-
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 1c1242a
Showing
20 changed files
with
404 additions
and
70 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.
164 changes: 162 additions & 2 deletions
164
.../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,173 @@ | ||
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 minimalFee = multiProvider.performRequest { getEstimateFee(MINIMAL_NUMBER_OF_CONFIRMATION_BLOCKS) } | ||
.map { feeResponse -> | ||
feeResponse.feeInCoinsPer1000Bytes | ||
?.divide(BigDecimal(BYTES_IN_KB)) | ||
?.movePointLeft(blockchain.decimals()) | ||
?: BigDecimal.ZERO | ||
} | ||
.successOr { return@coroutineScope it } | ||
|
||
Result.Success( | ||
BitcoinFee( | ||
minimalPerKb = minimalFee, | ||
normalPerKb = minimalFee.multiply(NORMAL_FEE_MULTIPLIER), | ||
priorityPerKb = minimalFee.multiply(PRIORITY_FEE_MULTIPLIER), | ||
), | ||
) | ||
} | ||
|
||
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 companion object { | ||
private const val MINIMAL_NUMBER_OF_CONFIRMATION_BLOCKS = 25 | ||
private val NORMAL_FEE_MULTIPLIER = 2.5.toBigDecimal() | ||
private val PRIORITY_FEE_MULTIPLIER = 5.toBigDecimal() | ||
|
||
/** | ||
* We use 1000, because Electrum node return fee for per 1000 bytes. | ||
*/ | ||
const val BYTES_IN_KB = 1000 | ||
} | ||
} |
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.