
com.hedera.hashgraph.sdk.TransferTransaction Maven / Gradle / Ivy
/*-
*
* Hedera Java SDK
*
* Copyright (C) 2020 - 2022 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.common.base.MoreObjects;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.UInt32Value;
import com.hedera.hashgraph.sdk.proto.AccountAmount;
import com.hedera.hashgraph.sdk.proto.CryptoServiceGrpc;
import com.hedera.hashgraph.sdk.proto.CryptoTransferTransactionBody;
import com.hedera.hashgraph.sdk.proto.NftTransfer;
import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody;
import com.hedera.hashgraph.sdk.proto.TransactionBody;
import com.hedera.hashgraph.sdk.proto.TransactionResponse;
import com.hedera.hashgraph.sdk.proto.TransferList;
import io.grpc.MethodDescriptor;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class TransferTransaction extends Transaction {
private final ArrayList tokenTransfers = new ArrayList<>();
private final ArrayList nftTransfers = new ArrayList<>();
private final ArrayList hbarTransfers = new ArrayList<>();
private static class HbarTransfer {
final AccountId accountId;
Hbar amount;
boolean isApproved;
HbarTransfer(AccountId accountId, Hbar amount, boolean isApproved) {
this.accountId = accountId;
this.amount = amount;
this.isApproved = isApproved;
}
AccountAmount toProtobuf() {
return AccountAmount.newBuilder()
.setAccountID(accountId.toProtobuf())
.setAmount(amount.toTinybars())
.setIsApproval(isApproved)
.build();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("accountId", accountId)
.add("amount", amount)
.add("isApproved", isApproved)
.toString();
}
}
private static class TokenTransferList {
final TokenId tokenId;
@Nullable
final Integer expectDecimals;
List transfers = new ArrayList<>();
List nftTransfers = new ArrayList<>();
TokenTransferList(TokenId tokenId, @Nullable Integer expectDecimals, @Nullable TokenTransfer transfer, @Nullable TokenNftTransfer nftTransfer) {
this.tokenId = tokenId;
this.expectDecimals = expectDecimals;
if (transfer != null) {
this.transfers.add(transfer);
}
if (nftTransfer != null) {
this.nftTransfers.add(nftTransfer);
}
}
com.hedera.hashgraph.sdk.proto.TokenTransferList toProtobuf() {
var transfers = new ArrayList();
var nftTransfers = new ArrayList();
for (var transfer : this.transfers) {
transfers.add(transfer.toProtobuf());
}
for (var transfer : this.nftTransfers) {
nftTransfers.add(transfer.toProtobuf());
}
var builder = com.hedera.hashgraph.sdk.proto.TokenTransferList.newBuilder()
.setToken(tokenId.toProtobuf())
.addAllTransfers(transfers)
.addAllNftTransfers(nftTransfers);
if (expectDecimals != null) {
builder.setExpectedDecimals(UInt32Value.newBuilder().setValue(expectDecimals).build());
}
return builder.build();
}
}
public TransferTransaction() {
defaultMaxTransactionFee = new Hbar(1);
}
TransferTransaction(LinkedHashMap> txs) throws InvalidProtocolBufferException {
super(txs);
initFromTransactionBody();
}
TransferTransaction(com.hedera.hashgraph.sdk.proto.TransactionBody txBody) {
super(txBody);
initFromTransactionBody();
}
public Map getTokenIdDecimals() {
Map decimalsMap = new HashMap<>();
for (var transfer : tokenTransfers) {
decimalsMap.put(transfer.tokenId, transfer.expectedDecimals);
}
return decimalsMap;
}
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 TransferTransaction 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;
return this;
}
}
tokenTransfers.add(new TokenTransfer(tokenId, accountId, value, isApproved));
return this;
}
public TransferTransaction addTokenTransfer(TokenId tokenId, AccountId accountId, long value) {
return doAddTokenTransfer(tokenId, accountId, value, false);
}
public TransferTransaction addApprovedTokenTransfer(TokenId tokenId, AccountId accountId, long value) {
return doAddTokenTransfer(tokenId, accountId, value, true);
}
private TransferTransaction 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) {
return this;
}
tokenTransfers.add(new TokenTransfer(tokenId, accountId, value, decimals, isApproved));
return this;
}
public TransferTransaction addTokenTransferWithDecimals(
TokenId tokenId,
AccountId accountId,
long value,
int decimals
) {
return doAddTokenTransferWithDecimals(tokenId, accountId, value, decimals, false);
}
public TransferTransaction addApprovedTokenTransferWithDecimals(
TokenId tokenId,
AccountId accountId,
long value,
int decimals
) {
return doAddTokenTransferWithDecimals(tokenId, accountId, value, decimals, true);
}
/**
* @deprecated - Use {@link #addApprovedTokenTransfer(TokenId, AccountId, long)} instead
*/
@Deprecated
public TransferTransaction setTokenTransferApproval(TokenId tokenId, AccountId accountId, boolean isApproved) {
requireNotFrozen();
for (var transfer : tokenTransfers) {
if (transfer.tokenId.equals(tokenId) && transfer.accountId.equals(accountId)) {
transfer.isApproved = isApproved;
return this;
}
}
return this;
}
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 TransferTransaction doAddNftTransfer(NftId nftId, AccountId sender, AccountId receiver, boolean isApproved) {
requireNotFrozen();
nftTransfers.add(new TokenNftTransfer(nftId.tokenId, sender, receiver, nftId.serial, isApproved));
return this;
}
public TransferTransaction addNftTransfer(NftId nftId, AccountId sender, AccountId receiver) {
return doAddNftTransfer(nftId, sender, receiver, false);
}
public TransferTransaction addApprovedNftTransfer(NftId nftId, AccountId sender, AccountId receiver) {
return doAddNftTransfer(nftId, sender, receiver, true);
}
/**
* @deprecated - Use {@link #addApprovedNftTransfer(NftId, AccountId, AccountId)} instead
*/
@Deprecated
public TransferTransaction setNftTransferApproval(NftId nftId, boolean isApproved) {
requireNotFrozen();
for (var transfer : nftTransfers) {
if (transfer.tokenId.equals(nftId.tokenId) && transfer.serial == nftId.serial) {
transfer.isApproved = isApproved;
return this;
}
}
return this;
}
public Map getHbarTransfers() {
Map transfers = new HashMap<>();
for (var transfer : hbarTransfers) {
transfers.put(transfer.accountId, transfer.amount);
}
return transfers;
}
private TransferTransaction doAddHbarTransfer(AccountId accountId, Hbar value, boolean isApproved) {
requireNotFrozen();
for (var transfer : hbarTransfers) {
if (transfer.accountId.equals(accountId) && transfer.isApproved == isApproved) {
transfer.amount = Hbar.fromTinybars(transfer.amount.toTinybars() + value.toTinybars());
return this;
}
}
hbarTransfers.add(new HbarTransfer(accountId, value, isApproved));
return this;
}
public TransferTransaction addHbarTransfer(AccountId accountId, Hbar value) {
return doAddHbarTransfer(accountId, value, false);
}
public TransferTransaction addApprovedHbarTransfer(AccountId accountId, Hbar value) {
return doAddHbarTransfer(accountId, value, true);
}
/**
* @deprecated - Use {@link #addApprovedHbarTransfer(AccountId, Hbar)} instead
*/
@Deprecated
public TransferTransaction setHbarTransferApproval(AccountId accountId, boolean isApproved) {
requireNotFrozen();
for (var transfer : hbarTransfers) {
if (transfer.accountId.equals(accountId)) {
transfer.isApproved = isApproved;
return this;
}
}
return this;
}
CryptoTransferTransactionBody.Builder build() {
var tokenTransfers = new ArrayList();
this.hbarTransfers.sort(Comparator.comparing((HbarTransfer a) -> a.accountId).thenComparing(a -> a.isApproved));
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 = !tokenTransfers.isEmpty() ? tokenTransfers.get(tokenTransfers.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) {
tokenTransfers.add(new TokenTransferList(iTokenId, this.tokenTransfers.get(i).expectedDecimals, this.tokenTransfers.get(i++), this.nftTransfers.get(j++)));
} else if (result < 0) {
tokenTransfers.add(new TokenTransferList(iTokenId, this.tokenTransfers.get(i).expectedDecimals, this.tokenTransfers.get(i++), null));
} else {
tokenTransfers.add(new TokenTransferList(jTokenId, null, null, this.nftTransfers.get(j++)));
}
} else if (i < this.tokenTransfers.size()) {
var iTokenId = this.tokenTransfers.get(i).tokenId;
var last = !tokenTransfers.isEmpty() ? tokenTransfers.get(tokenTransfers.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;
}
tokenTransfers.add(new TokenTransferList(iTokenId, this.tokenTransfers.get(i).expectedDecimals, this.tokenTransfers.get(i++), null));
} else {
var jTokenId = this.nftTransfers.get(j).tokenId;
var last = !tokenTransfers.isEmpty() ? tokenTransfers.get(tokenTransfers.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;
}
tokenTransfers.add(new TokenTransferList(jTokenId, null, null, this.nftTransfers.get(j++)));
}
}
var builder = CryptoTransferTransactionBody.newBuilder();
var transfers = TransferList.newBuilder();
for (var transfer : hbarTransfers) {
transfers.addAccountAmounts(transfer.toProtobuf());
}
builder.setTransfers(transfers);
for (var transfer : tokenTransfers) {
builder.addTokenTransfers(transfer.toProtobuf());
}
return builder;
}
@Override
void validateChecksums(Client client) throws BadEntityIdException {
for (var transfer : hbarTransfers) {
transfer.accountId.validateChecksum(client);
}
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);
}
}
@Override
MethodDescriptor getMethodDescriptor() {
return CryptoServiceGrpc.getCryptoTransferMethod();
}
@Override
void onFreeze(TransactionBody.Builder bodyBuilder) {
bodyBuilder.setCryptoTransfer(build());
}
@Override
void onScheduled(SchedulableTransactionBody.Builder scheduled) {
scheduled.setCryptoTransfer(build());
}
void initFromTransactionBody() {
var body = sourceTransactionBody.getCryptoTransfer();
for (var transfer : body.getTransfers().getAccountAmountsList()) {
hbarTransfers.add(new HbarTransfer(
AccountId.fromProtobuf(transfer.getAccountID()),
Hbar.fromTinybars(transfer.getAmount()),
transfer.getIsApproval()
));
}
for (var tokenTransferList : body.getTokenTransfersList()) {
var token = TokenId.fromProtobuf(tokenTransferList.getToken());
for (var transfer : tokenTransferList.getTransfersList()) {
tokenTransfers.add(new TokenTransfer(
token,
AccountId.fromProtobuf(transfer.getAccountID()),
transfer.getAmount(),
tokenTransferList.hasExpectedDecimals() ? tokenTransferList.getExpectedDecimals().getValue() : null,
transfer.getIsApproval()
));
}
for (var transfer : tokenTransferList.getNftTransfersList()) {
nftTransfers.add(new TokenNftTransfer(
token,
AccountId.fromProtobuf(transfer.getSenderAccountID()),
AccountId.fromProtobuf(transfer.getReceiverAccountID()),
transfer.getSerialNumber(),
transfer.getIsApproval()
));
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy