Skip to content

Commit 594d174

Browse files
committed
feat: Add multisig example.
1 parent c8d08e9 commit 594d174

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package org.bitcoindevkit
2+
3+
import java.nio.file.Paths
4+
5+
fun main() {
6+
// Add your regtest URL here. Regtest environment must have esplora to run this.
7+
// See here for regtest podman setup https://github.com/thunderbiscuit/podman-regtest-infinity-pro
8+
val REGTEST_URL = "http://127.0.0.1:3002"
9+
10+
//Create 3 wallets
11+
val aliceWallet = getNewWallet(ActiveWalletScriptType.P2WPKH, Network.REGTEST)
12+
val bobWallet = getNewWallet(ActiveWalletScriptType.P2WPKH, Network.REGTEST)
13+
val mattWallet = getNewWallet(ActiveWalletScriptType.P2WPKH, Network.REGTEST)
14+
15+
// Get the public descriptors of each wallet. We only want the derivation path, so we take it out of the string
16+
var alicePublicDescriptor = aliceWallet.publicDescriptor(KeychainKind.EXTERNAL)
17+
alicePublicDescriptor = alicePublicDescriptor.substring(alicePublicDescriptor.indexOf("(") + 1, alicePublicDescriptor.indexOf(")"))
18+
var bobPublicDescriptor = bobWallet.publicDescriptor(KeychainKind.EXTERNAL)
19+
bobPublicDescriptor = bobPublicDescriptor.substring(bobPublicDescriptor.indexOf("(") + 1, bobPublicDescriptor.indexOf(")"))
20+
var mattPublicDescriptor = mattWallet.publicDescriptor(KeychainKind.EXTERNAL)
21+
mattPublicDescriptor = mattPublicDescriptor.substring(mattPublicDescriptor.indexOf("(") + 1, mattPublicDescriptor.indexOf(")"))
22+
23+
// Get the change descriptors of each wallet
24+
var aliceChangeDescriptor = aliceWallet.publicDescriptor(KeychainKind.INTERNAL)
25+
aliceChangeDescriptor = aliceChangeDescriptor.substring(aliceChangeDescriptor.indexOf("(") + 1, aliceChangeDescriptor.indexOf(")"))
26+
var bobChangeDescriptor = bobWallet.publicDescriptor(KeychainKind.INTERNAL)
27+
bobChangeDescriptor = bobChangeDescriptor.substring(bobChangeDescriptor.indexOf("(") + 1, bobChangeDescriptor.indexOf(")"))
28+
var mattChangeDescriptor = mattWallet.publicDescriptor(KeychainKind.INTERNAL)
29+
mattChangeDescriptor = mattChangeDescriptor.substring(mattChangeDescriptor.indexOf("(") + 1, mattChangeDescriptor.indexOf(")"))
30+
31+
// Define the descriptors for a multisig wallet with Alice, Bob and Matt's public descriptors.
32+
val externalDescriptor = Descriptor(
33+
"wsh(multi(2,$alicePublicDescriptor,$bobPublicDescriptor,$mattPublicDescriptor))",
34+
Network.REGTEST
35+
)
36+
val changeDescriptor = Descriptor(
37+
"wsh(multi(2,$aliceChangeDescriptor,$bobChangeDescriptor,$mattChangeDescriptor))",
38+
Network.REGTEST
39+
)
40+
41+
//Create multisig wallet
42+
val connection: Persister = Persister.newSqlite(generateUniquePersistenceFilePath())
43+
val multisigWallet = Wallet(externalDescriptor, changeDescriptor, Network.REGTEST, connection)
44+
45+
val changeAddress = multisigWallet.revealNextAddress(KeychainKind.INTERNAL)
46+
val address = multisigWallet.revealNextAddress(KeychainKind.EXTERNAL)
47+
println("Change address: ${changeAddress.address}")
48+
49+
//Fund this address. Alice, Bob and Matt own funds sent here
50+
println("recieving address: ${address.address}")
51+
52+
//Client
53+
//Wait 40 second for you to send funds before scanning
54+
println("Waiting 40 seconds for funds to arrive here before scanning ${address.address} ...")
55+
Thread.sleep(40000)
56+
57+
val esploraClient = EsploraClient(REGTEST_URL)
58+
val fullScanRequest: FullScanRequest = multisigWallet.startFullScan().build()
59+
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
60+
multisigWallet.applyUpdate(update)
61+
multisigWallet.persist(connection)
62+
println("Balance: ${multisigWallet.balance().total.toSat()}")
63+
//esploraFullScanWallet(multisigWallet, esploraClient,fullScanRequest, connection)
64+
Thread.sleep(40000)
65+
66+
// Create PSBT
67+
//Add your own recipient Address here. (Address to send funds to)
68+
val recipient: Address = Address("bcrt1q645m0j78v9pajdfp0g0w6wacl4v8s7mvrwsjx5", Network.REGTEST)
69+
println("Balance: ${multisigWallet.balance().total.toSat()}")
70+
val psbt: Psbt = TxBuilder()
71+
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4420uL))
72+
.feeRate(FeeRate.fromSatPerVb(22uL))
73+
.finish(multisigWallet)
74+
75+
val psbtTransaction = psbt.extractTx()
76+
println(psbtTransaction.toString())
77+
println("Before any signature ${psbt.serialize()}")
78+
println("Before any signature. Json: ${psbt.jsonSerialize()}")
79+
80+
// Sign Psbt
81+
val signedByAlice = aliceWallet.sign(psbt, SignOptions(
82+
trustWitnessUtxo = false,
83+
assumeHeight = null,
84+
allowAllSighashes = false,
85+
tryFinalize = false,
86+
signWithTapInternalKey = true,
87+
allowGrinding = false
88+
))
89+
println("After signing with aliceWallet ${psbt.serialize()}")
90+
println("After signing with aliceWallet Json ${psbt.jsonSerialize()}")
91+
println("Transaction after signing with wallet1: ${psbt.extractTx().toString()}")
92+
93+
val signedByBob = bobWallet.sign(psbt, SignOptions(
94+
trustWitnessUtxo = false,
95+
assumeHeight = null,
96+
allowAllSighashes = false,
97+
tryFinalize = false,
98+
signWithTapInternalKey = true,
99+
allowGrinding = false
100+
))
101+
println("After signing with bobWallet ${psbt.serialize()}")
102+
println("After signing with bobWallet Json ${psbt.jsonSerialize()}")
103+
println("Transaction after signing with bobWallet: ${psbt.extractTx().toString()}")
104+
105+
// Matt does not need to sign since it is a 2 or 3 multisig. Alice and Bob, have already signed, which satisfies the unlocking script.
106+
val signedByMatt = mattWallet.sign(psbt, SignOptions(
107+
trustWitnessUtxo = false,
108+
assumeHeight = null,
109+
allowAllSighashes = false,
110+
tryFinalize = false,
111+
signWithTapInternalKey = true,
112+
allowGrinding = false
113+
))
114+
println("After signing with mattWallet ${psbt.serialize()}")
115+
println("After signing with mattWallet Json ${psbt.jsonSerialize()}")
116+
println("Transaction after signing with mattWallet: ${psbt.extractTx().toString()}")
117+
118+
multisigWallet.finalizePsbt(psbt)
119+
println("After finalize: ${psbt.serialize()}")
120+
println("After finalize Json: ${psbt.jsonSerialize()}")
121+
println("Transaction after finalize: ${psbt.extractTx().toString()}")
122+
123+
val tx: Transaction = psbt.extractTx()
124+
println("Txid is: ${tx.computeTxid()}")
125+
126+
//Now you can broadcast the transaction
127+
esploraClient.broadcast(tx)
128+
println("Tx was broadcasted")
129+
}
130+
131+
132+
fun esploraFullScanWallet(
133+
wallet: Wallet,
134+
esploraClient: EsploraClient,
135+
fullScanRequest: FullScanRequest,
136+
connection: Persister
137+
) {
138+
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
139+
wallet.applyUpdate(update)
140+
wallet.persist(connection)
141+
println("Balance: ${wallet.balance().total.toSat()}")
142+
}
143+
144+
fun getNewWallet(script: ActiveWalletScriptType, network: Network): Wallet {
145+
val (descriptor, changeDescriptor) = createDescriptorsFromBip32RootKey(
146+
script,
147+
network
148+
)
149+
val connection: Persister = Persister.newSqlite(generateUniquePersistenceFilePath())
150+
val wallet = Wallet(descriptor, changeDescriptor, network, connection)
151+
return wallet
152+
}
153+
154+
fun generateUniquePersistenceFilePath(): String {
155+
val randomSuffix = (1..100000).random() // Generate a unique random number for the path
156+
val currentDirectory = Paths.get("").toAbsolutePath().toString() + "/bdk-jvm/examples/src/main/kotlin/bdk_persistence_$randomSuffix.sqlite"
157+
return currentDirectory
158+
}

0 commit comments

Comments
 (0)