TransactionService.java

package com.syntifi.near.api.service;

import com.syntifi.crypto.key.hash.Sha256;
import com.syntifi.near.api.model.accesskey.AccessKey;
import com.syntifi.near.api.model.block.Block;
import com.syntifi.near.api.model.common.EncodedHash;
import com.syntifi.near.api.model.identifier.Finality;
import com.syntifi.near.api.model.key.PrivateKey;
import com.syntifi.near.api.model.key.PublicKey;
import com.syntifi.near.api.model.key.Signature;
import com.syntifi.near.api.model.transaction.Action;
import com.syntifi.near.api.model.transaction.SignedTransaction;
import com.syntifi.near.api.model.transaction.Transaction;
import com.syntifi.near.api.model.transaction.TransactionAwait;
import com.syntifi.near.api.model.transaction.TransferAction;
import com.syntifi.near.borshj.Borsh;

import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

/**
 * Transaction service provides methods to easily work with transactions
 *
 * @author Alexandre Carvalho
 * @author Andre Bertolace
 * @since 0.0.1
 */
public class TransactionService {
    /**
     * Sends a {@link TransferAction} transaction waiting for result using  {@link NearService#sendTransactionAwait(String)}
     *
     * @param nearService      the near service instance to use
     * @param signerId         the signer id
     * @param receiverId       the receiver id
     * @param signerPublicKey  signer public key
     * @param signerPrivateKey signer private key
     * @param amount           the amount to transfer
     * @return {@link TransactionAwait} object with the result
     * @throws GeneralSecurityException thrown if failed to sign the transaction
     */
    public static TransactionAwait sendTransferActionAwait(NearService nearService, String signerId, String receiverId,
                                                           PublicKey signerPublicKey, PrivateKey signerPrivateKey,
                                                           BigInteger amount)
            throws GeneralSecurityException {
        return sendTransactionAwait(nearService, signerId, receiverId, signerPublicKey, signerPrivateKey,
                Arrays.asList(
                        TransferAction.builder()
                                .deposit(amount)
                                .build()));
    }

    /**
     * Sends a {@link TransferAction} transaction async using {@link NearService#sendTransactionAsync(String)}
     *
     * @param nearService      the near service instance to use
     * @param signerId         the signer id
     * @param receiverId       the receiver id
     * @param signerPublicKey  signer public key
     * @param signerPrivateKey signer private key
     * @param amount           the amount to transfer
     * @return transaction Hash
     * @throws GeneralSecurityException thrown if failed to sign the transaction
     */
    public static EncodedHash sendTransferActionAsync(NearService nearService, String signerId, String receiverId,
                                                      PublicKey signerPublicKey, PrivateKey signerPrivateKey,
                                                      BigInteger amount)
            throws GeneralSecurityException {
        return sendTransactionAsync(nearService, signerId, receiverId, signerPublicKey, signerPrivateKey,
                Arrays.asList(
                        TransferAction.builder()
                                .deposit(amount)
                                .build()));
    }

    /**
     * Sends a list of {@link Action} transaction waiting for result using {@link NearService#sendTransactionAwait(String)}
     *
     * @param nearService      the near service instance to use
     * @param signerId         the signer id
     * @param receiverId       the receiver id
     * @param signerPublicKey  signer public key
     * @param signerPrivateKey signer private key
     * @param actionList       list of {@link Action} to send
     * @return {@link TransactionAwait} object with the result
     * @throws GeneralSecurityException thrown if failed to sign the transaction
     */
    public static TransactionAwait sendTransactionAwait(NearService nearService, String signerId, String receiverId,
                                                        PublicKey signerPublicKey, PrivateKey signerPrivateKey,
                                                        List<Action> actionList)
            throws GeneralSecurityException {
        return nearService.sendTransactionAwait(prepareTransactionForActionList(nearService, signerId, receiverId, signerPublicKey, signerPrivateKey, actionList));
    }

    /**
     * Sends a list of {@link Action} transaction waiting for result using  {@link NearService#sendTransactionAsync(String)}
     *
     * @param nearService      the near service instance to use
     * @param signerId         the signer id
     * @param receiverId       the receiver id
     * @param signerPublicKey  signer public key
     * @param signerPrivateKey signer private key
     * @param actionList       list of {@link Action} to send
     * @return {@link TransactionAwait} object with the result
     * @throws GeneralSecurityException thrown if failed to sign the transaction
     */
    public static EncodedHash sendTransactionAsync(NearService nearService, String signerId, String receiverId,
                                                   PublicKey signerPublicKey, PrivateKey signerPrivateKey,
                                                   List<Action> actionList)
            throws GeneralSecurityException {
        return EncodedHash.builder()
                .encodedHash(nearService.sendTransactionAsync(
                        prepareTransactionForActionList(nearService, signerId, receiverId, signerPublicKey, signerPrivateKey, actionList)))
                .build();
    }

    /**
     * Prepares the transaction to send
     *
     * @param nearService      the near service instance to use
     * @param signerId         the signer id
     * @param receiverId       the receiver id
     * @param signerPublicKey  signer public key
     * @param signerPrivateKey signer private key
     * @param actionList       list of {@link Action} to send
     * @return the base64 encoded signed transaction string
     * @throws GeneralSecurityException thrown if failed to sign the transaction
     */
    private static String prepareTransactionForActionList(NearService nearService, String signerId, String receiverId,
                                                          PublicKey signerPublicKey, PrivateKey signerPrivateKey,
                                                          List<Action> actionList)
            throws GeneralSecurityException {
        Block block = nearService.getBlock(Finality.FINAL);

        AccessKey accessKey = nearService.viewAccessKey(Finality.FINAL, signerId, signerPublicKey.toEncodedBase58String());

        long nextNonce = accessKey.getNonce() + 1L;

        Transaction transaction = Transaction
                .builder()
                .signerId(signerId)
                .publicKey(signerPublicKey)
                .nonce(nextNonce)
                .receiverId(receiverId)
                .blockHash(block.getHeader().getHash().getDecodedHash())
                .actions(actionList)
                .build();

        byte[] serializedTx = Borsh.serialize(transaction);
        byte[] hashedTx = Sha256.digest(serializedTx);
        byte[] signedTx = signerPrivateKey.getPrivateKey().sign(hashedTx);

        SignedTransaction signedTransaction =
                SignedTransaction.builder()
                        .transaction(transaction)
                        .signature(Signature.builder()
                                .keyType(signerPublicKey.getType())
                                .data(signedTx).build())
                        .build();

        byte[] borshTx = Borsh.serialize(signedTransaction);

        return Base64.getEncoder().encodeToString(borshTx);
    }
}