Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/publish-maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Publish to Maven (GitHub Pages)

on:
push:

permissions:
contents: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'zulu'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Create local.properties
run: touch local.properties

- name: Set short SHA
run: echo "SHORT_SHA=$(echo $GITHUB_SHA | cut -c1-7)" >> $GITHUB_ENV

- name: Build and publish all modules to local repo
run: ./gradlew publishReleasePublicationToLocalRepository generateRootPom
env:
SHORT_SHA: ${{ env.SHORT_SHA }}

- name: Prepare gh-pages
run: |
REMOTE_URL="https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
git clone --depth 1 --branch gh-pages "$REMOTE_URL" gh-pages 2>/dev/null || {
mkdir gh-pages
cd gh-pages
git init
}

- name: Copy artifacts to gh-pages
run: cp -r build/maven-repo/* gh-pages/

- name: Keep only last 20 versions
run: |
ROOT_PATH="gh-pages/com/github/piratecash/bitcoin-kit-android"
if [ -d "$ROOT_PATH" ]; then
cd "$ROOT_PATH"
ls -dt */ | tail -n +21 | xargs -r rm -rf
fi
for MODULE_PATH in gh-pages/com/github/piratecash/bitcoin-kit-android/*/; do
if [ -d "$MODULE_PATH" ]; then
cd "$MODULE_PATH"
ls -dt */ 2>/dev/null | tail -n +21 | xargs -r rm -rf
cd - > /dev/null
fi
done

