All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hedera.hashgraph.sdk.AbstractTokenTransferTransaction Maven / Gradle / Ivy

The newest version!
/*-
 *
 * Hedera Java SDK
 *
 * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.hedera.hashgraph.sdk;

import com.google.protobuf.InvalidProtocolBufferException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

abstract class AbstractTokenTransferTransaction> extends Transaction {

    protected final ArrayList tokenTransfers = new ArrayList<>();
    protected final ArrayList nftTransfers = new ArrayList<>();

    protected AbstractTokenTransferTransaction() {}

    /**
     * Constructor.
     *
     * @param txs Compound list of transaction id's list of (AccountId, Transaction) records
     * @throws InvalidProtocolBufferException when there is an issue with the protobuf
     */
    AbstractTokenTransferTransaction(
        LinkedHashMap> txs)
        throws InvalidProtocolBufferException {
        super(txs);
    }

    /**
     * Constructor.
     *
     * @param txBody protobuf TransactionBody
     */
    AbstractTokenTransferTransaction(com.hedera.hashgraph.sdk.proto.TransactionBody txBody) {
        super(txBody);
    }


    /**
     * Extract the list of token id decimals.
     *
     * @return the list of token id decimals
     */
    public Map getTokenIdDecimals() {
        Map decimalsMap = new HashMap<>();

        for (var transfer : tokenTransfers) {
            decimalsMap.put(transfer.tokenId, transfer.expectedDecimals);
        }

        return decimalsMap;
    }

    /**
     * Extract the list of token transfer records.
     *
     * @return the list of token transfer records
     */
    public Map> getTokenTransfers() {
        Map> transfers = new HashMap<>();

        for (var transfer : tokenTransfers) {
            var current = transfers.get(transfer.tokenId) != null
                ? transfers.get(transfer.tokenId) : new HashMap();
            current.put(transfer.accountId, transfer.amount);
            transfers.put(transfer.tokenId, current);
        }

        return transfers;
    }

    private T doAddTokenTransfer(TokenId tokenId, AccountId accountId, long value,
        boolean isApproved) {
        requireNotFrozen();

        for (var transfer : tokenTransfers) {
            if (transfer.tokenId.equals(tokenId) && transfer.accountId.equals(accountId)
                && transfer.isApproved == isApproved) {
                transfer.amount = transfer.amount + value;
                // noinspection unchecked
                return (T) this;
            }
        }

        tokenTransfers.add(new TokenTransfer(tokenId, accountId, value, isApproved));
        // noinspection unchecked
        return (T) this;
    }

    /**
     * Add a non-approved token transfer to the transaction.
     *
     * @param tokenId   the token id
     * @param accountId the account id
     * @param value     the value
     * @return the updated transaction
     */
    public T addTokenTransfer(TokenId tokenId, AccountId accountId, long value) {
        return doAddTokenTransfer(tokenId, accountId, value, false);
    }

    /**
     * Add an approved token transfer to the transaction.
     *
     * @param tokenId   the token id
     * @param accountId the account id
     * @param value     the value
     * @return the updated transaction
     */
    public T addApprovedTokenTransfer(TokenId tokenId, AccountId accountId, long value) {
        return doAddTokenTransfer(tokenId, accountId, value, true);
    }

    private T doAddTokenTransferWithDecimals(
        TokenId tokenId,
        AccountId accountId,
        long value,
        int decimals,
        boolean isApproved
    ) {
        requireNotFrozen();

        var found = false;

        for (var transfer : tokenTransfers) {
            if (transfer.tokenId.equals(tokenId)) {
                if (transfer.expectedDecimals != null && transfer.expectedDecimals != decimals) {
                    throw new IllegalArgumentException(
                        "expected decimals for a token in a token transfer cannot be changed after being set");
                }

                transfer.expectedDecimals = decimals;

                if (transfer.accountId.equals(accountId) && transfer.isApproved == isApproved) {
                    transfer.amount = transfer.amount + value;
                    found = true;
                }

            }
        }

        if (found) {
            // noinspection unchecked
            return (T) this;
        }
        tokenTransfers.add(new TokenTransfer(tokenId, accountId, value, decimals, isApproved));

        // noinspection unchecked
        return (T) this;
    }

    /**
     * Add a non-approved token transfer with decimals.
     *
     * @param tokenId   the token id
     * @param accountId the account id
     * @param value     the value
     * @param decimals  the decimals
     * @return the updated transaction
     */
    public T addTokenTransferWithDecimals(
        TokenId tokenId,
        AccountId accountId,
        long value,
        int decimals
    ) {
        return doAddTokenTransferWithDecimals(tokenId, accountId, value, decimals, false);
    }

    /**
     * Add an approved token transfer with decimals.
     *
     * @param tokenId   the token id
     * @param accountId the account id
     * @param value     the value
     * @param decimals  the decimals
     * @return the updated transaction
     */
    public T addApprovedTokenTransferWithDecimals(
        TokenId tokenId,
        AccountId accountId,
        long value,
        int decimals
    ) {
        return doAddTokenTransferWithDecimals(tokenId, accountId, value, decimals, true);
    }

    /**
     * @param tokenId    the token id
     * @param accountId  the account id
     * @param isApproved whether the transfer is approved
     * @return {@code this}
     * @deprecated - Use {@link #addApprovedTokenTransfer(TokenId, AccountId, long)} instead
     */
    @Deprecated
    public T setTokenTransferApproval(TokenId tokenId, AccountId accountId, boolean isApproved) {
        requireNotFrozen();

        for (var transfer : tokenTransfers) {
            if (transfer.tokenId.equals(tokenId) && transfer.accountId.equals(accountId)) {
                transfer.isApproved = isApproved;
                // noinspection unchecked
                return (T) this;
            }
        }

        // noinspection unchecked
        return (T) this;
    }

    /**
     * Extract the of token nft transfers.
     *
     * @return list of token nft transfers
     */
    public Map> getTokenNftTransfers() {
        Map> transfers = new HashMap<>();

        for (var transfer : nftTransfers) {
            var current = transfers.get(transfer.tokenId) != null
                ? transfers.get(transfer.tokenId) : new ArrayList();
            current.add(transfer);
            transfers.put(transfer.tokenId, current);
        }

        return transfers;
    }

    private T doAddNftTransfer(NftId nftId, AccountId sender, AccountId receiver,
        boolean isApproved) {
        requireNotFrozen();
        nftTransfers.add(new TokenNftTransfer(nftId.tokenId, sender, receiver, nftId.serial, isApproved));
        return (T) this;
    }

    /**
     * Add a non-approved nft transfer.
     *
     * @param nftId    the nft's id
     * @param sender   the sender account id
     * @param receiver the receiver account id
     * @return the updated transaction
     */
    public T addNftTransfer(NftId nftId, AccountId sender, AccountId receiver) {
        return doAddNftTransfer(nftId, sender, receiver, false);
    }

    /**
     * Add an approved nft transfer.
     *
     * @param nftId    the nft's id
     * @param sender   the sender account id
     * @param receiver the receiver account id
     * @return the updated transaction
     */
    public T addApprovedNftTransfer(NftId nftId, AccountId sender, AccountId receiver) {
        return doAddNftTransfer(nftId, sender, receiver, true);
    }

    /**
     * @param nftId      the NFT id
     * @param isApproved whether the transfer is approved
     * @return {@code this}
     * @deprecated - Use {@link #addApprovedNftTransfer(NftId, AccountId, AccountId)} instead
     */
    @Deprecated
    public T setNftTransferApproval(NftId nftId, boolean isApproved) {
        requireNotFrozen();

        for (var transfer : nftTransfers) {
            if (transfer.tokenId.equals(nftId.tokenId) && transfer.serial == nftId.serial) {
                transfer.isApproved = isApproved;
                // noinspection unchecked
                return (T) this;
            }
        }

        // noinspection unchecked
        return (T) this;
    }

    protected ArrayList sortTransfersAndBuild() {
        var transferLists = new ArrayList();

        this.tokenTransfers.sort(Comparator.comparing((TokenTransfer a) -> a.tokenId).thenComparing(a -> a.accountId)
            .thenComparing(a -> a.isApproved));
        this.nftTransfers.sort(Comparator.comparing((TokenNftTransfer a) -> a.tokenId).thenComparing(a -> a.sender)
            .thenComparing(a -> a.receiver).thenComparing(a -> a.serial));

        var i = 0;
        var j = 0;

        // Effectively merge sort
        while (i < this.tokenTransfers.size() || j < this.nftTransfers.size()) {
            if (i < this.tokenTransfers.size() && j < this.nftTransfers.size()) {
                var iTokenId = this.tokenTransfers.get(i).tokenId;
                var jTokenId = this.nftTransfers.get(j).tokenId;
                var last = !transferLists.isEmpty() ? transferLists.get(transferLists.size() - 1) : null;
                var lastTokenId = last != null ? last.tokenId : null;

                if (last != null && iTokenId.compareTo(lastTokenId) == 0) {
                    last.transfers.add(this.tokenTransfers.get(i++));
                    continue;
                }

                if (last != null && jTokenId.compareTo(lastTokenId) == 0) {
                    last.nftTransfers.add(this.nftTransfers.get(j++));
                    continue;
                }

                var result = iTokenId.compareTo(jTokenId);

                if (result == 0) {
                    transferLists.add(new com.hedera.hashgraph.sdk.TokenTransferList(iTokenId,
                        this.tokenTransfers.get(i).expectedDecimals, this.tokenTransfers.get(i++),
                        this.nftTransfers.get(j++)));
                } else if (result < 0) {
                    transferLists.add(new com.hedera.hashgraph.sdk.TokenTransferList(iTokenId,
                        this.tokenTransfers.get(i).expectedDecimals, this.tokenTransfers.get(i++), null));
                } else {
                    transferLists.add(new com.hedera.hashgraph.sdk.TokenTransferList(jTokenId, null, null,
                        this.nftTransfers.get(j++)));
                }
            } else if (i < this.tokenTransfers.size()) {
                var iTokenId = this.tokenTransfers.get(i).tokenId;
                var last = !transferLists.isEmpty() ? transferLists.get(transferLists.size() - 1) : null;
                var lastTokenId = last != null ? last.tokenId : null;

                if (last != null && iTokenId.compareTo(lastTokenId) == 0) {
                    last.transfers.add(this.tokenTransfers.get(i++));
                    continue;
                }

                transferLists.add(new com.hedera.hashgraph.sdk.TokenTransferList(iTokenId,
                    this.tokenTransfers.get(i).expectedDecimals, this.tokenTransfers.get(i++), null));
            } else {
                var jTokenId = this.nftTransfers.get(j).tokenId;
                var last = !transferLists.isEmpty() ? transferLists.get(transferLists.size() - 1) : null;
                var lastTokenId = last != null ? last.tokenId : null;

                if (last != null && jTokenId.compareTo(lastTokenId) == 0) {
                    last.nftTransfers.add(this.nftTransfers.get(j++));
                    continue;
                }

                transferLists.add(
                    new com.hedera.hashgraph.sdk.TokenTransferList(jTokenId, null, null, this.nftTransfers.get(j++)));
            }
        }
        return transferLists;
    }

    @Override
    void validateChecksums(Client client) throws BadEntityIdException {
        for (var transfer : nftTransfers) {
            transfer.tokenId.validateChecksum(client);
            transfer.sender.validateChecksum(client);
            transfer.receiver.validateChecksum(client);
        }

        for (var transfer : tokenTransfers) {
            transfer.tokenId.validateChecksum(client);
            transfer.accountId.validateChecksum(client);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy