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.
Setting up the project
Section titled “Setting up the project”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.
Adding a few extensions
Section titled “Adding a few extensions”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.valuePlease note that Kotlin has no
|operator for integers. We combine theSigHashTypeflags with the infix functionor.
Building a Transaction
Section titled “Building a Transaction”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:
- Java getters become properties, so
alice.getPublicKey()is writtenalice.publicKey. - There is no
new, andTransactionBuilder()creates the builder. 50_000.toBigInteger()turns a KotlinIntinto theBigIntegerthat the builder takes.- The library’s checked exceptions, such as
InvalidKeyExceptionfromfromWIF(), need nothrowsclause. It should be noted that they are still thrown, so one still handles the ones that matter.
Working with amounts
Section titled “Working with amounts”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].amountval change = spendingTx.outputs[1].amountval fee = fundingTx.outputs[0].amount - paid - changeLists from Java read with index syntax as well, so
spendingTx.outputs[0]isgetOutputs().get(0). The Change Output is the last Output, as described on the fees and change page.
Tracing a script with a lambda
Section titled “Tracing a script with a lambda”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().
Null safety
Section titled “Null safety”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.

