class BridgeSupport {

    public void registerUldTransaction(Transaction uscTx, byte[] uldTxSerialized, int height, byte[] pmtSerialized) throws IOException, BlockStoreException {
        Context.propagate(uldContext);

        Sha256Hash uldTxHash = UldTransactionFormatUtils.calculateUldTxHash(uldTxSerialized);
        // Check the tx was not already processed
        if (provider.getUldTxHashesAlreadyProcessed().keySet().contains(uldTxHash)) {
            logger.warn("Supplied tx was already processed");
            return;
        }

        if (!PartialMerkleTreeFormatUtils.hasExpectedSize(pmtSerialized)) {
            throw new BridgeIllegalArgumentException("PartialMerkleTree doesn't have expected size");
        }

        Sha256Hash merkleRoot;
        try {
            PartialMerkleTree pmt = new PartialMerkleTree(bridgeConstants.getUldParams(), pmtSerialized, 0);
            List<Sha256Hash> hashesInPmt = new ArrayList<>();
            merkleRoot = pmt.getTxnHashAndMerkleRoot(hashesInPmt);
            if (!hashesInPmt.contains(uldTxHash)) {
                logger.warn("Supplied tx is not in the supplied partial merkle tree");
                return;
            }
        } catch (VerificationException e) {
            throw new BridgeIllegalArgumentException("PartialMerkleTree could not be parsed " + Hex.toHexString(pmtSerialized), e);
        }

        if (height < 0) {
            String panicMessage = String.format("Height is %d but should be greater than 0", height);
            logger.warn(panicMessage);
            panicProcessor.panic("uldlock", panicMessage);
            return;
        }

        // Check there are at least N blocks on top of the supplied height
        int uldBestChainHeight = getUldBlockchainBestChainHeight();
        int confirmations = uldBestChainHeight - height + 1;
        if (confirmations < bridgeConstants.getUld2UscMinimumAcceptableConfirmations()) {
            logger.warn(
                    "At least {} confirmations are required, but there are only {} confirmations",
                    bridgeConstants.getUld2UscMinimumAcceptableConfirmations(),
                    confirmations
            );
            return;
        }

        if (UldTransactionFormatUtils.getInputsCount(uldTxSerialized) == 0) {
            logger.warn("Tx {} has no inputs ", uldTxHash);
            // this is the exception thrown by co.usc.ulordj.core.UldTransaction#verify when there are no inputs.
            throw new VerificationException.EmptyInputsOrOutputs();
        }

        // Check the the merkle root equals merkle root of uld block at specified height in the uld best chain
        // ULD blockstore is available since we've already queried the best chain height
        UldBlock blockHeader = BridgeUtils.getStoredBlockAtHeight(uldBlockStore, height).getHeader();
        if (!blockHeader.getMerkleRoot().equals(merkleRoot)) {
            String panicMessage = String.format(
                    "Supplied merkle root %s does not match block's merkle root %s",
                    merkleRoot,
                    blockHeader.getMerkleRoot()
            );
            logger.warn(panicMessage);
            panicProcessor.panic("uldlock", panicMessage);
            return;
        }

        UldTransaction uldTx = new UldTransaction(bridgeConstants.getUldParams(), uldTxSerialized);
        uldTx.verify();

        boolean locked = true;

        Federation activeFederation = getActiveFederation();
        // Specific code for lock/release/none txs
        if (BridgeUtils.isLockTx(uldTx, getLiveFederations(), uldContext, bridgeConstants)) {
            logger.debug("This is a lock tx {}", uldTx);
            Optional<Script> scriptSig = BridgeUtils.getFirstInputScriptSig(uldTx);
            if (!scriptSig.isPresent()) {
                logger.warn(
                        "[uldtx:{}] First input does not spend a Pay-to-PubkeyHash {}",
                        uldTx.getHash(),
                        uldTx.getInput(0)
                );
                return;
            }

            // Compute the total amount sent. Value could have been sent both to the
            // currently active federation as well as to the currently retiring federation.
            // Add both amounts up in that case.
            Coin amountToActive = uldTx.getValueSentToMe(getActiveFederationWallet());
            Coin amountToRetiring = Coin.ZERO;
            Wallet retiringFederationWallet = getRetiringFederationWallet();
            if (retiringFederationWallet != null) {
                amountToRetiring = uldTx.getValueSentToMe(retiringFederationWallet);
            }
            Coin totalAmount = amountToActive.add(amountToRetiring);

            // Get the sender public key
            byte[] data = scriptSig.get().getChunks().get(1).data;

            // Tx is a lock tx, check whether the sender is whitelisted
            UldECKey senderUldKey = UldECKey.fromPublicOnly(data);
            Address senderUldAddress = new Address(uldContext.getParams(), senderUldKey.getPubKeyHash());

            // If the address is not whitelisted, then return the funds
            // using the exact same utxos sent to us.
            // That is, build a release transaction and get it in the release transaction set.
            // Otherwise, transfer SULD to the sender of the ULD
            // The USC account to update is the one that matches the pubkey "spent" on the first ulord tx input
            LockWhitelist lockWhitelist = provider.getLockWhitelist();
            if (!lockWhitelist.isWhitelistedFor(senderUldAddress, totalAmount, height)) {
                locked = false;
                // Build the list of UTXOs in the ULD transaction sent to either the active
                // or retiring federation
                List<UTXO> utxosToUs = uldTx.getWalletOutputs(getNoSpendWalletForLiveFederations()).stream()
                        .map(output ->
                                new UTXO(
                                        uldTx.getHash(),
                                        output.getIndex(),
                                        output.getValue(),
                                        0,
                                        uldTx.isCoinBase(),
                                        output.getScriptPubKey()
                                )
                        ).collect(Collectors.toList());
                // Use the list of UTXOs to build a transaction builder
                // for the return uld transaction generation
                ReleaseTransactionBuilder txBuilder = new ReleaseTransactionBuilder(
                        uldContext.getParams(),
                        getUTXOBasedWalletForLiveFederations(utxosToUs),
                        senderUldAddress,
                        getFeePerKb()
                );
                Optional<ReleaseTransactionBuilder.BuildResult> buildReturnResult = txBuilder.buildEmptyWalletTo(senderUldAddress);
                if (buildReturnResult.isPresent()) {
                    provider.getReleaseTransactionSet().add(buildReturnResult.get().getUldTx(), uscExecutionBlock.getNumber());
                    logger.info("whitelist money return tx build successful to {}. Tx {}. Value {}.", senderUldAddress, uscTx, totalAmount);
                } else {
                    logger.warn("whitelist money return tx build for uld tx {} error. Return was to {}. Tx {}. Value {}", uldTx.getHash(), senderUldAddress, uscTx, totalAmount);
                    panicProcessor.panic("whitelist-return-funds", String.format("whitelist money return tx build for uld tx {} error. Return was to {}. Tx {}. Value {}", uldTx.getHash(), senderUldAddress, uscTx, totalAmount));
                }
            } else {
                org.ethereum.crypto.ECKey key = org.ethereum.crypto.ECKey.fromPublicOnly(data);
                UscAddress sender = new UscAddress(key.getAddress());

                uscRepository.transfer(
                        PrecompiledContracts.BRIDGE_ADDR,
                        sender,
                        co.usc.core.Coin.fromUlord(totalAmount)
                );
                // Consume this whitelisted address
                lockWhitelist.consume(senderUldAddress);
            }
        } else if (BridgeUtils.isReleaseTx(uldTx, getLiveFederations())) {
            logger.debug("This is a release tx {}", uldTx);
            // do-nothing
            // We could call removeUsedUTXOs(uldTx) here, but we decided to not do that.
            // Used utxos should had been removed when we created the release tx.
            // Invoking removeUsedUTXOs() here would make "some" sense in theses scenarios:
            // a) In testnet, devnet or local: we restart the USC blockchain whithout changing the federation address. We don't want to have utxos that were already spent.
            // Open problem: TxA spends TxB. registerUldTransaction() for TxB is called, it spends a utxo the bridge is not yet aware of,
            // so nothing is removed. Then registerUldTransaction() for TxA and the "already spent" utxo is added as it was not spent.
            // When is not guaranteed to be called in the chronological order, so a Federator can inform
            // b) In prod: Federator created a tx manually or the federation was compromised and some utxos were spent. Better not try to spend them.
            // Open problem: For performance removeUsedUTXOs() just removes 1 utxo
        } else if (BridgeUtils.isMigrationTx(uldTx, activeFederation, getRetiringFederation(), uldContext, bridgeConstants)) {
            logger.debug("This is a migration tx {}", uldTx);
        } else {
            logger.warn("This is not a lock, a release nor a migration tx {}", uldTx);
            panicProcessor.panic("uldlock", "This is not a lock, a release nor a migration tx " + uldTx);
            return;
        }

        // Mark tx as processed on this block
        provider.getUldTxHashesAlreadyProcessed().put(uldTxHash, uscExecutionBlock.getNumber());

        // Save UTXOs from the federation(s) only if we actually
        // locked the funds.
        if (locked) {
            saveNewUTXOs(uldTx);
        }
        logger.info("ULD Tx {} processed in USC", uldTxHash);
    }

