ℹ️ NOTE: This section has been recently added to the course and is an early draft that may still be awaiting review. Caveat reader.
Partially Signed Bitcoin Transactions (PSBTs) are the newest way to vary the creation of basic Bitcoin transactions. They do so by introducing collaboration into every step of the process, allowing people (or programs) to not just authenticate transactions together (as with multisigs), but also to easily create, fund, and broadcast collaboratively.
⚠️ VERSION WARNING: This is an innovation from Bitcoin Core v 0.17.0. Earlier versions of Bitcoin Core will not be able to work with the PSBT while it is in progress (though they will still be able to recognize the final transaction). Some updates and upgrades for PSBTs have continued through 0.20.0.
Multisignatures were great for the very specific case of jointly holding funds and setting rules for whom among the joint signers could authenticate the use of those funds. There are many use cases, such as: a spousal joint bank account (a 1-of-2 signature); a fiduciary requirement for dual control (a 2-of-2 signature); and an escrow (a 2-of-3 signature).
📖 What is a PSBT? As the name suggests, a PSBT is a transaction that has not been fully signed. That's important, because once a transaction is signed, its content is locked in. BIP174 defined an abstracted methodology for putting PSBTs together that describes and standardizes roles in their collaborative creation. A Creator proposes a transaction; one or more Updaters supplement it; and one or more Signers authenticate it; before a Finalizer completes it; and an Extracter turns it into a transaction for the Bitcoin network. There may also be a Combiner who merges parallel PSBTs from different users.
PSBTs may initially look sort of the same as multi-sigs because they have a single overlapping bit of functionality: the ability to jointly sign a transaction. However, they were created for a totally different use case. PSBTs recognize the need for multiple programs to jointly create a transaction for a number of different reasons, and they provide a regularized format for doing so. They're especially useful for use cases involving hardware wallets (for which, see §7.3), which are protected from full access to the internet and tend to have minimal transaction history.
In general, PSBTs provide a number of functional elements that improve this use case:
- They provide a standard for collaboratively creating transactions, whereas previous methodologies (including the multi-sig one from the previous chapter) were implementation dependent.
- They support a wider variety of use cases, including simple joint funding.
- They support hardware wallets and other cases where a node may not have full transaction history.
- They optionally allow for the combination of non-serialized transactions, not requiring an ever-bigger hex code to be passed from user to user.
PSBTs do their work by supplementing normal transaction information with a set of inputs and outputs, each of which defines everything you need to know about those UTXOs, so that even an airgapped wallet can make an informed decision about signatures. Thus, an input lists out the amount of money in a UTXO and what needs to be done to spend it, while an output does the same for the UTXOs it's creating.
This first section will outline the standard PSBT process of: Creator, Updater, Signer, Finalizer, Extractor. It'll do so from one machine, which will sort of make this look like a convoluted way to create a raw transaction. But, have faith, there's a purpose to this! §7.2 and §7.3 will show some real-life examples of using PSBTs and will turn this simple system into a collaborative process shared between multiple machines that has real effects and creates real opportunities.
The easiest way to create a PSBT is to take an existing transaction and use converttopsbt
to turn it into a PSBT. This is certainly not the best way since it requires you to make a transaction for one format (a raw transaction) then convert it to another (PSBT), but if you've got old software that can only generate a raw transaction, you may need to use it.
You just create your raw transaction normally:
$ utxo_txid_1=$(bitcoin-cli listunspent | jq -r '.[0] | .txid')
$ utxo_vout_1=$(bitcoin-cli listunspent | jq -r '.[0] | .vout')
$ utxo_txid_2=$(bitcoin-cli listunspent | jq -r '.[1] | .txid')
$ utxo_vout_2=$(bitcoin-cli listunspent | jq -r '.[1] | .vout')
$ echo $utxo_txid_1 $utxo_vout_1 $utxo_txid_2 $utxo_vout_2
c6de60427b28d8ec8102e49771e5d0348fc3ef6a5bf02eb864ec745105a6951b 1 8748eff5f12ca886e3603d9e30227dcb3f0332e0706c4322fec96001f7c7f41c 0
$ recipient=tb1qcaedd724gts3aug73m78c7nfsv9d8zs9q6h2kd
$ rawtxhex=$(bitcoin-cli -named createrawtransaction inputs='''[ { "txid": "'$utxo_txid_1'", "vout": '$utxo_vout_1' }, { "txid": "'$utxo_txid_2'", "vout": '$utxo_vout_2' } ]''' outputs='''{ "'$recipient'": 0.0000065 }''')
Then you convert it:
$ psbt=$(bitcoin-cli -named converttopsbt hexstring=$rawtxhex)
$ echo $psbt
cHNidP8BAHsCAAAAAhuVpgVRdOxkuC7wW2rvw4800OVxl+QCgezYKHtCYN7GAQAAAAD/////HPTH9wFgyf4iQ2xw4DIDP8t9IjCePWDjhqgs8fXvSIcAAAAAAP////8BigIAAAAAAAAWABTHctb5VULhHvEejvx8emmDCtOKBQAAAAAAAAAA
You'll note that the PSBT encoding looks very different from the transaction hex.
But if you can, you want to create the PSBT directly instead ...
The first methodology for creating a PSBT without going through another format is the PSBT-equivalent of createrawtransaction
. It's called createpsbt
and it gives you maximal control at the cost of maximal labor and the maximal opportunity for error.
The CLI should look quite familiar, just with a new RPC command:
$ psbt_1=$(bitcoin-cli -named createpsbt inputs='''[ { "txid": "'$utxo_txid_1'", "vout": '$utxo_vout_1' }, { "txid": "'$utxo_txid_2'", "vout": '$utxo_vout_2' } ]''' outputs='''{ "'$recipient'": 0.0000065 }''')
The Bitcoin Core team made sure that createpsbt
worked much like createrawtransaction
, so you don't need to learn a different creation format.
You can verify that the new PSBT is the same as the one created by converttopsbt
:
$ echo $psbt_1
cHNidP8BAHsCAAAAAhuVpgVRdOxkuC7wW2rvw4800OVxl+QCgezYKHtCYN7GAQAAAAD/////HPTH9wFgyf4iQ2xw4DIDP8t9IjCePWDjhqgs8fXvSIcAAAAAAP////8BigIAAAAAAAAWABTHctb5VULhHvEejvx8emmDCtOKBQAAAAAAAAAA
$ if [ "$psbt" == "$psbt_1" ]; then echo "PSBTs are equal"; else echo "PSBTs are not equal"; fi
PSBTs are equal
So what does your PSBT actually look like? You can see that with the decodepsbt
command:
$ bitcoin-cli -named decodepsbt psbt=$psbt
{
"tx": {
"txid": "ea73a631b456d2b041ed73bf5767946408c6ff067716929a68ecda2e3e4de6d3",
"hash": "ea73a631b456d2b041ed73bf5767946408c6ff067716929a68ecda2e3e4de6d3",
"version": 2,
"size": 123,
"vsize": 123,
"weight": 492,
"locktime": 0,
"vin": [
{
"txid": "c6de60427b28d8ec8102e49771e5d0348fc3ef6a5bf02eb864ec745105a6951b",
"vout": 1,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967295
},
{
"txid": "8748eff5f12ca886e3603d9e30227dcb3f0332e0706c4322fec96001f7c7f41c",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.00000650,
"n": 0,
"scriptPubKey": {
"asm": "0 c772d6f95542e11ef11e8efc7c7a69830ad38a05",
"hex": "0014c772d6f95542e11ef11e8efc7c7a69830ad38a05",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"tb1qcaedd724gts3aug73m78c7nfsv9d8zs9q6h2kd"
]
}
}
]
},
"unknown": {
},
"inputs": [
{
},
{
}
],
"outputs": [
{
}
]
}
It's important to note that even though we've defined the fundamentals of the transaction: the vins
of where the money is coming from and the vouts
of where it's going to, we haven't yet defined the inputs
and outputs
that are the heart of a PSBT and that are required for offline users to assess them. This is expected: the role of the Creator as defined in BIP174 is to outline the transaction, while the role of the Updater is to start filling in the PSBT-specific data. (Other commands combine the Creator and Updater roles, but createpsbt
doesn't because it doesn't have access to your wallet.)
You can also use the analyzepsbt
command to look at its current state:
standup@btctest20:~$ bitcoin-cli -named analyzepsbt psbt=$psbt
{
"inputs": [
{
"has_utxo": false,
"is_final": false,
"next": "updater"
},
{
"has_utxo": false,
"is_final": false,
"next": "updater"
}
],
"next": "updater"
}
Similarly, analyzepsbt
shows us a PSBT that needs work. We get a look at each of the two inputs
(corresponding to the two vins
), and neither one has the information it needs.
There is a utxoupdatepsbt
command that can be used to Update UTXOs, importing their descriptor information by hand, but you don't want to use it unless you have a use case where you don't have all of that information in the wallets of everyone who will be signing the PSBT.
ℹ️ NOTE: If you choose to Update the PSBT with
utxoupdatepsbt
, you would still need to usewalletprocesspsbt
to Sign it: it's the only Signer-role command for PSBTs that's available inbitcoin-cli
.
Instead, you should use walletprocesspsbt
, which will Update, Sign, and Finalize:
$ bitcoin-cli walletprocesspsbt $psbt
{
"psbt": "cHNidP8BAHsCAAAAAhuVpgVRdOxkuC7wW2rvw4800OVxl+QCgezYKHtCYN7GAQAAAAD/////HPTH9wFgyf4iQ2xw4DIDP8t9IjCePWDjhqgs8fXvSIcAAAAAAP////8BigIAAAAAAAAWABTHctb5VULhHvEejvx8emmDCtOKBQAAAAAAAQEfAQAAAAAAAAAWABRsRdOvqHYghsS9dtinGsfJduGRlgEIawJHMEQCIAqJbxz6dBzNpfaDu4XZXb+DbDkM3UWnhezh9UdmeVghAiBRxMlW2o0wEtphtUZRWIiJOaGtXfsQbB4lovkvE4eRIgEhArrDpkX9egpTfGJ6039faVBYxY0ZzrADPpE/Gpl14A3uAAEBH0gDAAAAAAAAFgAU1ZEJG4B0ojde2ZhanEsY7+z9QWUBCGsCRzBEAiB+sNNCO4xiFQ+DoHVrqqk9yM0V4H9ZSyExx1PW7RbjsgIgUeWkQ3L7aAv1xIe7h+8PZb8ECsXg1UzbtPW8wd2qx0UBIQKIO7VGPjfVUlLYs9XCFBsAezfIp9tiEfdclVrMXqMl6wAA",
"complete": true
}
Obviously, you're going to need to save that psbt
information using jq
:
$ psbt_f=$(bitcoin-cli walletprocesspsbt $psbt | jq -r '.psbt')
You can see the inputs
have now been filled in:
$ bitcoin-cli decodepsbt $psbt_f
{
"tx": {
"txid": "ea73a631b456d2b041ed73bf5767946408c6ff067716929a68ecda2e3e4de6d3",
"hash": "ea73a631b456d2b041ed73bf5767946408c6ff067716929a68ecda2e3e4de6d3",
"version": 2,
"size": 123,
"vsize": 123,
"weight": 492,
"locktime": 0,
"vin": [
{
"txid": "c6de60427b28d8ec8102e49771e5d0348fc3ef6a5bf02eb864ec745105a6951b",
"vout": 1,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967295
},
{
"txid": "8748eff5f12ca886e3603d9e30227dcb3f0332e0706c4322fec96001f7c7f41c",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.00000650,
"n": 0,
"scriptPubKey": {
"asm": "0 c772d6f95542e11ef11e8efc7c7a69830ad38a05",
"hex": "0014c772d6f95542e11ef11e8efc7c7a69830ad38a05",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"tb1qcaedd724gts3aug73m78c7nfsv9d8zs9q6h2kd"
]
}
}
]
},
"unknown": {
},
"inputs": [
{
"witness_utxo": {
"amount": 0.00000001,
"scriptPubKey": {
"asm": "0 6c45d3afa8762086c4bd76d8a71ac7c976e19196",
"hex": "00146c45d3afa8762086c4bd76d8a71ac7c976e19196",
"type": "witness_v0_keyhash",
"address": "tb1qd3za8tagwcsgd39awmv2wxk8e9mwryvktqmkkg"
}
},
"final_scriptwitness": [
"304402200a896f1cfa741ccda5f683bb85d95dbf836c390cdd45a785ece1f54766795821022051c4c956da8d3012da61b5465158888939a1ad5dfb106c1e25a2f92f1387912201",
"02bac3a645fd7a0a537c627ad37f5f695058c58d19ceb0033e913f1a9975e00dee"
]
},
{
"witness_utxo": {
"amount": 0.00000840,
"scriptPubKey": {
"asm": "0 d591091b8074a2375ed9985a9c4b18efecfd4165",
"hex": "0014d591091b8074a2375ed9985a9c4b18efecfd4165",
"type": "witness_v0_keyhash",
"address": "tb1q6kgsjxuqwj3rwhkenpdfcjccalk06st9z0k0kh"
}
},
"final_scriptwitness": [
"304402207eb0d3423b8c62150f83a0756baaa93dc8cd15e07f594b2131c753d6ed16e3b2022051e5a44372fb680bf5c487bb87ef0f65bf040ac5e0d54cdbb4f5bcc1ddaac74501",
"02883bb5463e37d55252d8b3d5c2141b007b37c8a7db6211f75c955acc5ea325eb"
]
}
],
"outputs": [
{
}
],
"fee": 0.00000191
}
Or to be more precise: (1) the PSBT has been updated with the witness_utxo
information; (2) the PSBT has been signed; and (3) the PSBT has been finalized.
If you think that there should be a command that's the equivalent of fundrawtransaction
, you'll be pleased to know there is: walletcreatefundedpsbt
. You could use it just the same as createpsbt
:
$ bitcoin-cli -named walletcreatefundedpsbt inputs='''[ { "txid": "'$utxo_txid_1'", "vout": '$utxo_vout_1' }, { "txid": "'$utxo_txid_2'", "vout": '$utxo_vout_2' } ]''' outputs='''{ "'$recipient'": 0.0000065 }'''
{
"psbt": "cHNidP8BAOwCAAAABBuVpgVRdOxkuC7wW2rvw4800OVxl+QCgezYKHtCYN7GAQAAAAD/////HPTH9wFgyf4iQ2xw4DIDP8t9IjCePWDjhqgs8fXvSIcAAAAAAP/////uFwerANKjyVK6WaR7gzlX+lOf+ORsfjP5LYCSNIbhaAAAAAAA/v///4XjOeey0NyGpJYpszNWF8AFNiuFaWsjkOrk35Jp+9kKAAAAAAD+////AtYjEAAAAAAAFgAUMPsier2ey1eH48oGqrbbYGzNHgKKAgAAAAAAABYAFMdy1vlVQuEe8R6O/Hx6aYMK04oFAAAAAAABAR8BAAAAAAAAABYAFGxF06+odiCGxL122Kcax8l24ZGWIgYCusOmRf16ClN8YnrTf19pUFjFjRnOsAM+kT8amXXgDe4Q1gQ4AAAAAIABAACADgAAgAABAR9IAwAAAAAAABYAFNWRCRuAdKI3XtmYWpxLGO/s/UFlIgYCiDu1Rj431VJS2LPVwhQbAHs3yKfbYhH3XJVazF6jJesQ1gQ4AAAAAIABAACADAAAgAABAIwCAAAAAdVmsvkSBmfeHqNAe/wDCQ5lEp9F/587ftzCD1UL60nMAQAAABcWABRzFxRJfFPl8FJ6SxjAJzy3mCAMXf7///8CQEIPAAAAAAAZdqkUf0NzebzGbEB0XtwYkeprODDhl12IrMEwLQAAAAAAF6kU/d+kMX6XijmD+jWdUrLZlJUnH2iHPhQbACIGA+/e40wACf0XXzsgteWlUX/V0WdG8uY1tEYXra/q68OIENYEOAAAAACAAAAAgBIAAIAAAQEfE4YBAAAAAAAWABTVkQkbgHSiN17ZmFqcSxjv7P1BZSIGAog7tUY+N9VSUtiz1cIUGwB7N8in22IR91yVWsxeoyXrENYEOAAAAACAAQAAgAwAAIAAIgICKMavAB+71Adqsbf+XtC1g/OlmLEuTp3U0axyeu/LAI0Q1gQ4AAAAAIABAACAGgAAgAAA",
"fee": 0.00042300,
"changepos": 0
}
However, the big advantage is that you can use it to self-fund by leaving out the inputs
, just like fundrawtransaction
.
$ psbt_new=$(bitcoin-cli -named walletcreatefundedpsbt inputs='''[]''' outputs='''{ "'$recipient'": 0.0000065 }''' | jq -r '.psbt')
$ bitcoin-cli decodepsbt $psbt_new
{
"tx": {
"txid": "9f2c6205ac797c1020f7f261e3ab71cd0699ff4b1a8934f68b273c71547e235f",
"hash": "9f2c6205ac797c1020f7f261e3ab71cd0699ff4b1a8934f68b273c71547e235f",
"version": 2,
"size": 154,
"vsize": 154,
"weight": 616,
"locktime": 0,
"vin": [
{
"txid": "8748eff5f12ca886e3603d9e30227dcb3f0332e0706c4322fec96001f7c7f41c",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967294
},
{
"txid": "68e1863492802df9337e6ce4f89f53fa5739837ba459ba52c9a3d200ab0717ee",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967294
}
],
"vout": [
{
"value": 0.00971390,
"n": 0,
"scriptPubKey": {
"asm": "0 09a74ef0bae4d68b0b2ec9a7c4557a2b5c85bd8b",
"hex": "001409a74ef0bae4d68b0b2ec9a7c4557a2b5c85bd8b",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"tb1qpxn5au96untgkzewexnug4t69dwgt0vtfahcv6"
]
}
},
{
"value": 0.00000650,
"n": 1,
"scriptPubKey": {
"asm": "0 c772d6f95542e11ef11e8efc7c7a69830ad38a05",
"hex": "0014c772d6f95542e11ef11e8efc7c7a69830ad38a05",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"tb1qcaedd724gts3aug73m78c7nfsv9d8zs9q6h2kd"
]
}
}
]
},
"unknown": {
},
"inputs": [
{
"witness_utxo": {
"amount": 0.00000840,
"scriptPubKey": {
"asm": "0 d591091b8074a2375ed9985a9c4b18efecfd4165",
"hex": "0014d591091b8074a2375ed9985a9c4b18efecfd4165",
"type": "witness_v0_keyhash",
"address": "tb1q6kgsjxuqwj3rwhkenpdfcjccalk06st9z0k0kh"
}
},
"bip32_derivs": [
{
"pubkey": "02883bb5463e37d55252d8b3d5c2141b007b37c8a7db6211f75c955acc5ea325eb",
"master_fingerprint": "d6043800",
"path": "m/0'/1'/12'"
}
]
},
{
"non_witness_utxo": {
"txid": "68e1863492802df9337e6ce4f89f53fa5739837ba459ba52c9a3d200ab0717ee",
"hash": "68e1863492802df9337e6ce4f89f53fa5739837ba459ba52c9a3d200ab0717ee",
"version": 2,
"size": 140,
"vsize": 140,
"weight": 560,
"locktime": 1774654,
"vin": [
{
"txid": "cc49eb0b550fc2dc7e3b9fff459f12650e0903fc7b40a31ede670612f9b266d5",
"vout": 1,
"scriptSig": {
"asm": "0014731714497c53e5f0527a4b18c0273cb798200c5d",
"hex": "160014731714497c53e5f0527a4b18c0273cb798200c5d"
},
"sequence": 4294967294
}
],
"vout": [
{
"value": 0.01000000,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 7f437379bcc66c40745edc1891ea6b3830e1975d OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9147f437379bcc66c40745edc1891ea6b3830e1975d88ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"ms7ruzvL4atCu77n47dStMb3of6iScS8kZ"
]
}
},
{
"value": 0.02961601,
"n": 1,
"scriptPubKey": {
"asm": "OP_HASH160 fddfa4317e978a3983fa359d52b2d99495271f68 OP_EQUAL",
"hex": "a914fddfa4317e978a3983fa359d52b2d99495271f6887",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2NGParh82hE2Zif5PVK3AfLpYhfwF5FyRGr"
]
}
}
]
},
"bip32_derivs": [
{
"pubkey": "03efdee34c0009fd175f3b20b5e5a5517fd5d16746f2e635b44617adafeaebc388",
"master_fingerprint": "d6043800",
"path": "m/0'/0'/18'"
}
]
}
],
"outputs": [
{
"bip32_derivs": [
{
"pubkey": "029bb586a52657dd98852cecef78552a4e21d081a7a30e4008ce9b419840d4deac",
"master_fingerprint": "d6043800",
"path": "m/0'/1'/27'"
}
]
},
{
}
],
"fee": 0.00028800
}
As you can see it Created the PSBT, and then Updated it with all the information it could find locally.
From there, you need to use walletprocesspsbt
to Finalize, as usual:
$ psbt_new_f=$(bitcoin-cli walletprocesspsbt $psbt_new | jq -r '.psbt')
Afterward, an analysis will show it's about ready to go too:
$ bitcoin-cli analyzepsbt $psbt_new_f
{
"inputs": [
{
"has_utxo": true,
"is_final": true,
"next": "extractor"
},
{
"has_utxo": true,
"is_final": true,
"next": "extractor"
}
],
"estimated_vsize": 288,
"estimated_feerate": 0.00100000,
"fee": 0.00028800,
"next": "extractor"
}
Now would you really want to use walletcreatefundedpsbt
if you were creating a bitcoin-cli
program? Probably not. But it's the same analysis as whether to use fundrawtransaction
. Do you let Bitcoin Core do the analysis and calculation and decisions, or do you take that on yourself?
To finalize the PSBT, you use finalizepsbt
, which will turn your PSBT back into hex. (It'll also take on the Finalizer role, if that didn't happen already.)
$ bitcoin-cli finalizepsbt $psbt_f
{
"hex": "020000000001021b95a6055174ec64b82ef05b6aefc38f34d0e57197e40281ecd8287b4260dec60100000000ffffffff1cf4c7f70160c9fe22436c70e032033fcb7d22309e3d60e386a82cf1f5ef48870000000000ffffffff018a02000000000000160014c772d6f95542e11ef11e8efc7c7a69830ad38a050247304402200a896f1cfa741ccda5f683bb85d95dbf836c390cdd45a785ece1f54766795821022051c4c956da8d3012da61b5465158888939a1ad5dfb106c1e25a2f92f13879122012102bac3a645fd7a0a537c627ad37f5f695058c58d19ceb0033e913f1a9975e00dee0247304402207eb0d3423b8c62150f83a0756baaa93dc8cd15e07f594b2131c753d6ed16e3b2022051e5a44372fb680bf5c487bb87ef0f65bf040ac5e0d54cdbb4f5bcc1ddaac745012102883bb5463e37d55252d8b3d5c2141b007b37c8a7db6211f75c955acc5ea325eb00000000",
"complete": true
}
As usual you'll want to save that and then you can send it out
$ psbt_hex=$(bitcoin-cli finalizepsbt $psbt_f | jq -r '.hex')
$ bitcoin-cli -named sendrawtransaction hexstring=$psbt_hex
ea73a631b456d2b041ed73bf5767946408c6ff067716929a68ecda2e3e4de6d3
When creating bitcoin-cli
software, it's most likely that you'll fulfill the five core roles of PSBTs with createpsbt
, walletprocesspsbt
, and finalizepsbt
. Here's what that flow looks like:
If you choose to use the shortcut of walletcreatefundedpsbt
, this is what it looks like instead:
Finally, if you instead need more control and choose to use utxoupdatepsbt
(which is largely undocumented here), you instead have this workflow:
Creating a PSBT involves a somewhat complex workflow of Creating, Updating, Signing, Finalizing, and Extracting a PSBT, after which it converts back into a raw transaction. Why would you go to all that trouble? Because you want to collaborate between multiple users or multiple programs. Now that you understand this workflow, the next section has some real-life examples of doing so.
Continue "Expanding Bitcoin Transactions with PSBTs" with §7.2: Using a Partially Signed Bitcoin Transaction.