- name: Commit and push to gh-pages
run: |
cd gh-pages
git checkout -B gh-pages
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git diff --cached --quiet && echo "No changes" && exit 0
git commit -m "Publish ${{ env.SHORT_SHA }}"
git remote remove origin 2>/dev/null || true
git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git push --force origin gh-pages
Original file line number Diff line number Diff line change
Expand Up @@ -597,13 +597,13 @@ class BitcoinCore(
sealed class KitState {
object Synced : KitState()
class NotSynced(val exception: Throwable) : KitState()
class Syncing(val progress: Double, val substatus: SyncSubstatus? = null) : KitState()
class Syncing(val progress: Double, val substatus: SyncSubstatus? = null, val maxBlockHeight: Int? = null) : KitState()
class ApiSyncing(val transactions: Int) : KitState()

override fun equals(other: Any?) = when {
this is Synced && other is Synced -> true
this is NotSynced && other is NotSynced -> exception == other.exception
this is Syncing && other is Syncing -> this.progress == other.progress && this.substatus == other.substatus
this is Syncing && other is Syncing -> this.progress == other.progress && this.substatus == other.substatus && this.maxBlockHeight == other.maxBlockHeight
this is ApiSyncing && other is ApiSyncing -> this.transactions == other.transactions
else -> false
}
Expand All @@ -623,6 +623,7 @@ class BitcoinCore(
if (this is Syncing) {
result = 31 * result + progress.hashCode()
result = 31 * result + (substatus?.hashCode() ?: 0)
result = 31 * result + (maxBlockHeight?.hashCode() ?: 0)
}
if (this is NotSynced) {
result = 31 * result + exception.hashCode()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class SyncManager(
syncState = if (progress >= 1) {
KitState.Synced
} else {
KitState.Syncing(progress)
KitState.Syncing(progress, maxBlockHeight = maxBlockHeight)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,33 @@ package io.horizontalsystems.bitcoincore.models.converters
import androidx.room.TypeConverter
import io.horizontalsystems.bitcoincore.models.MerkleBlock
import kotlinx.serialization.json.Json
import timber.log.Timber

class MerkleBlockConverter {
private val json = Json { allowStructuredMapKeys = true }

@TypeConverter
fun fromMerkleBlock(block: MerkleBlock?): String? {
return block?.let { Json.encodeToString(MerkleBlock.serializer(), it) }
return block?.let {
try {
json.encodeToString(MerkleBlock.serializer(), it)
} catch (e: Exception) {
val keys = it.associatedTransactionHashes.keys.take(5).map { key -> "${key::class.simpleName}(${key.bytes.contentToString()})" }
Timber.e(e, "Failed to serialize MerkleBlock: hash=${it.blockHash.contentToString()}, txCount=${it.associatedTransactionHashes.size}, keys=$keys")
null
}
}
}

@TypeConverter
fun toMerkleBlock(data: String?): MerkleBlock? {
return data?.let { Json.decodeFromString(MerkleBlock.serializer(), it) }
return data?.let {
try {
json.decodeFromString(MerkleBlock.serializer(), it)
} catch (e: Exception) {
Timber.e(e, "Failed to deserialize MerkleBlock from: ${it.take(200)}")
null
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class GetMerkleBlocksTask(
.d("GetMerkleBlocksTask: Orphan merkle block: hash=${merkleBlock.blockHash.contentToString()}, height=${merkleBlock.height}")
} catch (e: Exception) {
Timber.tag(logTag)
.d("Failed to process merkle block: hash=${merkleBlock.blockHash.contentToString()}, error=${e.message}")
.e(e, "Failed to process merkle block: hash=${merkleBlock.blockHash.contentToString()}, height=${merkleBlock.height}")
listener?.onTaskFailed(this, e)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.horizontalsystems.bitcoincore.transactions

import io.horizontalsystems.bitcoincore.core.IPluginData
import io.horizontalsystems.bitcoincore.extensions.hexToByteArray
import io.horizontalsystems.bitcoincore.io.BitcoinInputMarkable
import io.horizontalsystems.bitcoincore.models.Transaction
import io.horizontalsystems.bitcoincore.managers.BloomFilterManager
import io.horizontalsystems.bitcoincore.models.TransactionDataSortType
import io.horizontalsystems.bitcoincore.serializers.BaseTransactionSerializer
Expand All @@ -9,6 +12,7 @@ import io.horizontalsystems.bitcoincore.storage.UnspentOutput
import io.horizontalsystems.bitcoincore.storage.UtxoFilters
import io.horizontalsystems.bitcoincore.transactions.builder.MutableTransaction
import io.horizontalsystems.bitcoincore.transactions.builder.TransactionBuilder
import io.horizontalsystems.bitcoincore.transactions.builder.SignedTransactionData
import io.horizontalsystems.bitcoincore.transactions.builder.TransactionSigner

class TransactionCreator(
Expand Down Expand Up @@ -66,14 +70,34 @@ class TransactionCreator(
}

suspend fun create(mutableTransaction: MutableTransaction): FullTransaction {
transactionSigner.sign(mutableTransaction)
val signedData = transactionSigner.sign(mutableTransaction)

val fullTransaction = mutableTransaction.build(transactionSerializer)
processAndSend(fullTransaction)
val fullTransaction = if (signedData != null) {
buildFromSignedData(signedData, mutableTransaction)
} else {
mutableTransaction.build(transactionSerializer)
}

processAndSend(fullTransaction)
return fullTransaction
}

private fun buildFromSignedData(
signedData: SignedTransactionData,
mutableTransaction: MutableTransaction
): FullTransaction {
val rawBytes = signedData.serializedTx.hexToByteArray()
val deserialized = transactionSerializer.deserialize(
BitcoinInputMarkable(rawBytes)
)
deserialized.header.apply {
status = Transaction.Status.NEW
isMine = true
isOutgoing = mutableTransaction.transaction.isOutgoing
}
return deserialized
}

private fun processAndSend(transaction: FullTransaction): FullTransaction {
// Always save the transaction first - don't block on peer availability
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.horizontalsystems.bitcoincore.transactions.builder

data class SignedTransactionData(
val serializedTx: String,
val signatures: List<String>
)

interface IFullTransactionSigner {
suspend fun signFullTransaction(mutableTransaction: MutableTransaction): SignedTransactionData
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.horizontalsystems.bitcoincore.managers.SelectedUnspentOutputInfo
import io.horizontalsystems.bitcoincore.managers.SendValueErrors
import io.horizontalsystems.bitcoincore.managers.UnspentOutputQueue
import io.horizontalsystems.bitcoincore.models.Address
import io.horizontalsystems.bitcoincore.models.PublicKey
import io.horizontalsystems.bitcoincore.models.TransactionDataSortType
import io.horizontalsystems.bitcoincore.models.TransactionInput
import io.horizontalsystems.bitcoincore.storage.InputToSign
Expand Down Expand Up @@ -115,14 +116,17 @@ class InputSetter(
var changeInfo: ChangeInfo? = null
unspentOutputInfo.changeValue?.let { changeValue ->
val firstOutput = unspentOutputInfo.outputs.firstOrNull()
val changePubKey: PublicKey
val changeAddress = if (changeToFirstInput && firstOutput != null) {
addressConverter.convert(firstOutput.publicKey, firstOutput.output.scriptType)
changePubKey = firstOutput.publicKey
addressConverter.convert(changePubKey, firstOutput.output.scriptType)
} else {
val changePubKey = publicKeyManager.changePublicKey()
changePubKey = publicKeyManager.changePublicKey()
addressConverter.convert(changePubKey, changeScriptType)
}

mutableTransaction.changeAddress = changeAddress
mutableTransaction.changePublicKey = changePubKey
mutableTransaction.changeValue = changeValue
changeInfo = ChangeInfo(address = changeAddress, value = changeValue)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.horizontalsystems.bitcoincore.transactions.builder

import io.horizontalsystems.bitcoincore.models.Address
import io.horizontalsystems.bitcoincore.models.PublicKey
import io.horizontalsystems.bitcoincore.models.Transaction
import io.horizontalsystems.bitcoincore.models.TransactionOutput
import io.horizontalsystems.bitcoincore.serializers.BaseTransactionSerializer
Expand All @@ -17,6 +18,7 @@ class MutableTransaction(isOutgoing: Boolean = true) {
var recipientValue = 0L
var memo: String? = null
var changeAddress: Address? = null
var changePublicKey: PublicKey? = null
var changeValue = 0L

private val pluginData = mutableMapOf<Byte, ByteArray>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ class TransactionSigner(
private val ecdsaInputSigner: IInputSigner,
private val schnorrInputSigner: ISchnorrInputSigner
) {
suspend fun sign(mutableTransaction: MutableTransaction) {
suspend fun sign(mutableTransaction: MutableTransaction): SignedTransactionData? {
(ecdsaInputSigner as? IFullTransactionSigner)?.let { fullSigner ->
return fullSigner.signFullTransaction(mutableTransaction)
}

// Bunch sign added to support Tangem sdk
if (mutableTransaction.inputsToSign.size > 1 && isAllOutputsOneType(mutableTransaction)) {
if (batchSign(mutableTransaction)) return
if (batchSign(mutableTransaction)) return null
}

mutableTransaction.inputsToSign.forEachIndexed { index, inputToSign ->
Expand All @@ -23,6 +27,8 @@ class TransactionSigner(
ecdsaSign(index, mutableTransaction)
}
}

return null
}

private suspend fun batchSign(mutableTransaction: MutableTransaction): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,6 @@ class BitcoinKit : AbstractKit {
Purpose.BIP44 -> {
bitcoinCore.addRestoreKeyConverter(Bip44RestoreKeyConverter(base58AddressConverter))
bitcoinCore.addRestoreKeyConverter(hodlerPlugin)

if (extendedKey != null) {
bitcoinCore.addRestoreKeyConverter(Bip49RestoreKeyConverter(base58AddressConverter))
bitcoinCore.addRestoreKeyConverter(Bip84RestoreKeyConverter(bech32AddressConverter))
}
}

Purpose.BIP49 -> {
Expand Down
68 changes: 68 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
buildscript {
ext.kotlin_version = '2.0.21'
ext.room_version = '2.6.1'
ext.shortSha = providers.environmentVariable("SHORT_SHA")
.orElse(provider {
def process = new ProcessBuilder("git", "rev-parse", "--short=7", "HEAD")
.directory(rootDir)
.redirectErrorStream(true)
.start()
process.inputStream.text.trim() ?: "unknown"
})
.get()
repositories {
google()
mavenCentral()
Expand All @@ -28,6 +37,65 @@ allprojects {
}
}

def libraryModules = [
'bitcoincore', 'bitcoinkit', 'bitcoincashkit', 'litecoinkit',
'dashkit', 'dashlib', 'dogecoinkit', 'cosantakit', 'piratecashkit',
'ecashkit', 'hodler'
]

subprojects { sub ->
if (libraryModules.contains(sub.name)) {
sub.afterEvaluate {
sub.publishing {
publications.withType(MavenPublication).configureEach {
groupId = "com.github.piratecash.bitcoin-kit-android"
artifactId = sub.name
version = rootProject.ext.shortSha
}
repositories {
maven {
name = "local"
url = uri(rootProject.layout.buildDirectory.dir("maven-repo"))
}
}
}
}
}
}

tasks.register('generateRootPom') {
def repoDir = rootProject.layout.buildDirectory.dir("maven-repo/com/github/piratecash/bitcoin-kit-android/${rootProject.ext.shortSha}")
outputs.dir(repoDir)
doLast {
def dir = repoDir.get().asFile
dir.mkdirs()
def deps = libraryModules.collect { mod ->
""" <dependency>
<groupId>com.github.piratecash.bitcoin-kit-android</groupId>
<artifactId>${mod}</artifactId>
<version>${rootProject.ext.shortSha}</version>
<type>aar</type>
</dependency>"""
}.join('\n')

new File(dir, "bitcoin-kit-android-${rootProject.ext.shortSha}.pom").text = """\
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.piratecash</groupId>
<artifactId>bitcoin-kit-android</artifactId>
<version>${rootProject.ext.shortSha}</version>
<packaging>pom</packaging>
<dependencies>
${deps}
</dependencies>
</project>
"""
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ constraintlayout = "2.1.4"
core = "1.5.0"
espressoCore = "3.2.0"
guava = "33.5.0-android"
hdWalletKitAndroid = "a74b51f"
hdWalletKitAndroid = "8f873a1"
junit = "4.13.2"
junitVersion = "1.1.1"
kotlinxCoroutinesCore = "1.10.1"
Expand Down