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 ListgetTransactions(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,