From a81244d8a74a85f2fac4298c3d33d0e97549e0a7 Mon Sep 17 00:00:00 2001 From: neodiX Date: Fri, 6 Dec 2024 11:14:45 +0400 Subject: [PATCH] add sendRawMessageWithConfirmation() to Tonlib; (works with tonlibjson.so/dll from testnet) * Sends raw message, bag of cells encoded in base64, with deliver confirmation. After the message * has been sent to the network this method looks up specified account transactions and returns * true if message was found among them. Timeout 60 seconds. add waitForDeployment() with default timeout 60s Transaction tl-b populates now hash field with in_msg.hash - does not coincide with rawTransaction hash though (todo); to all contracts added method sendWithConfirmation(); --- .../org/ton/java/tlb/types/Transaction.java | 263 +++++++------- .../highload/HighloadWalletV3.java | 35 +- .../smartcontract/lockup/LockupWalletV1.java | 21 ++ .../java/smartcontract/wallet/Contract.java | 343 +++++++++--------- .../smartcontract/wallet/v1/WalletV1R1.java | 12 +- .../smartcontract/wallet/v1/WalletV1R2.java | 10 + .../smartcontract/wallet/v1/WalletV1R3.java | 10 + .../smartcontract/wallet/v2/WalletV2R1.java | 10 + .../smartcontract/wallet/v2/WalletV2R2.java | 10 + .../smartcontract/wallet/v3/WalletV3R1.java | 10 + .../smartcontract/wallet/v3/WalletV3R2.java | 10 + .../smartcontract/wallet/v4/WalletV4R2.java | 9 + .../smartcontract/wallet/v5/WalletV5.java | 13 +- .../integrationtests/CommonTest.java | 17 +- .../integrationtests/TestWalletV2R1Short.java | 9 +- .../integrationtests/TestWalletV3R1.java | 51 +++ .../main/java/org/ton/java/tonlib/Tonlib.java | 40 ++ 17 files changed, 559 insertions(+), 314 deletions(-) diff --git a/cell/src/main/java/org/ton/java/tlb/types/Transaction.java b/cell/src/main/java/org/ton/java/tlb/types/Transaction.java index f7a2561e..5fe4fc02 100644 --- a/cell/src/main/java/org/ton/java/tlb/types/Transaction.java +++ b/cell/src/main/java/org/ton/java/tlb/types/Transaction.java @@ -1,5 +1,8 @@ package org.ton.java.tlb.types; +import static java.util.Objects.nonNull; + +import java.math.BigInteger; import lombok.Builder; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -8,12 +11,11 @@ import org.ton.java.cell.CellBuilder; import org.ton.java.cell.CellSlice; import org.ton.java.cell.TonHashMapE; - -import java.math.BigInteger; - -import static java.util.Objects.nonNull; +import org.ton.java.utils.Utils; /** + * + * *
  * transaction$0111
  *   account_addr:bits256
@@ -37,135 +39,142 @@
 @Data
 @Slf4j
 public class Transaction {
-    int magic;
-    BigInteger accountAddr;
-    BigInteger lt;
-    BigInteger prevTxHash;
-    BigInteger prevTxLt;
-    long now;
-    long outMsgCount;
-    AccountStates origStatus;
-    AccountStates endStatus;
-    TransactionIO inOut;
-    CurrencyCollection totalFees;
-    HashUpdate stateUpdate;
-    TransactionDescription description;
-
-    // not in scheme, but might be filled based on request data for flexibility
-    byte[] hash;
-
-    private String getMagic() {
-        return Long.toBinaryString(magic);
-    }
-
-    public String getAccountAddrShort() {
-        if (nonNull(accountAddr)) {
-            String str64 = StringUtils.leftPad(accountAddr.toString(16), 64, "0");
-            return str64.substring(0, 5)
-                    + "..."
-                    + str64.substring(str64.length() - 5);
-        } else {
-            return "N/A";
-        }
-    }
-
-    private String getPrevTxHash() {
-        if (nonNull(accountAddr)) {
-            return prevTxHash.toString(16);
-        } else {
-            return "null";
-        }
+  int magic;
+  BigInteger accountAddr;
+  BigInteger lt;
+  BigInteger prevTxHash;
+  BigInteger prevTxLt;
+  long now;
+  long outMsgCount;
+  AccountStates origStatus;
+  AccountStates endStatus;
+  TransactionIO inOut;
+  CurrencyCollection totalFees;
+  HashUpdate stateUpdate;
+  TransactionDescription description;
+
+  String hash; // not in tl-b scheme. equals to in_msg.hash in base64 - TODO testing
+
+  private String getMagic() {
+    return Long.toBinaryString(magic);
+  }
+
+  public String getAccountAddrShort() {
+    if (nonNull(accountAddr)) {
+      String str64 = StringUtils.leftPad(accountAddr.toString(16), 64, "0");
+      return str64.substring(0, 5) + "..." + str64.substring(str64.length() - 5);
+    } else {
+      return "N/A";
     }
+  }
 
-    public Cell toCell() {
-        CellBuilder c = CellBuilder.beginCell();
-        c.storeUint(0b0111, 4);
-        c.storeUint(accountAddr, 256);
-        c.storeUint(lt, 64);
-        c.storeUint(prevTxHash, 256);
-        c.storeUint(prevTxLt, 64);
-        c.storeUint(now, 32);
-        c.storeUint(outMsgCount, 15);
-        c.storeCell(serializeAccountState(origStatus));
-        c.storeCell(serializeAccountState(endStatus));
-        c.storeCell(totalFees.toCell());
-
-        c.storeRef(inOut.toCell());
-        c.storeRef(stateUpdate.toCell());
-        c.storeRef(description.toCell());
-
-        return c.endCell();
+  private String getPrevTxHash() {
+    if (nonNull(accountAddr)) {
+      return prevTxHash.toString(16);
+    } else {
+      return "null";
     }
-
-    public static Transaction deserialize(CellSlice cs) {
-        long magic = cs.loadUint(4).intValue();
-        assert (magic == 0b0111)
-                : "Transaction: magic not equal to 0b0111, found 0b" + Long.toBinaryString(magic);
-
-        Transaction tx =
-                Transaction.builder()
-                        .magic(0b0111)
-                        .accountAddr(cs.loadUint(256))
-                        .lt(cs.loadUint(64))
-                        .prevTxHash(cs.loadUint(256))
-                        .prevTxLt(cs.loadUint(64))
-                        .now(cs.loadUint(32).longValue())
-                        .outMsgCount(cs.loadUint(15).intValue())
-                        .origStatus(deserializeAccountState(cs.loadUint(2).byteValue()))
-                        .endStatus(deserializeAccountState(cs.loadUint(2).byteValueExact()))
-                        .build();
-
-        CellSlice inOutMsgs = CellSlice.beginParse(cs.loadRef());
-        Message msg =
-                inOutMsgs.loadBit() ? Message.deserialize(CellSlice.beginParse(inOutMsgs.loadRef())) : null;
-        TonHashMapE out =
-                inOutMsgs.loadDictE(
-                        15,
-                        k -> k.readInt(15),
-                        v -> Message.deserialize(CellSlice.beginParse(CellSlice.beginParse(v).loadRef())));
-
-        tx.setInOut(TransactionIO.builder().in(msg).out(out).build());
-
-        tx.setTotalFees(CurrencyCollection.deserialize(cs));
-        tx.setStateUpdate(HashUpdate.deserialize(CellSlice.beginParse(cs.loadRef())));
-        tx.setDescription(TransactionDescription.deserialize(CellSlice.beginParse(cs.loadRef())));
-
-        return tx;
+  }
+
+  public Cell toCell() {
+    CellBuilder c = CellBuilder.beginCell();
+    c.storeUint(0b0111, 4);
+    c.storeUint(accountAddr, 256);
+    c.storeUint(lt, 64);
+    c.storeUint(prevTxHash, 256);
+    c.storeUint(prevTxLt, 64);
+    c.storeUint(now, 32);
+    c.storeUint(outMsgCount, 15);
+    c.storeCell(serializeAccountState(origStatus));
+    c.storeCell(serializeAccountState(endStatus));
+    c.storeCell(totalFees.toCell());
+
+    c.storeRef(inOut.toCell());
+    c.storeRef(stateUpdate.toCell());
+    c.storeRef(description.toCell());
+
+    return c.endCell();
+  }
+
+  public static Transaction deserialize(CellSlice cs) {
+    long magic = cs.loadUint(4).intValue();
+    assert (magic == 0b0111)
+        : "Transaction: magic not equal to 0b0111, found 0b" + Long.toBinaryString(magic);
+
+    Transaction tx =
+        Transaction.builder()
+            .magic(0b0111)
+            .accountAddr(cs.loadUint(256))
+            .lt(cs.loadUint(64))
+            .prevTxHash(cs.loadUint(256))
+            .prevTxLt(cs.loadUint(64))
+            .now(cs.loadUint(32).longValue())
+            .outMsgCount(cs.loadUint(15).intValue())
+            .origStatus(deserializeAccountState(cs.loadUint(2).byteValue()))
+            .endStatus(deserializeAccountState(cs.loadUint(2).byteValueExact()))
+            .build();
+
+    CellSlice inOutMsgs = CellSlice.beginParse(cs.loadRef());
+    Message msg =
+        inOutMsgs.loadBit() ? Message.deserialize(CellSlice.beginParse(inOutMsgs.loadRef())) : null;
+    TonHashMapE out =
+        inOutMsgs.loadDictE(
+            15,
+            k -> k.readInt(15),
+            v -> Message.deserialize(CellSlice.beginParse(CellSlice.beginParse(v).loadRef())));
+
+    tx.setInOut(TransactionIO.builder().in(msg).out(out).build());
+
+    tx.setTotalFees(CurrencyCollection.deserialize(cs));
+    tx.setStateUpdate(HashUpdate.deserialize(CellSlice.beginParse(cs.loadRef())));
+    tx.setDescription(TransactionDescription.deserialize(CellSlice.beginParse(cs.loadRef())));
+    if (nonNull(msg)) {
+      tx.setHash(Utils.bytesToBase64(msg.toCell().getHash()));
     }
-
-    public static Cell serializeAccountState(AccountStates state) {
-        switch (state) {
-            case UNINIT: {
-                return CellBuilder.beginCell().storeUint(0, 2).endCell();
-            }
-            case FROZEN: {
-                return CellBuilder.beginCell().storeUint(1, 2).endCell();
-            }
-            case ACTIVE: {
-                return CellBuilder.beginCell().storeUint(2, 2).endCell();
-            }
-            case NON_EXIST: {
-                return CellBuilder.beginCell().storeUint(3, 2).endCell();
-            }
+    return tx;
+  }
+
+  public static Cell serializeAccountState(AccountStates state) {
+    switch (state) {
+      case UNINIT:
+        {
+          return CellBuilder.beginCell().storeUint(0, 2).endCell();
+        }
+      case FROZEN:
+        {
+          return CellBuilder.beginCell().storeUint(1, 2).endCell();
+        }
+      case ACTIVE:
+        {
+          return CellBuilder.beginCell().storeUint(2, 2).endCell();
+        }
+      case NON_EXIST:
+        {
+          return CellBuilder.beginCell().storeUint(3, 2).endCell();
         }
-        return null;
     }
-
-    public static AccountStates deserializeAccountState(byte state) {
-        switch (state) {
-            case 0: {
-                return AccountStates.UNINIT;
-            }
-            case 1: {
-                return AccountStates.FROZEN;
-            }
-            case 2: {
-                return AccountStates.ACTIVE;
-            }
-            case 3: {
-                return AccountStates.NON_EXIST;
-            }
+    return null;
+  }
+
+  public static AccountStates deserializeAccountState(byte state) {
+    switch (state) {
+      case 0:
+        {
+          return AccountStates.UNINIT;
+        }
+      case 1:
+        {
+          return AccountStates.FROZEN;
+        }
+      case 2:
+        {
+          return AccountStates.ACTIVE;
+        }
+      case 3:
+        {
+          return AccountStates.NON_EXIST;
         }
-        return null;
     }
+    return null;
+  }
 }
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/highload/HighloadWalletV3.java b/smartcontract/src/main/java/org/ton/java/smartcontract/highload/HighloadWalletV3.java
index 84b3545b..069f0b0e 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/highload/HighloadWalletV3.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/highload/HighloadWalletV3.java
@@ -13,14 +13,12 @@
 import org.ton.java.address.Address;
 import org.ton.java.cell.Cell;
 import org.ton.java.cell.CellBuilder;
-import org.ton.java.smartcontract.types.Destination;
-import org.ton.java.smartcontract.types.HighloadV3Config;
-import org.ton.java.smartcontract.types.HighloadV3InternalMessageBody;
-import org.ton.java.smartcontract.types.WalletCodes;
+import org.ton.java.smartcontract.types.*;
 import org.ton.java.smartcontract.wallet.Contract;
 import org.ton.java.tlb.types.*;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.tonlib.types.RunResult;
 import org.ton.java.tonlib.types.TvmStackEntryNumber;
 import org.ton.java.utils.Utils;
@@ -182,6 +180,35 @@ public ExtMessageInfo send(HighloadV3Config highloadConfig) {
     return tonlib.sendRawMessage(externalMessage.toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(HighloadV3Config highloadConfig) {
+    Address ownAddress = getAddress();
+
+    Cell body = createTransferMessage(highloadConfig);
+
+    Message externalMessage =
+        Message.builder()
+            .info(
+                ExternalMessageInInfo.builder()
+                    .dstAddr(
+                        MsgAddressIntStd.builder()
+                            .workchainId(ownAddress.wc)
+                            .address(ownAddress.toBigInteger())
+                            .build())
+                    .build())
+            .body(
+                CellBuilder.beginCell()
+                    .storeBytes(
+                        Utils.signData(keyPair.getPublicKey(), keyPair.getSecretKey(), body.hash()))
+                    .storeRef(body)
+                    .endCell())
+            .build();
+    return tonlib.sendRawMessageWithConfirmation(externalMessage.toCell().toBase64(), getAddress());
+  }
+
   public ExtMessageInfo deploy(HighloadV3Config highloadConfig) {
     Address ownAddress = getAddress();
 
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/lockup/LockupWalletV1.java b/smartcontract/src/main/java/org/ton/java/smartcontract/lockup/LockupWalletV1.java
index 74e2adf9..ed861edf 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/lockup/LockupWalletV1.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/lockup/LockupWalletV1.java
@@ -23,6 +23,7 @@
 import org.ton.java.tlb.types.*;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.tonlib.types.RunResult;
 import org.ton.java.tonlib.types.TvmStackEntryNumber;
 import org.ton.java.utils.Utils;
@@ -305,4 +306,24 @@ public ExtMessageInfo send(LockupWalletV1Config config) {
 
     return tonlib.sendRawMessage(externalMessage.toCell().toBase64());
   }
+
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(LockupWalletV1Config config) {
+    Cell body = createTransferBody(config);
+
+    Message externalMessage =
+        Message.builder()
+            .info(ExternalMessageInInfo.builder().dstAddr(getAddressIntStd()).build())
+            .body(
+                CellBuilder.beginCell()
+                    .storeBytes(
+                        Utils.signData(keyPair.getPublicKey(), keyPair.getSecretKey(), body.hash()))
+                    .storeCell(body)
+                    .endCell())
+            .build();
+    return tonlib.sendRawMessageWithConfirmation(externalMessage.toCell().toBase64(), getAddress());
+  }
 }
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java
index 4df9a551..16d7948f 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java
@@ -1,5 +1,7 @@
 package org.ton.java.smartcontract.wallet;
 
+import java.math.BigInteger;
+import java.util.List;
 import org.apache.commons.lang3.StringUtils;
 import org.ton.java.address.Address;
 import org.ton.java.cell.Cell;
@@ -12,173 +14,180 @@
 import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.utils.Utils;
 
-import java.math.BigInteger;
-import java.util.List;
-
-/**
- * Interface for all smart contract objects in ton4j.
- */
+/** Interface for all smart contract objects in ton4j. */
 public interface Contract {
 
-    Tonlib getTonlib();
-
-    /**
-     * Used for late tonlib assignment
-     *
-     * @param pTonlib Tonlib instance
-     */
-    void setTonlib(Tonlib pTonlib);
-
-    long getWorkchain();
-
-    String getName();
-
-    default Address getAddress() {
-        return StateInit.builder()
-                .code(createCodeCell())
-                .data(createDataCell())
-                .build()
-                .getAddress(getWorkchain());
-    }
-
-    default Address getAddress(byte workchain) {
-        return getStateInit().getAddress(workchain);
-    }
-
-    default MsgAddressIntStd getAddressIntStd() {
-        Address ownAddress = getStateInit().getAddress(getWorkchain());
-        return MsgAddressIntStd.builder()
-                .workchainId(ownAddress.wc)
-                .address(ownAddress.toBigInteger())
-                .build();
-    }
-
-    default MsgAddressIntStd getAddressIntStd(int workchain) {
-        Address ownAddress = getStateInit().getAddress();
-        return MsgAddressIntStd.builder()
-                .workchainId((byte) workchain)
-                .address(ownAddress.toBigInteger())
-                .build();
-    }
-
-    /**
-     * @return Cell containing contact code
-     */
-    Cell createCodeCell();
-
-    /**
-     * Method to override
-     *
-     * @return {Cell} cell contains contract data
-     */
-    Cell createDataCell();
-
-    default Cell createLibraryCell() {
-        return null;
-    }
-
-    /**
-     * Message StateInit consists of initial contract code, data and address in a blockchain
-     *
-     * @return StateInit
-     */
-    default StateInit getStateInit() {
-        return StateInit.builder()
-                .code(createCodeCell())
-                .data(createDataCell())
-                .lib(createLibraryCell())
-                .build();
-    }
-
-    default long getSeqno() {
-
-        if (this instanceof WalletV1R1) {
-            throw new Error("Wallet V1R1 does not have seqno method");
-        }
-
-        return getTonlib().getSeqno(getAddress());
-    }
-
-    default boolean isDeployed() {
-        return StringUtils.isNotEmpty(getTonlib().getRawAccountState(getAddress()).getCode());
-    }
-
-    default void waitForDeployment(int timeoutSeconds) {
-        System.out.println("waiting for deployment (up to " + timeoutSeconds + "s)");
-        int i = 0;
-        do {
-            if (++i * 2 >= timeoutSeconds) {
-                throw new Error("Can't deploy contract within specified timeout.");
-            }
-            Utils.sleep(2);
-        } while (!isDeployed());
-    }
-
-    default void waitForBalanceChange(int timeoutSeconds) {
-        System.out.println(
-                "waiting for balance change (up to "
-                        + timeoutSeconds
-                        + "s) - "
-                        + (getTonlib().isTestnet()
-                        ? getAddress().toBounceableTestnet()
-                        : getAddress().toBounceable())
-                        + " ("
-                        + getAddress().toRaw()
-                        + ")");
-        BigInteger initialBalance = getBalance();
-        int i = 0;
-        do {
-            if (++i * 2 >= timeoutSeconds) {
-                throw new Error("Balance was not changed within specified timeout.");
-            }
-            Utils.sleep(2);
-        } while (initialBalance.equals(getBalance()));
-    }
-
-    default BigInteger getBalance() {
-        return new BigInteger(getTonlib().getAccountState(getAddress()).getBalance());
-    }
-
-    default List getTransactions(int historyLimit) {
-        return getTonlib()
-                .getAllRawTransactions(getAddress().toBounceable(), BigInteger.ZERO, null, historyLimit)
-                .getTransactions();
-    }
-
-    default List getTransactions() {
-        return getTonlib()
-                .getAllRawTransactions(getAddress().toBounceable(), BigInteger.ZERO, null, 20)
-                .getTransactions();
-    }
-
-    default Message prepareDeployMsg() {
-        throw new Error("not implemented");
-    }
-
-    default Message prepareExternalMsg(WalletConfig config) {
-        throw new Error("not implemented");
-    }
-
-    default BigInteger getGasFees() {
-        switch (getName()) {
-            case "V1R1":
-                return BigInteger.valueOf(40000); // 0.00004 toncoins
-            case "V1R2":
-                return BigInteger.valueOf(40000);
-            case "V1R3":
-                return BigInteger.valueOf(40000);
-            case "V2R1":
-                return BigInteger.valueOf(40000);
-            case "V2R2":
-                return BigInteger.valueOf(40000);
-            case "V3R1":
-                return BigInteger.valueOf(40000);
-            case "V3R2":
-                return BigInteger.valueOf(40000);
-            case "V4R2":
-                return BigInteger.valueOf(310000);
-            default:
-                throw new Error("Unknown wallet version");
-        }
-    }
+  Tonlib getTonlib();
+
+  /**
+   * Used for late tonlib assignment
+   *
+   * @param pTonlib Tonlib instance
+   */
+  void setTonlib(Tonlib pTonlib);
+
+  long getWorkchain();
+
+  String getName();
+
+  default Address getAddress() {
+    return StateInit.builder()
+        .code(createCodeCell())
+        .data(createDataCell())
+        .build()
+        .getAddress(getWorkchain());
+  }
+
+  default Address getAddress(byte workchain) {
+    return getStateInit().getAddress(workchain);
+  }
+
+  default MsgAddressIntStd getAddressIntStd() {
+    Address ownAddress = getStateInit().getAddress(getWorkchain());
+    return MsgAddressIntStd.builder()
+        .workchainId(ownAddress.wc)
+        .address(ownAddress.toBigInteger())
+        .build();
+  }
+
+  default MsgAddressIntStd getAddressIntStd(int workchain) {
+    Address ownAddress = getStateInit().getAddress();
+    return MsgAddressIntStd.builder()
+        .workchainId((byte) workchain)
+        .address(ownAddress.toBigInteger())
+        .build();
+  }
+
+  /**
+   * @return Cell containing contact code
+   */
+  Cell createCodeCell();
+
+  /**
+   * Method to override
+   *
+   * @return {Cell} cell contains contract data
+   */
+  Cell createDataCell();
+
+  default Cell createLibraryCell() {
+    return null;
+  }
+
+  /**
+   * Message StateInit consists of initial contract code, data and address in a blockchain
+   *
+   * @return StateInit
+   */
+  default StateInit getStateInit() {
+    return StateInit.builder()
+        .code(createCodeCell())
+        .data(createDataCell())
+        .lib(createLibraryCell())
+        .build();
+  }
+
+  default long getSeqno() {
+
+    if (this instanceof WalletV1R1) {
+      throw new Error("Wallet V1R1 does not have seqno method");
+    }
+
+    return getTonlib().getSeqno(getAddress());
+  }
+
+  default boolean isDeployed() {
+    return StringUtils.isNotEmpty(getTonlib().getRawAccountState(getAddress()).getCode());
+  }
+
+  /** Checks every 2 seconds for 60 seconds if account state was deployed at address */
+  default void waitForDeployment() {
+    waitForDeployment(60);
+  }
+
+  /** Checks every 2 seconds for timeoutSeconds if account state was deployed at address */
+  default void waitForDeployment(int timeoutSeconds) {
+    System.out.println("waiting for deployment (up to " + timeoutSeconds + "s)");
+    int i = 0;
+    do {
+      if (++i * 2 >= timeoutSeconds) {
+        throw new Error("Can't deploy contract within specified timeout.");
+      }
+      Utils.sleep(2);
+    } while (!isDeployed());
+  }
+
+  /** Checks every 2 seconds for 60 if account balance was changed */
+  default void waitForBalanceChange() {
+    waitForDeployment(60);
+  }
+
+  /** Checks every 2 seconds for timeoutSeconds if account balance was changed */
+  default void waitForBalanceChange(int timeoutSeconds) {
+    System.out.println(
+        "waiting for balance change (up to "
+            + timeoutSeconds
+            + "s) - "
+            + (getTonlib().isTestnet()
+                ? getAddress().toBounceableTestnet()
+                : getAddress().toBounceable())
+            + " ("
+            + getAddress().toRaw()
+            + ")");
+    BigInteger initialBalance = getBalance();
+    int i = 0;
+    do {
+      if (++i * 2 >= timeoutSeconds) {
+        throw new Error("Balance was not changed within specified timeout.");
+      }
+      Utils.sleep(2);
+    } while (initialBalance.equals(getBalance()));
+  }
+
+  default BigInteger getBalance() {
+    return new BigInteger(getTonlib().getAccountState(getAddress()).getBalance());
+  }
+
+  default List getTransactions(int historyLimit) {
+    return getTonlib()
+        .getAllRawTransactions(getAddress().toBounceable(), BigInteger.ZERO, null, historyLimit)
+        .getTransactions();
+  }
+
+  default List getTransactions() {
+    return getTonlib()
+        .getAllRawTransactions(getAddress().toBounceable(), BigInteger.ZERO, null, 20)
+        .getTransactions();
+  }
+
+  default Message prepareDeployMsg() {
+    throw new Error("not implemented");
+  }
+
+  default Message prepareExternalMsg(WalletConfig config) {
+    throw new Error("not implemented");
+  }
+
+  default BigInteger getGasFees() {
+    switch (getName()) {
+      case "V1R1":
+        return BigInteger.valueOf(40000); // 0.00004 toncoins
+      case "V1R2":
+        return BigInteger.valueOf(40000);
+      case "V1R3":
+        return BigInteger.valueOf(40000);
+      case "V2R1":
+        return BigInteger.valueOf(40000);
+      case "V2R2":
+        return BigInteger.valueOf(40000);
+      case "V3R1":
+        return BigInteger.valueOf(40000);
+      case "V3R2":
+        return BigInteger.valueOf(40000);
+      case "V4R2":
+        return BigInteger.valueOf(310000);
+      default:
+        throw new Error("Unknown wallet version");
+    }
+  }
 }
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R1.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R1.java
index 861cf647..12a03789 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R1.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R1.java
@@ -16,6 +16,7 @@
 import org.ton.java.tlb.types.*;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.utils.Utils;
 
 @Builder
