PSBT.fromTransferPsbt constructor

PSBT.fromTransferPsbt(
  1. BtcTransferInfo btcInfo, {
  2. WalletType walletType = WalletType.SingleSignatureWallet,
})

Implementation

factory PSBT.fromTransferPsbt(BtcTransferInfo btcInfo,
    {WalletType walletType = WalletType.SingleSignatureWallet}) {
  String xpubkey = btcInfo.xpubkey;
  final chainConf = getChainConfig(btcInfo.chain).mainnet;
  final hdWallet =
      bitcoin.HDWallet.fromBase58(xpubkey, network: chainConf.networkType);
  final Origin btcTxData = btcInfo.origin;

  int offset = 0;
  Uint8List psbtBytes = psbtToBytes(btcTxData.hex);
  offset += 5;

  Map<String, dynamic> psbtData = {"global": {}, "inputs": [], "outputs": []};

  // Global
  Map<String, String> globalMap = {};
  while (true) {
    int keyLen = Varints.read(psbtBytes, offset);
    offset += _getOffset(psbtBytes[offset]);
    if (keyLen == 0) {
      break;
    }
    Uint8List key = psbtBytes.sublist(offset, offset + keyLen);
    offset += keyLen;
    int valueLen = Varints.read(psbtBytes, offset);
    offset += _getOffset(psbtBytes[offset]);
    Uint8List value = psbtBytes.sublist(offset, offset + valueLen);
    offset += valueLen;
    globalMap[Converter.bytesToHex(key)] = Converter.bytesToHex(value);
  }
  psbtData["global"] = globalMap;
  // Inputs

  if (psbtData["global"]["00"] == null) {
    throw Exception('Invalid PSBT');
  }
  Transaction globalTx =
      Transaction.parsePsbtTransaction(psbtData["global"]["00"]);

  for (int i = 0; i < globalTx.inputs.length; i++) {
    Map<String, String> inputData = {};
    while (true) {
      int keyLen = Varints.read(psbtBytes, offset);
      offset += _getOffset(psbtBytes[offset]);
      if (keyLen == 0) {
        break;
      }
      Uint8List key = psbtBytes.sublist(offset, offset + keyLen);
      offset += keyLen;
      int valueLen = Varints.read(psbtBytes, offset);
      offset += _getOffset(psbtBytes[offset]);
      Uint8List value = psbtBytes.sublist(offset, offset + valueLen);
      offset += valueLen;
      inputData[Converter.bytesToHex(key)] = Converter.bytesToHex(value);
    }
    //derivation path
    String bip32DerivationKeyType =
        getKeyType(inputKeyType, 'BIP32_DERIVATION');
    String derivationPath =
        btcTxData.inputs[i].path.derivation ?? btcInfo.path;
    if (walletType == WalletType.SingleSignatureWallet) {
      final List<int> pathList = getHDPath(derivationPath);
      final child = hdWallet.derive(pathList[3]).derive(pathList[4]);

      String publicKey = child.pubKey!;
      String toxonlypub = child.pubKey!.length == 64
          ? child.pubKey!
          : child.pubKey!.substring(2);
      Uint8List fingerPrint = hdWallet.fingerprint!;
      if (btcInfo.txType != fx.BtcTxType.TAPROOT) {
        inputData[bip32DerivationKeyType + publicKey] = Converter.bytesToHex(
                fingerPrint) +
            Converter.bytesToHex(_serializeDerivationPath(derivationPath));
      } else {
        inputData[PSBT_IN_TAP_BIP32_DERIVATION + toxonlypub] =
            '00${Converter.bytesToHex(fingerPrint)}${Converter.bytesToHex(_serializeDerivationPath(derivationPath))}';
      }
    }
    psbtData["inputs"].add(inputData);
  }

  // Change
  String receiveAddress = btcInfo.outputAddress;
  final outputs = btcTxData.outputs;
  final result = outputs.firstWhere((e) => e.address == receiveAddress);
  num actualAmount =
      result.amount ?? NumberUtil.toDouble(result.value) / 1000000;
  List<int> outputsIndex = List.generate(outputs.length, (index) => index);
  String inputAddress = btcInfo.inputAddress;
  for (var i = 0; i < outputs.length; i++) {
    if (outputs[i].address == receiveAddress) {
      if (outputs[i].amount == actualAmount) {
        outputsIndex.remove(i);
      }
    }
  }
  int changeIndex = -1; // total - payoff;
  if (outputsIndex.isNotEmpty &&
      (outputs[outputsIndex[0]].address == inputAddress ||
          (btcInfo.chain.toLowerCase() == 'bch' &&
              (outputs[outputsIndex[0]].address ==
                      bitcoin.Address.bchToLegacy(inputAddress) ||
                  outputs[outputsIndex[0]].address ==
                      bitcoin.Address.legacyToBch(
                          address: inputAddress, prefix: 'bitcoincash'))))) {
    changeIndex = outputsIndex[0];
  }

  // Outputs
  for (int i = 0; i < globalTx.outputs.length; i++) {
    Map<String, String> outputData = {};
    while (true) {
      int keyLen = Varints.read(psbtBytes, offset);
      offset += _getOffset(psbtBytes[offset]);
      if (keyLen == 0) {
        break;
      }
      Uint8List key = psbtBytes.sublist(offset, offset + keyLen);
      offset += keyLen;
      int valueLen = Varints.read(psbtBytes, offset);

      offset += _getOffset(psbtBytes[offset]);
      Uint8List value = psbtBytes.sublist(offset, offset + valueLen);

      offset += valueLen;
      outputData[Converter.bytesToHex(key)] = Converter.bytesToHex(value);
    }
    if (i == changeIndex) {
      String bip32DerivationKeyType =
          getKeyType(outputKeyType, 'BIP32_DERIVATION');
      String derivationPath = btcTxData.outputs[i].path ?? btcInfo.path;
      final List<int> pathList = getHDPath(derivationPath);
      final child = hdWallet.derive(pathList[3]).derive(pathList[4]);
      String publicKey = child.pubKey!;
      Uint8List fingerPrint = hdWallet.fingerprint!;
      outputData[bip32DerivationKeyType + publicKey] =
          Converter.bytesToHex(fingerPrint) +
              Converter.bytesToHex(_serializeDerivationPath(derivationPath));
    }
    psbtData["outputs"].add(outputData);
  }

  return PSBT(psbtData);
}