private static Protos.Transaction makeTxProto(WalletTransaction wtx) {
    Transaction tx = wtx.getTransaction();
    Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder();

    txBuilder
        .setPool(getProtoPool(wtx))
        .setHash(hashToByteString(tx.getHash()))
        .setVersion((int) tx.getVersion());

    if (tx.getUpdateTime() != null) {
      txBuilder.setUpdatedAt(tx.getUpdateTime().getTime());
    }

    if (tx.getLockTime() > 0) {
      txBuilder.setLockTime((int) tx.getLockTime());
    }

    // Handle inputs.
    for (TransactionInput input : tx.getInputs()) {
      Protos.TransactionInput.Builder inputBuilder =
          Protos.TransactionInput.newBuilder()
              .setScriptBytes(ByteString.copyFrom(input.getScriptBytes()))
              .setTransactionOutPointHash(hashToByteString(input.getOutpoint().getHash()))
              .setTransactionOutPointIndex((int) input.getOutpoint().getIndex());
      if (input.hasSequence()) inputBuilder.setSequence((int) input.getSequenceNumber());
      if (input.getValue() != null) inputBuilder.setValue(input.getValue().value);
      txBuilder.addTransactionInput(inputBuilder);
    }

    // Handle outputs.
    for (TransactionOutput output : tx.getOutputs()) {
      Protos.TransactionOutput.Builder outputBuilder =
          Protos.TransactionOutput.newBuilder()
              .setScriptBytes(ByteString.copyFrom(output.getScriptBytes()))
              .setValue(output.getValue().value);
      final TransactionInput spentBy = output.getSpentBy();
      if (spentBy != null) {
        Sha256Hash spendingHash = spentBy.getParentTransaction().getHash();
        int spentByTransactionIndex = spentBy.getParentTransaction().getInputs().indexOf(spentBy);
        outputBuilder
            .setSpentByTransactionHash(hashToByteString(spendingHash))
            .setSpentByTransactionIndex(spentByTransactionIndex);
      }
      txBuilder.addTransactionOutput(outputBuilder);
    }

    // Handle which blocks tx was seen in.
    final Map<Sha256Hash, Integer> appearsInHashes = tx.getAppearsInHashes();
    if (appearsInHashes != null) {
      for (Map.Entry<Sha256Hash, Integer> entry : appearsInHashes.entrySet()) {
        txBuilder.addBlockHash(hashToByteString(entry.getKey()));
        txBuilder.addBlockRelativityOffsets(entry.getValue());
      }
    }

    if (tx.hasConfidence()) {
      TransactionConfidence confidence = tx.getConfidence();
      Protos.TransactionConfidence.Builder confidenceBuilder =
          Protos.TransactionConfidence.newBuilder();
      writeConfidence(txBuilder, confidence, confidenceBuilder);
    }

    Protos.Transaction.Purpose purpose;
    switch (tx.getPurpose()) {
      case UNKNOWN:
        purpose = Protos.Transaction.Purpose.UNKNOWN;
        break;
      case USER_PAYMENT:
        purpose = Protos.Transaction.Purpose.USER_PAYMENT;
        break;
      case KEY_ROTATION:
        purpose = Protos.Transaction.Purpose.KEY_ROTATION;
        break;
      case ASSURANCE_CONTRACT_CLAIM:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_CLAIM;
        break;
      case ASSURANCE_CONTRACT_PLEDGE:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_PLEDGE;
        break;
      case ASSURANCE_CONTRACT_STUB:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_STUB;
        break;
      default:
        throw new RuntimeException("New tx purpose serialization not implemented.");
    }
    txBuilder.setPurpose(purpose);

    ExchangeRate exchangeRate = tx.getExchangeRate();
    if (exchangeRate != null) {
      Protos.ExchangeRate.Builder exchangeRateBuilder =
          Protos.ExchangeRate.newBuilder()
              .setCoinValue(exchangeRate.coin.value)
              .setFiatValue(exchangeRate.fiat.value)
              .setFiatCurrencyCode(exchangeRate.fiat.currencyCode);
      txBuilder.setExchangeRate(exchangeRateBuilder);
    }

    if (tx.getMemo() != null) txBuilder.setMemo(tx.getMemo());

    return txBuilder.build();
  }
  /**
   * Converts the given wallet to the object representation of the protocol buffers. This can be
   * modified, or additional data fields set, before serialization takes place.
   */
  public Protos.Wallet walletToProto(Wallet wallet) {
    Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder();
    walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId());
    if (wallet.getDescription() != null) {
      walletBuilder.setDescription(wallet.getDescription());
    }

    for (WalletTransaction wtx : wallet.getWalletTransactions()) {
      Protos.Transaction txProto = makeTxProto(wtx);
      walletBuilder.addTransaction(txProto);
    }

    walletBuilder.addAllKey(wallet.serializeKeychainToProtobuf());

    for (Script script : wallet.getWatchedScripts()) {
      Protos.Script protoScript =
          Protos.Script.newBuilder()
              .setProgram(ByteString.copyFrom(script.getProgram()))
              .setCreationTimestamp(script.getCreationTimeSeconds() * 1000)
              .build();

      walletBuilder.addWatchedScript(protoScript);
    }

    // Populate the lastSeenBlockHash field.
    Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash();
    if (lastSeenBlockHash != null) {
      walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash));
      walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight());
    }
    if (wallet.getLastBlockSeenTimeSecs() > 0)
      walletBuilder.setLastSeenBlockTimeSecs(wallet.getLastBlockSeenTimeSecs());

    // Populate the scrypt parameters.
    KeyCrypter keyCrypter = wallet.getKeyCrypter();
    if (keyCrypter == null) {
      // The wallet is unencrypted.
      walletBuilder.setEncryptionType(EncryptionType.UNENCRYPTED);
    } else {
      // The wallet is encrypted.
      walletBuilder.setEncryptionType(keyCrypter.getUnderstoodEncryptionType());
      if (keyCrypter instanceof KeyCrypterScrypt) {
        KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) keyCrypter;
        walletBuilder.setEncryptionParameters(keyCrypterScrypt.getScryptParameters());
      } else {
        // Some other form of encryption has been specified that we do not know how to persist.
        throw new RuntimeException(
            "The wallet has encryption of type '"
                + keyCrypter.getUnderstoodEncryptionType()
                + "' but this WalletProtobufSerializer does not know how to persist this.");
      }
    }

    if (wallet.getKeyRotationTime() != null) {
      long timeSecs = wallet.getKeyRotationTime().getTime() / 1000;
      walletBuilder.setKeyRotationTime(timeSecs);
    }

    populateExtensions(wallet, walletBuilder);

    for (Map.Entry<String, ByteString> entry : wallet.getTags().entrySet()) {
      Protos.Tag.Builder tag =
          Protos.Tag.newBuilder().setTag(entry.getKey()).setData(entry.getValue());
      walletBuilder.addTags(tag);
    }

    for (TransactionSigner signer : wallet.getTransactionSigners()) {
      // do not serialize LocalTransactionSigner as it's being added implicitly
      if (signer instanceof LocalTransactionSigner) continue;
      Protos.TransactionSigner.Builder protoSigner = Protos.TransactionSigner.newBuilder();
      protoSigner.setClassName(signer.getClass().getName());
      protoSigner.setData(ByteString.copyFrom(signer.serialize()));
      walletBuilder.addTransactionSigners(protoSigner);
    }

    walletBuilder.setSigsRequiredToSpend(wallet.getSigsRequiredToSpend());

    // Populate the wallet version.
    walletBuilder.setVersion(wallet.getVersion());

    return walletBuilder.build();
  }