Looks like a good path.. Two extra sanity checks I use before letting a watch-only touch the network: try to do things that require keys and confirm hard failure. In Core, dumpwallet should error with "private keys are disabled," and signrawtransactionwithwallet on a dummy tx should refuse to sign.
And one more tip that might be helpful: PSBTs can leak derivation paths and global xpubs. If you ever hand a PSBT to anything other than your offline box, create it with bip32derivs=false or strip derivs first. That keeps your keys unspilled and your privacy intact.
Could you develop on this?
To do a PSBT transaction you are going to need to place the file on your online box last time I checked, so it's not like you can avoid this. I did a number of test transactions on the testnet to practice the PSBT workflow and I think it was something like this:
0) Enable the "Enable PSBT Controls" checkbox
1) Go on the watch-only wallet and with Coin Control pick addresses as needed.
2) Click on "Create unsigned" and save .psbt file
3) Get this file into the airgap laptop and sign with the "File->Load PSBT from file..." GUI menu by clicking on "Sign Tx"
4) Save this now signed psbt file and move it back into watch-only laptop wallet
5) Load the file with "File->Load PSBT from file..." and click on "Broadcast Tx"
So as you can see, the psbt file is created on the online wallet already since step 1, so I don't understand why you claim " If you ever hand a PSBT to anything other than your offline box" since it's unavoidable, and I also do not understand what the other "bip32derivs=false or strip derivs first." thing does. If you could please elaborate and explain what this does and exact steps.
Confirmed, PSBTs can carry two kinds of wallet-structure data: per-key BIP32 derivation paths and a global xpubs map. Both aid signers, and both reveal account structure to anyone who sees the PSBT.
In Bitcoin Core, `walletcreatefundedpsbt` has a `bip32derivs` switch. It was default *false* in 0.19, and is default *true* by 22-23+. Set it to *false* on the watch-only box if you don't want derivation paths in the PSBT.
Derivation data on *outputs* lets a device verify the change address. If you omit it, you lose that automatic check, so verify change manually or use a fixed change address from your descriptor.
Flow that matches your setup:
1). Create PSBT on the watch-only wallet with derivs off.
bitcoin-cli -rpcwallet=watch -named walletcreatefundedpsbt \
inputs='[]' outputs='{"bc1...":0.001}' options='{"add_inputs":true}' bip32derivs=false
2). Check it contains no xpubs or derivs:
bitcoin-cli decodepsbt "$PSBT" | grep -Ei 'xpub|bip32_derivs' # no hits
3). Move PSBT to the air-gapped wallet and sign:
SIGNED=$(bitcoin-cli -rpcwallet=airgap walletprocesspsbt "$PSBT" true | jq -r .psbt)
RAW=$(bitcoin-cli finalizepsbt "$SIGNED" true | jq -r .hex)
4). Broadcast only the raw hex from the online node:
bitcoin-cli sendrawtransaction "$RAW"