@@ -53,7 +54,7 @@ public Tonlib getTonlib() {
   public void setTonlib(Tonlib pTonlib) {
     tonlib = pTonlib;
   }
-  
+
   @Override
   public long getWorkchain() {
     return wc;
@@ -121,6 +122,15 @@ public ExtMessageInfo send(WalletV1R1Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV1R1Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   public Message prepareExternalMsg(WalletV1R1Config config) {
     Cell body = createTransferBody(config);
     return MsgUtils.createExternalMessageWithSignedBody(keyPair, getAddress(), null, body);
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R2.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R2.java
index 3a6cc681..eb279830 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R2.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R2.java
@@ -16,6 +16,7 @@
 import org.ton.java.tlb.types.*;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.utils.Utils;
 
 @Builder
@@ -121,6 +122,15 @@ public ExtMessageInfo send(WalletV1R2Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV1R2Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   public Message prepareExternalMsg(WalletV1R2Config config) {
     Cell body = createTransferBody(config);
     return MsgUtils.createExternalMessageWithSignedBody(keyPair, getAddress(), null, body);
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R3.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R3.java
index da47bc7a..8cc79c8d 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R3.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v1/WalletV1R3.java
@@ -17,6 +17,7 @@
 import org.ton.java.tlb.types.*;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.tonlib.types.RunResult;
 import org.ton.java.tonlib.types.TvmStackEntryNumber;
 import org.ton.java.utils.Utils;
@@ -136,6 +137,15 @@ public ExtMessageInfo send(WalletV1R3Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV1R3Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   public Message prepareExternalMsg(WalletV1R3Config config) {
     Cell body = createTransferBody(config);
     return MsgUtils.createExternalMessageWithSignedBody(keyPair, getAddress(), null, body);
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v2/WalletV2R1.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v2/WalletV2R1.java
index 4f05aa47..deedec3e 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v2/WalletV2R1.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v2/WalletV2R1.java
@@ -20,6 +20,7 @@
 import org.ton.java.tlb.types.Message;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.utils.Utils;
 
 @Builder
@@ -150,6 +151,15 @@ public ExtMessageInfo send(WalletV2R1Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV2R1Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   public Message prepareExternalMsg(WalletV2R1Config config) {
     Cell body = createTransferBody(config);
     return MsgUtils.createExternalMessageWithSignedBody(keyPair, getAddress(), null, body);
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v2/WalletV2R2.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v2/WalletV2R2.java
index 5f9cad77..e3172513 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v2/WalletV2R2.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v2/WalletV2R2.java
@@ -21,6 +21,7 @@
 import org.ton.java.tlb.types.Message;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.tonlib.types.RunResult;
 import org.ton.java.tonlib.types.TvmStackEntryNumber;
 import org.ton.java.utils.Utils;
@@ -166,6 +167,15 @@ public ExtMessageInfo send(WalletV2R2Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV2R2Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   public Message prepareExternalMsg(WalletV2R2Config config) {
     Cell body = createTransferBody(config);
     return MsgUtils.createExternalMessageWithSignedBody(keyPair, getAddress(), null, body);
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v3/WalletV3R1.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v3/WalletV3R1.java
index d5785dae..7eaa1b69 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v3/WalletV3R1.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v3/WalletV3R1.java
@@ -16,6 +16,7 @@
 import org.ton.java.tlb.types.*;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.utils.Utils;
 
 @Builder
@@ -160,6 +161,15 @@ public ExtMessageInfo send(WalletV3Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV3Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   public Message prepareExternalMsg(WalletV3Config config) {
     Cell body = createTransferBody(config);
     return MsgUtils.createExternalMessageWithSignedBody(keyPair, getAddress(), null, body);
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v3/WalletV3R2.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v3/WalletV3R2.java
index 8482add6..81e5c427 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v3/WalletV3R2.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v3/WalletV3R2.java
@@ -16,6 +16,7 @@
 import org.ton.java.tlb.types.*;
 import org.ton.java.tonlib.Tonlib;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.tonlib.types.RunResult;
 import org.ton.java.tonlib.types.TvmStackEntryNumber;
 import org.ton.java.utils.Utils;
@@ -167,6 +168,15 @@ public ExtMessageInfo send(WalletV3Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV3Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   public Message prepareExternalMsg(WalletV3Config config) {
     Cell body = createTransferBody(config);
     return MsgUtils.createExternalMessageWithSignedBody(keyPair, getAddress(), null, body);
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v4/WalletV4R2.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v4/WalletV4R2.java
index 65300bcc..26287f25 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v4/WalletV4R2.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v4/WalletV4R2.java
@@ -399,6 +399,15 @@ public ExtMessageInfo send(WalletV4R2Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV4R2Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   public Message prepareExternalMsg(WalletV4R2Config config) {
     Cell body = createTransferBody(config);
     return MsgUtils.createExternalMessageWithSignedBody(keyPair, getAddress(), null, body);
diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v5/WalletV5.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v5/WalletV5.java
index 3d293271..f2fc19b6 100644
--- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v5/WalletV5.java
+++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/v5/WalletV5.java
@@ -12,10 +12,8 @@
 import org.apache.commons.lang3.StringUtils;
 import org.ton.java.address.Address;
 import org.ton.java.cell.*;
+import org.ton.java.smartcontract.types.*;
 import org.ton.java.smartcontract.types.Destination;
-import org.ton.java.smartcontract.types.WalletCodes;
-import org.ton.java.smartcontract.types.WalletV5Config;
-import org.ton.java.smartcontract.types.WalletV5InnerRequest;
 import org.ton.java.smartcontract.wallet.Contract;
 import org.ton.java.tlb.types.*;
 import org.ton.java.tonlib.Tonlib;
@@ -148,6 +146,15 @@ public ExtMessageInfo send(WalletV5Config config) {
     return tonlib.sendRawMessage(prepareExternalMsg(config).toCell().toBase64());
   }
 
+  /**
+   * Sends amount of nano toncoins to destination address and waits till message found among
+   * account's transactions
+   */
+  public RawTransaction sendWithConfirmation(WalletV5Config config) {
+    return tonlib.sendRawMessageWithConfirmation(
+        prepareExternalMsg(config).toCell().toBase64(), getAddress());
+  }
+
   /** Deploy wallet without any extensions. One can be installed later into the wallet. */
   public ExtMessageInfo deploy() {
     return tonlib.sendRawMessage(prepareDeployMsg().toCell().toBase64());
diff --git a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/CommonTest.java b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/CommonTest.java
index 8148349e..d78eaa6b 100644
--- a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/CommonTest.java
+++ b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/CommonTest.java
@@ -9,13 +9,14 @@
 @Slf4j
 @RunWith(JUnit4.class)
 public class CommonTest {
-    static Tonlib tonlib;
+  static Tonlib tonlib;
 
-    @BeforeClass
-    public static void setUpBeforeClass() {
-        tonlib = Tonlib.builder()
-                .testnet(true)
-                .ignoreCache(false)
-                .build();
-    }
+  static String tonlibPath =
+      System.getProperty("user.dir") + "/../2.ton-test-artifacts/tonlibjson.dll";
+
+  @BeforeClass
+  public static void setUpBeforeClass() {
+    tonlib =
+        Tonlib.builder().testnet(true).pathToTonlibSharedLib(tonlibPath).ignoreCache(false).build();
+  }
 }
diff --git a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV2R1Short.java b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV2R1Short.java
index adfe59a9..2d2e70a9 100644
--- a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV2R1Short.java
+++ b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV2R1Short.java
@@ -13,6 +13,7 @@
 import org.ton.java.smartcontract.types.WalletV2R1Config;
 import org.ton.java.smartcontract.wallet.v2.WalletV2R1;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.utils.Utils;
 
 @Slf4j
@@ -40,7 +41,7 @@ public void testWalletV2R1() throws InterruptedException {
     ExtMessageInfo extMessageInfo = contract.deploy();
     assertThat(extMessageInfo.getError().getCode()).isZero();
 
-    contract.waitForDeployment(20);
+    contract.waitForDeployment();
 
     // transfer coins from new wallet (back to faucet)
     WalletV2R1Config config =
@@ -50,11 +51,11 @@ public void testWalletV2R1() throws InterruptedException {
             .amount1(Utils.toNano(0.1))
             .build();
 
-    extMessageInfo = contract.send(config);
-    assertThat(extMessageInfo.getError().getCode()).isZero();
+    RawTransaction rawTransaction = contract.sendWithConfirmation(config);
+    assertThat(rawTransaction).isNotNull();
 
     log.info("sending to one destination");
-    contract.waitForBalanceChange(90);
+    contract.waitForBalanceChange();
 
     // multi send
     config =
diff --git a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV3R1.java b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV3R1.java
index ec30bc59..b2a75f09 100644
--- a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV3R1.java
+++ b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV3R1.java
@@ -17,6 +17,7 @@
 import org.ton.java.smartcontract.wallet.v3.WalletV3R1;
 import org.ton.java.tlb.types.Message;
 import org.ton.java.tonlib.types.ExtMessageInfo;
+import org.ton.java.tonlib.types.RawTransaction;
 import org.ton.java.utils.Utils;
 
 @Slf4j
@@ -89,4 +90,54 @@ public void testWalletV3R1() throws InterruptedException {
     log.info("new wallet {} balance: {}", contract.getName(), Utils.formatNanoValue(balance));
     assertThat(balance.longValue()).isLessThan(Utils.toNano(0.2).longValue());
   }
+
+  @Test
+  public void testWalletV3R1SendRawMessageWithConfirmation() throws InterruptedException {
+    TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair();
+
+    WalletV3R1 contract = WalletV3R1.builder().tonlib(tonlib).keyPair(keyPair).walletId(42).build();
+
+    Message msg =
+        MsgUtils.createExternalMessageWithSignedBody(
+            keyPair,
+            contract.getAddress(),
+            contract.getStateInit(),
+            CellBuilder.beginCell()
+                .storeUint(42, 32) // subwallet
+                .storeUint(Instant.now().getEpochSecond() + 5 * 60L, 32) // valid-until
+                .storeUint(0, 32) // seqno
+                .endCell());
+    Address address = msg.getInit().getAddress();
+
+    // top up new wallet using test-faucet-wallet
+    BigInteger balance =
+        TestnetFaucet.topUpContract(tonlib, Address.of(address.toNonBounceable()), Utils.toNano(1));
+    log.info("new wallet {} balance: {}", contract.getName(), Utils.formatNanoValue(balance));
+
+    // deploy new wallet
+    RawTransaction tx = tonlib.sendRawMessageWithConfirmation(msg.toCell().toBase64(), address);
+    log.info("msg found in tx {}", tx);
+    assertThat(tx).isNotNull();
+
+    contract.waitForDeployment(40);
+
+    // try to transfer coins from new wallet (back to faucet)
+    WalletV3Config config =
+        WalletV3Config.builder()
+            .seqno(contract.getSeqno())
+            .walletId(42)
+            .destination(Address.of(TestnetFaucet.BOUNCEABLE))
+            .amount(Utils.toNano(0.8))
+            .comment("testWalletV3R1")
+            .build();
+
+    ExtMessageInfo extMessageInfo = contract.send(config);
+    assertThat(extMessageInfo.getError().getCode()).isZero();
+
+    contract.waitForBalanceChange(90);
+
+    balance = contract.getBalance();
+    log.info("new wallet {} balance: {}", contract.getName(), Utils.formatNanoValue(balance));
+    assertThat(balance.longValue()).isLessThan(Utils.toNano(0.2).longValue());
+  }
 }
diff --git a/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java b/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java
index abacc09b..fd01d524 100644
--- a/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java
+++ b/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java
@@ -1395,6 +1395,46 @@ public ExtMessageInfo sendRawMessage(String serializedBoc) {
     }
   }
 
+  /**
+   * Sends raw message, bag of cells encoded in base64, with deliver confirmation. After the message
+   * has been sent to the network this method looks up specified account transactions and returns
+   * true if message was found among them. Timeout 60 seconds.
+   *
+   * @param serializedBoc - base64 encoded BoC
+   * @return RawTransaction in case of success, null if message not found within a timeout and
+   *     throws Error in case of failure.
+   */
+  public RawTransaction sendRawMessageWithConfirmation(String serializedBoc, Address account) {
+    synchronized (gson) {
+      SendRawMessageQuery sendMessageQuery =
+          SendRawMessageQuery.builder().body(serializedBoc).build();
+
+      String result = syncAndRead(gson.toJson(sendMessageQuery));
+
+      if ((isNull(result)) || (result.contains("@type") && result.contains("error"))) {
+        TonlibError error = gson.fromJson(result, TonlibError.class);
+        throw new Error("Cannot send message. Error " + error.toString());
+      } else {
+        ExtMessageInfo extMessageInfo = gson.fromJson(result, ExtMessageInfo.class);
+        extMessageInfo.setError(TonlibError.builder().code(0).build());
+        log.info("Message has been successfully sent. Waiting for deliver. {}", extMessageInfo);
+        RawTransactions rawTransactions = null;
+        for (int i = 0; i < 12; i++) {
+          rawTransactions = getRawTransactions(account.toRaw(), null, null);
+          for (RawTransaction tx : rawTransactions.getTransactions()) {
+            if (nonNull(tx.getIn_msg())
+                && tx.getIn_msg().getHash().equals(extMessageInfo.getHash())) {
+              log.info("Message has been delivered.");
+              return tx;
+            }
+          }
+          Utils.sleep(5);
+        }
+        return null;
+      }
+    }
+  }
+
   public QueryFees estimateFees(
       String destinationAddress,
       String body,