Skip to content

Instantly share code, notes, and snippets.

@denisvasyanin
Created February 21, 2025 13:18
Show Gist options
  • Save denisvasyanin/b107428bdd8a3ae1bea927ea9b14d058 to your computer and use it in GitHub Desktop.
Save denisvasyanin/b107428bdd8a3ae1bea927ea9b14d058 to your computer and use it in GitHub Desktop.
package ru.disav.signet
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import org.bitcoinj.base.Address
import org.bitcoinj.base.AddressParser
import org.bitcoinj.base.BitcoinNetwork
import org.bitcoinj.base.Coin
import org.bitcoinj.base.Sha256Hash
import org.bitcoinj.core.Context
import org.bitcoinj.core.Transaction
import org.bitcoinj.core.TransactionConfidence
import org.bitcoinj.core.TransactionOutPoint
import org.bitcoinj.core.UTXO
import org.bitcoinj.crypto.DumpedPrivateKey
import org.bitcoinj.crypto.ECKey
import org.bitcoinj.script.ScriptBuilder
import org.bouncycastle.util.encoders.Hex
import ru.disav.signet.ui.theme.SignetTheme
/**
* {
* "txid": "7365cc68f64ba5d2b092d2b9a452ba2473641f5fbc2ed35f1726ec2672513718",
* "vout": 0,
* "status": {
* "confirmed": true,
* "block_height": 213060,
* "block_hash": "0000004df88f08f785ede857dd02e3941abecf82309a5eae5a04a57b9c7b3189",
* "block_time": 1726232391
* },
* "value": 10000
* },
* {
* "txid": "44e95d4e29eb1917ff393c624883b2003c7517334898ccb43e65a6887f288271",
* "vout": 0,
* "status": {
* "confirmed": true,
* "block_height": 213060,
* "block_hash": "0000004df88f08f785ede857dd02e3941abecf82309a5eae5a04a57b9c7b3189",
* "block_time": 1726232391
* },
* "value": 852
* }
*/
object TransactionFactory {
fun create(
sendAmount: Coin,
changeAmount: Coin,
ecKey: ECKey,
addressReceiver: Address,
addressChange: Address,
utxos: List<UTXO>
): Transaction {
val transaction = Transaction()
transaction.addOutput(sendAmount, addressReceiver)
transaction.addOutput(changeAmount, addressChange)
for (utxoItem in utxos) {
val outPoint = TransactionOutPoint(utxoItem.index, utxoItem.hash)
transaction.addSignedInput(
/* prevOut = */ outPoint,
/* scriptPubKey = */ ScriptBuilder.createOutputScript(addressChange),
/* amount = */ utxoItem.value,
/* sigKey = */ ecKey,
)
}
transaction.purpose = Transaction.Purpose.USER_PAYMENT
return transaction
}
}
interface UTXOSource {
fun load(address: Address): List<UTXO>
}
class DummyUTXOSource : UTXOSource {
override fun load(address: Address) = listOf(
UTXO(
/* hash = */ Sha256Hash.wrap("44e95d4e29eb1917ff393c624883b2003c7517334898ccb43e65a6887f288271"),
/* index = */ 0,
/* value = */ Coin.valueOf(852),
/* height = */ 213060,
/* coinbase = */ false,
/* script = */ ScriptBuilder.createOutputScript(address)
),
UTXO(
/* hash = */ Sha256Hash.wrap("7365cc68f64ba5d2b092d2b9a452ba2473641f5fbc2ed35f1726ec2672513718"),
/* index = */ 1,
/* value = */ Coin.valueOf(10000),
/* height = */ 213060,
/* coinbase = */ false,
/* script = */ ScriptBuilder.createOutputScript(address)
)
)
}
// curl -X POST -sSLd "010000000001027182287f88a6653eb4cc98483317753c00b28348623c39ff1719eb294e5de9440000000000ffffffff1837517226ec26175fd32ebc5f1f647324ba52a4b9d292b0d2a54bf668cc65730100000000ffffffff02e80300000000000016001412f52f43e3a5df83e27b6878f9a9a5201fddc49a18260000000000001600148340734e760b072739b739fcfe67934a7cd81d1a02483045022100932c77113410843e78c20c3ef3c2163ff68855e0b32211e30e5bedbcdcf9ce8102201030ffb89a161f2e740f62c629e07746c5e33822a3f4f48c16559d929436454d0121023b3f5b57495a366965da047e608a438e71ebcfe1e1c08257c67fa8990ee9426b024730440220011c0161584421a516d12c10871e8361ee76fe54b3f621c274e373d015d1847a02204214bd2147c2822c1d873541150810b9b986dba69bc0646a9b877ea8b3bbb1180121023b3f5b57495a366965da047e608a438e71ebcfe1e1c08257c67fa8990ee9426b00000000" "https://mempool.space/api/tx"
private const val MINER_FEE = 1000L
private const val RECEIVER_ADDRESS = "tb1qzt6j7slr5h0c8cnmdpu0n2d9yq0am3y69rfu6j"
private const val CHANGE_ADDRESS = "tb1qsdq8xnnkpvrjwwdh8870ueunff7ds8g63y8ep5"
private const val PRIVATE_KEY = "cRfMHmheNWpA9L5dn9SvUbWgzEydiLxz6hDQbboAx2AyaJ5ASpY6"
fun main() {
val addressReceiver = AddressParser
.getDefault(BitcoinNetwork.SIGNET)
.parseAddress(RECEIVER_ADDRESS)
val addressChange = AddressParser
.getDefault(BitcoinNetwork.SIGNET)
.parseAddress(CHANGE_ADDRESS)
val utxos = DummyUTXOSource().load(addressChange)
val totalAmount = utxos.sumOf { it.value.value }
val amountToSend = Coin.valueOf(1000)
val minerFee = Coin.valueOf(MINER_FEE)
val change = Coin
.valueOf(totalAmount)
.minus(amountToSend)
.minus(minerFee)
val ecKey = DumpedPrivateKey
.fromBase58(BitcoinNetwork.SIGNET, PRIVATE_KEY)
.key
val transaction = TransactionFactory.create(amountToSend, change, ecKey, addressReceiver, addressChange, utxos)
Context.getOrCreate()
transaction.confidence.setSource(TransactionConfidence.Source.SELF);
println(transaction)
println(Hex.toHexString(transaction.bitcoinSerialize()))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment