Skip to content

Using Bitcoin4J from Kotlin

Bitcoin4J is plain Java, so it works from Kotlin without any wrapper. Every page in these docs applies, and the Kotlin code reads much like the Java. In this section we will build the Transaction from your first transaction once more, in Kotlin, and point out the few places where the two languages differ.

In a Gradle project with the Kotlin plugin, the dependency is the same as for Java.

plugins {
kotlin("jvm") version "2.4.21"
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.twostack:bitcoin4j:1.8.0")
}
kotlin {
jvmToolchain(17)
}

The example project behind these pages compiles its Kotlin tests for JVM 17. Any target from 11 upwards works, since that is what the library itself needs.

Kotlin extension functions are a good way of fixing choices once, in one place. Here we fix the network and the usual Sighash type.

// We make the network explicit once, in one place, with a small extension.
fun PrivateKey.testnetAddress(): Address =
Address.fromKey(NetworkAddressType.TEST_PKH, publicKey)
val SIGHASH_ALL_FORKID = SigHashType.ALL.value or SigHashType.FORKID.value

Please note that Kotlin has no | operator for integers. We combine the SigHashType flags with the infix function or.

val alice = PrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs")
val aliceAddress = alice.testnetAddress()
val bobAddress = Address.fromString(NetworkType.TEST, "mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc")
val fundingTx = Fixtures.fundingTransaction(P2PKHLockBuilder(aliceAddress), 100_000)
// There are no checked exceptions to declare, since Kotlin does not have them.
val spendingTx = TransactionBuilder()
.spendFromTransaction(
TransactionSigner(SIGHASH_ALL_FORKID, alice), fundingTx, 0,
TransactionInput.MAX_SEQ_NUMBER, P2PKHUnlockBuilder(alice.publicKey)
)
.spendTo(P2PKHLockBuilder(bobAddress), 50_000.toBigInteger())
.sendChangeTo(aliceAddress)
.build(true)

Referring to the preceding example, we notice a few things:

  1. Java getters become properties, so alice.getPublicKey() is written alice.publicKey.
  2. There is no new, and TransactionBuilder() creates the builder.
  3. 50_000.toBigInteger() turns a Kotlin Int into the BigInteger that the builder takes.
  4. The library’s checked exceptions, such as InvalidKeyException from fromWIF(), need no throws clause. It should be noted that they are still thrown, so one still handles the ones that matter.

The builder hands amounts back as BigInteger, and Kotlin’s standard library gives BigInteger the usual arithmetic operators.

// BigInteger comes with Kotlin operators, so the arithmetic reads naturally.
val paid = spendingTx.outputs[0].amount
val change = spendingTx.outputs[1].amount
val fee = fundingTx.outputs[0].amount - paid - change

Lists from Java read with index syntax as well, so spendingTx.outputs[0] is getOutputs().get(0). The Change Output is the last Output, as described on the fees and change page.

Java interfaces with a single method take a Kotlin lambda, which makes the interpreter’s trace callback quite compact.

val steps = mutableListOf<String>()
val script = ScriptBuilder().number(2).number(3).op(OP_ADD).build()
// ScriptTraceCallback is a Java interface with a single method, so a Kotlin lambda fits.
Interpreter.executeScript(
null, 0, script, LinkedList(), Coin.ZERO,
EnumSet.of(Script.VerifyFlag.UTXO_AFTER_GENESIS), null,
ScriptTraceCallback { _, _, name, stack, _ -> steps += "$name -> ${stack.size}" }
)

The interpreter page explains the parameters of executeScript().

The library has no nullability annotations, so Kotlin sees its return types as platform types (Address!). The compiler lets us treat them as non-null, and a null shows up as an exception at run time. A few methods do return null on purpose, e.g. ScriptTemplateRegistry.identifyScript() and Script.getScriptType(). For those, the developer is encouraged to declare the type as nullable, as in val template: ScriptTemplate? = ..., so that the compiler makes us check it.