LedgerHQ has laid the foundation for a PSBTv2 library, but it currently lacks some features and has issues with de/serialization. A complete Psbtv2 class can be found in my fork.
It is suggested to use PSBTv0 for all operations and only convert to v2 when communicating with the Ledger device. This can be achieved through utilizing BitcoinJS's excellent PSBTv0 library and utilizing an adapter function from v0 -> v2.
For exmaple if your're working with p2sh(p2wsh) script types, you might have something like:
export function convertPsbtv0ToV2(psbtv0: Psbt): PsbtV2 {
const psbtv2 = new PsbtV2()
const psbtv0GlobalMap = psbtv0.data.globalMap
// Set tx version and psbt version
psbtv2.setGlobalTxVersion(1)
psbtv2.setGlobalPsbtVersion(2)
// Set global input / output counts
psbtv2.setGlobalInputCount(psbtv0.data.inputs.length)
psbtv2.setGlobalOutputCount(psbtv0.data.outputs.length)
// Global fall back time
psbtv2.setGlobalFallbackLocktime(0)
// Add global xpubs
for (const globalXpub of psbtv0GlobalMap.globalXpub ?? []) {
psbtv2.setGlobalXpub(
globalXpub.extendedPubkey,
globalXpub.masterFingerprint,
pathStringToArray(globalXpub.path),
)
}
// Other unknown global properties
for (const globalProperty of psbtv0GlobalMap.unknownKeyVals ?? []) {
const keyLenBufferReader = new BufferReader(globalProperty.key)
const keyLen = sanitizeBigintToNumber(keyLenBufferReader.readVarInt())
if (keyLen == 0) {
throw new Error('Failed to convert PSBT. Invalid key length')
}
const keyType = keyLenBufferReader.readUInt8()
const keyData = keyLenBufferReader.readSlice(keyLen - 1)
psbtv2.setGlobalUnknownKeyVal(keyType, keyData, globalProperty.value)
}
// Add inputs
for (const [index, input] of psbtv0.data.inputs.entries()) {
if (input.nonWitnessUtxo) {
psbtv2.setInputNonWitnessUtxo(index, input.nonWitnessUtxo)
}
if (input.witnessUtxo) {
// Amount is 64 bit uint LE
const amount = Buffer.alloc(8, 0)
amount.writeBigUInt64LE(BigInt(input.witnessUtxo.value))
psbtv2.setInputWitnessUtxo(index, amount, input.witnessUtxo?.script)
}
if (input.redeemScript) {
psbtv2.setInputRedeemScript(index, input.redeemScript)
}
if (input.witnessScript) {
psbtv2.setInputScriptwitness(index, input.witnessScript)
}
if (input.bip32Derivation) {
for (const bip32 of input.bip32Derivation) {
psbtv2.setInputBip32Derivation(
index,
bip32.pubkey,
bip32.masterFingerprint,
pathStringToArray(bip32.path),
)
}
}
}
// Add input nsequence, vout, and prev txid
for (const [index, input] of psbtv0.txInputs.entries()) {
psbtv2.setInputSequence(index, input.sequence ?? MAX_NSEQUENCE)
psbtv2.setInputPreviousTxId(index, input.hash)
psbtv2.setInputOutputIndex(index, input.index)
}
// Add output value and script
for (const [index, output] of psbtv0.txOutputs.entries()) {
psbtv2.setOutputAmount(index, output.value)
psbtv2.setOutputScript(index, output.script)
}
for (const [index, output] of psbtv0.data.outputs.entries()) {
if (output.bip32Derivation) {
for (const bip32 of output.bip32Derivation) {
psbtv2.setOutputBip32Derivation(
index,
bip32.pubkey,
bip32.masterFingerprint,
pathStringToArray(bip32.path),
)
}
}
if (output.redeemScript) {
psbtv2.setOutputRedeemScript(index, output.redeemScript)
}
if (output.witnessScript) {
psbtv2.setOutputWitnessScript(index, output.witnessScript)
}
}
return psbtv2
}