    public  BridgeSupport(
            UscSystemProperties config,
            Repository repository,
            BridgeEventLogger eventLogger,
            BridgeConstants bridgeConstants,
            BridgeStorageProvider provider,
            UldBlockstoreWithCache uldBlockStore,
            UldBlockChain uldBlockChain,
            Block executionBlock);
    public  BridgeSupport(
            UscSystemProperties config,
            Repository repository,
            BridgeEventLogger eventLogger,
            UscAddress contractAddress,
            Block uscExecutionBlock);
    public  BridgeSupport(
            UscSystemProperties config,
            Repository repository,
            BridgeEventLogger eventLogger,
            BridgeStorageProvider provider,
            Block uscExecutionBlock);
    private  BridgeSupport(
            Repository repository,
            BridgeStorageProvider provider,
            Block executionBlock,
            UscSystemProperties config,
            BridgeConstants bridgeConstants,
            BridgeEventLogger eventLogger,
            Context uldContext,
            UldBlockstoreWithCache uldBlockStore,
            UldBlockChain uldBlockChain);
    public  BridgeSupport(
            Repository repository,
            BridgeStorageProvider provider,
            Block executionBlock,
            UscSystemProperties config,
            BridgeConstants bridgeConstants,
            BridgeEventLogger eventLogger,
            Context uldContext,
            FederationSupport federationSupport,
            UldBlockstoreWithCache uldBlockStore,
            UldBlockChain uldBlockChain);

}