diff --git a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java
index e1f57a7e..730421a4 100644
--- a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java
+++ b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java
@@ -8,10 +8,10 @@ import io.swagger.v3.oas.annotations.media.Schema;
@XmlAccessorType(XmlAccessType.FIELD)
public class CrossChainCancelRequest {
- @Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
- public byte[] creatorPublicKey;
+ @Schema(description = "AT's trade public key", example = "K6wuddsBV3HzRrXFFezE7P5MoRXp5m3mEDokRDGZB6ry")
+ public byte[] tradePublicKey;
- @Schema(description = "Qortal AT address")
+ @Schema(description = "Qortal trade AT address")
public String atAddress;
public CrossChainCancelRequest() {
diff --git a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java
index f0b5d0d1..52b9f125 100644
--- a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java
+++ b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java
@@ -14,8 +14,11 @@ public class CrossChainSecretRequest {
@Schema(description = "Qortal AT address")
public String atAddress;
- @Schema(description = "secret-A + secret-B (64 bytes)", example = "2gt2nSVBFknLfdU5buKtScLuTibkt9C3x6PZVqnA3AJ6BdEf3A9RbSj5Hn5QkvavdTTfmttNEaYEVw34TZdz135Q")
- public byte[] secret;
+ @Schema(description = "secret-A (32 bytes)", example = "FHMzten4he9jZ4HGb4297Utj6F5g2w7serjq2EnAg2s1")
+ public byte[] secretA;
+
+ @Schema(description = "secret-B (32 bytes)", example = "EN2Bgx3BcEMtxFCewmCVSMkfZjVKYhx3KEXC5A21KBGx")
+ public byte[] secretB;
public CrossChainSecretRequest() {
}
diff --git a/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java
index 32737dd5..3a632bf3 100644
--- a/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java
+++ b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java
@@ -9,13 +9,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class CrossChainTradeRequest {
@Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
- public byte[] creatorPublicKey;
+ public byte[] tradePublicKey;
@Schema(description = "Qortal AT address")
public String atAddress;
- @Schema(description = "Qortal address for trade partner/recipient")
- public String recipient;
+ @Schema(description = "Signature of trading partner's MESSAGE transaction")
+ public byte[] messageTransactionSignature;
public CrossChainTradeRequest() {
}
diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java
index 6db79ceb..a68eb0e5 100644
--- a/src/main/java/org/qortal/api/resource/CrossChainResource.java
+++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java
@@ -68,6 +68,7 @@ import org.qortal.repository.RepositoryManager;
import org.qortal.transaction.DeployAtTransaction;
import org.qortal.transaction.MessageTransaction;
import org.qortal.transaction.Transaction;
+import org.qortal.transaction.Transaction.TransactionType;
import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
@@ -76,8 +77,6 @@ import org.qortal.transform.transaction.MessageTransactionTransformer;
import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
-import com.google.common.primitives.Bytes;
-
@Path("/crosschain")
@Tag(name = "Cross-Chain")
public class CrossChainResource {
@@ -224,7 +223,7 @@ public class CrossChainResource {
summary = "Builds raw, unsigned MESSAGE transaction that sends cross-chain trade recipient address, triggering 'trade' mode",
description = "Specify address of cross-chain AT that needs to be messaged, and address of Qortal recipient.
"
+ "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
"
- + "You need to sign output with same account as the AT creator otherwise the MESSAGE transaction will be invalid.",
+ + "You need to sign output with trade private key otherwise the MESSAGE transaction will be invalid.",
requestBody = @RequestBody(
required = true,
content = @Content(
@@ -248,28 +247,52 @@ public class CrossChainResource {
public String sendTradeRecipient(CrossChainTradeRequest tradeRequest) {
Security.checkApiCallAllowed(request);
- byte[] creatorPublicKey = tradeRequest.creatorPublicKey;
+ byte[] tradePublicKey = tradeRequest.tradePublicKey;
- if (creatorPublicKey == null || creatorPublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
+ if (tradePublicKey == null || tradePublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
if (tradeRequest.atAddress == null || !Crypto.isValidAtAddress(tradeRequest.atAddress))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
- if (tradeRequest.recipient == null || !Crypto.isValidAddress(tradeRequest.recipient))
+ if (tradeRequest.messageTransactionSignature == null || !Crypto.isValidAddress(tradeRequest.messageTransactionSignature))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, creatorPublicKey, tradeRequest.atAddress);
+ ATData atData = fetchAtDataWithChecking(repository, tradeRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
+ // Does supplied public key match trade public key?
+ if (tradePublicKey != null && !Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress))
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
+
+ TransactionData transactionData = repository.getTransactionRepository().fromSignature(tradeRequest.messageTransactionSignature);
+ if (transactionData == null)
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_UNKNOWN);
+
+ if (transactionData.getType() != TransactionType.MESSAGE)
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID);
+
+ MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData;
+ byte[] messageData = messageTransactionData.getData();
+ BTCACCT.OfferMessageData offerMessageData = BTCACCT.extractOfferMessageData(messageData);
+ if (offerMessageData == null)
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID);
+
// Good to make MESSAGE
- byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(tradeRequest.recipient), 32, 0);
- byte[] messageTransactionBytes = buildAtMessage(repository, creatorPublicKey, tradeRequest.atAddress, recipientAddressBytes);
+ byte[] aliceForeignPublicKeyHash = offerMessageData.recipientBitcoinPKH;
+ byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
+ int lockTimeA = (int) offerMessageData.lockTimeA;
+
+ String aliceNativeAddress = Crypto.toAddress(messageTransactionData.getCreatorPublicKey());
+ int lockTimeB = BTCACCT.calcLockTimeB(messageTransactionData.getTimestamp(), lockTimeA);
+
+ byte[] outgoingMessageData = BTCACCT.buildTradeMessage(aliceNativeAddress, aliceForeignPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
+ byte[] messageTransactionBytes = buildAtMessage(repository, tradePublicKey, tradeRequest.atAddress, outgoingMessageData);
return Base58.encode(messageTransactionBytes);
} catch (DataException e) {
@@ -315,11 +338,14 @@ public class CrossChainResource {
if (secretRequest.atAddress == null || !Crypto.isValidAtAddress(secretRequest.atAddress))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
- if (secretRequest.secret == null || secretRequest.secret.length != BTCACCT.SECRET_LENGTH * 2)
+ if (secretRequest.secretA == null || secretRequest.secretA.length != BTCACCT.SECRET_LENGTH)
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
+
+ if (secretRequest.secretB == null || secretRequest.secretB.length != BTCACCT.SECRET_LENGTH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, null, secretRequest.atAddress); // null to skip creator check
+ ATData atData = fetchAtDataWithChecking(repository, secretRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
if (crossChainTradeData.mode == CrossChainTradeData.Mode.OFFER)
@@ -334,7 +360,8 @@ public class CrossChainResource {
// Good to make MESSAGE
- byte[] messageTransactionBytes = buildAtMessage(repository, recipientPublicKey, secretRequest.atAddress, secretRequest.secret);
+ byte[] messageData = BTCACCT.buildRedeemMessage(secretRequest.secretA, secretRequest.secretB);
+ byte[] messageTransactionBytes = buildAtMessage(repository, recipientPublicKey, secretRequest.atAddress, messageData);
return Base58.encode(messageTransactionBytes);
} catch (DataException e) {
@@ -348,7 +375,7 @@ public class CrossChainResource {
summary = "Builds raw, unsigned MESSAGE transaction that cancels cross-chain trade offer",
description = "Specify address of cross-chain AT that needs to be cancelled.
"
+ "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
"
- + "You need to sign output with same account as the AT creator otherwise the MESSAGE transaction will be invalid.",
+ + "You need to sign output with trade's private key otherwise the MESSAGE transaction will be invalid.",
requestBody = @RequestBody(
required = true,
content = @Content(
@@ -372,28 +399,31 @@ public class CrossChainResource {
public String cancelTradeOffer(CrossChainCancelRequest cancelRequest) {
Security.checkApiCallAllowed(request);
- byte[] creatorPublicKey = cancelRequest.creatorPublicKey;
+ byte[] tradePublicKey = cancelRequest.tradePublicKey;
- if (creatorPublicKey == null || creatorPublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
+ if (tradePublicKey == null || tradePublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
if (cancelRequest.atAddress == null || !Crypto.isValidAtAddress(cancelRequest.atAddress))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, creatorPublicKey, cancelRequest.atAddress);
+ ATData atData = fetchAtDataWithChecking(repository, cancelRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
+ // Does supplied public key match trade public key?
+ if (tradePublicKey != null && !Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress))
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
+
// Good to make MESSAGE
- PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey);
- String creatorAddress = creatorAccount.getAddress();
- byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(creatorAddress), 32, 0);
+ String atCreatorAddress = crossChainTradeData.qortalCreator;
+ byte[] messageData = BTCACCT.buildRefundMessage(atCreatorAddress);
- byte[] messageTransactionBytes = buildAtMessage(repository, creatorPublicKey, cancelRequest.atAddress, recipientAddressBytes);
+ byte[] messageTransactionBytes = buildAtMessage(repository, tradePublicKey, cancelRequest.atAddress, messageData);
return Base58.encode(messageTransactionBytes);
} catch (DataException e) {
@@ -468,7 +498,7 @@ public class CrossChainResource {
// Extract data from cross-chain trading AT
try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, null, templateRequest.atAddress); // null to skip creator check
+ ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
if (crossChainTradeData.mode == Mode.OFFER)
@@ -551,7 +581,7 @@ public class CrossChainResource {
// Extract data from cross-chain trading AT
try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, null, templateRequest.atAddress); // null to skip creator check
+ ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
if (crossChainTradeData.mode == Mode.OFFER)
@@ -684,7 +714,7 @@ public class CrossChainResource {
// Extract data from cross-chain trading AT
try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, null, refundRequest.atAddress); // null to skip creator check
+ ATData atData = fetchAtDataWithChecking(repository, refundRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
if (crossChainTradeData.mode == Mode.OFFER)
@@ -820,7 +850,7 @@ public class CrossChainResource {
// Extract data from cross-chain trading AT
try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, null, redeemRequest.atAddress); // null to skip creator check
+ ATData atData = fetchAtDataWithChecking(repository, redeemRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
if (crossChainTradeData.mode == Mode.OFFER)
@@ -920,7 +950,7 @@ public class CrossChainResource {
public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) {
Security.checkApiCallAllowed(request);
- if (tradeBotCreateRequest.tradeTimeout < 600)
+ if (tradeBotCreateRequest.tradeTimeout < 60)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
try (final Repository repository = RepositoryManager.getRepository()) {
@@ -978,7 +1008,7 @@ public class CrossChainResource {
// Extract data from cross-chain trading AT
try (final Repository repository = RepositoryManager.getRepository()) {
- ATData atData = fetchAtDataWithChecking(repository, null, atAddress); // null to skip creator check
+ ATData atData = fetchAtDataWithChecking(repository, atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
if (crossChainTradeData.mode != Mode.OFFER)
@@ -1049,15 +1079,11 @@ public class CrossChainResource {
}
}
- private ATData fetchAtDataWithChecking(Repository repository, byte[] creatorPublicKey, String atAddress) throws DataException {
+ private ATData fetchAtDataWithChecking(Repository repository, String atAddress) throws DataException {
ATData atData = repository.getATRepository().fromATAddress(atAddress);
if (atData == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
- // Does supplied public key match that of AT?
- if (creatorPublicKey != null && !Arrays.equals(creatorPublicKey, atData.getCreatorPublicKey()))
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
-
// Must be correct AT - check functionality using code hash
if (!Arrays.equals(atData.getCodeHash(), BTCACCT.CODE_BYTES_HASH))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
@@ -1082,15 +1108,14 @@ public class CrossChainResource {
int nonce = 0;
long amount = 0L;
Long assetId = null; // no assetId as amount is zero
- Long fee = null;
+ Long fee = 0L;
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, senderPublicKey, fee, null);
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, atAddress, amount, assetId, messageData, false, false);
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
- fee = messageTransaction.calcRecommendedFee();
- messageTransactionData.setFee(fee);
+ messageTransaction.computeNonce();
ValidationResult result = messageTransaction.isValidUnconfirmed();
if (result != ValidationResult.OK)
diff --git a/src/main/java/org/qortal/controller/TradeBot.java b/src/main/java/org/qortal/controller/TradeBot.java
index 0402fe0a..4e32316c 100644
--- a/src/main/java/org/qortal/controller/TradeBot.java
+++ b/src/main/java/org/qortal/controller/TradeBot.java
@@ -435,7 +435,7 @@ public class TradeBot {
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
// Refund P2SH-A if AT finished (i.e. Bob cancelled trade) or we've passed lockTime-A
- if (atData.getIsFinished() || NTP.getTime() >= tradeBotData.getLockTimeA()) {
+ if (atData.getIsFinished() || NTP.getTime() >= tradeBotData.getLockTimeA() * 1000L) {
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
@@ -595,7 +595,7 @@ public class TradeBot {
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
// Refund P2SH-B if we've passed lockTime-B
- if (NTP.getTime() >= crossChainTradeData.lockTimeB) {
+ if (NTP.getTime() >= crossChainTradeData.lockTimeB * 1000L) {
tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_B);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
@@ -711,7 +711,7 @@ public class TradeBot {
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
// We can't refund P2SH-B until lockTime-B has passed
- if (NTP.getTime() <= crossChainTradeData.lockTimeB)
+ if (NTP.getTime() <= crossChainTradeData.lockTimeB * 1000L)
return;
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB);
@@ -721,7 +721,7 @@ public class TradeBot {
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
- Transaction p2shRefundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, tradeBotData.getLockTimeA());
+ Transaction p2shRefundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, crossChainTradeData.lockTimeB);
if (!BTC.getInstance().broadcastTransaction(p2shRefundTransaction)) {
// We couldn't refund P2SH-B at this time
LOGGER.debug(() -> String.format("Couldn't broadcast P2SH-B refund transaction?"));
@@ -745,7 +745,12 @@ public class TradeBot {
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
// We can't refund P2SH-A until lockTime-A has passed
- if (NTP.getTime() <= tradeBotData.getLockTimeA())
+ if (NTP.getTime() <= tradeBotData.getLockTimeA() * 1000L)
+ return;
+
+ // We can't refund P2SH-A until we've passed median block time
+ Integer medianBlockTime = BTC.getInstance().getMedianBlockTime();
+ if (medianBlockTime == null || NTP.getTime() <= medianBlockTime * 1000L)
return;
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
@@ -754,6 +759,10 @@ public class TradeBot {
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin);
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
+ if (fundingOutputs == null) {
+ LOGGER.debug(() -> String.format("Couldn't fetch unspent outputs for %s", p2shAddress));
+ return;
+ }
Transaction p2shRefundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, tradeBotData.getLockTimeA());
if (!BTC.getInstance().broadcastTransaction(p2shRefundTransaction)) {
diff --git a/src/main/java/org/qortal/crosschain/BTCP2SH.java b/src/main/java/org/qortal/crosschain/BTCP2SH.java
index 90e77710..0e8a2b0b 100644
--- a/src/main/java/org/qortal/crosschain/BTCP2SH.java
+++ b/src/main/java/org/qortal/crosschain/BTCP2SH.java
@@ -93,9 +93,9 @@ public class BTCP2SH {
// Input (without scriptSig prior to signing)
TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor());
if (lockTime != null)
- input.setSequenceNumber(BTC.LOCKTIME_NO_RBF_SEQUENCE); // Use max-value, so no lockTime and no RBF
+ input.setSequenceNumber(BTC.LOCKTIME_NO_RBF_SEQUENCE); // Use max-value - 1, so lockTime can be used but not RBF
else
- input.setSequenceNumber(BTC.NO_LOCKTIME_NO_RBF_SEQUENCE); // Use max-value - 1, so lockTime can be used but not RBF
+ input.setSequenceNumber(BTC.NO_LOCKTIME_NO_RBF_SEQUENCE); // Use max-value, so no lockTime and no RBF
transaction.addInput(input);
}
diff --git a/src/main/java/org/qortal/crosschain/ElectrumX.java b/src/main/java/org/qortal/crosschain/ElectrumX.java
index 0c5213f5..d3541527 100644
--- a/src/main/java/org/qortal/crosschain/ElectrumX.java
+++ b/src/main/java/org/qortal/crosschain/ElectrumX.java
@@ -128,16 +128,26 @@ public class ElectrumX {
// Methods for use by other classes
public Integer getCurrentHeight() {
- JSONObject blockJson = (JSONObject) this.rpc("blockchain.headers.subscribe");
- if (blockJson == null || !blockJson.containsKey("height"))
+ Object blockObj = this.rpc("blockchain.headers.subscribe");
+ if (!(blockObj instanceof JSONObject))
+ return null;
+
+ JSONObject blockJson = (JSONObject) blockObj;
+
+ if (!blockJson.containsKey("height"))
return null;
return ((Long) blockJson.get("height")).intValue();
}
public List getBlockHeaders(int startHeight, long count) {
- JSONObject blockJson = (JSONObject) this.rpc("blockchain.block.headers", startHeight, count);
- if (blockJson == null || !blockJson.containsKey("count") || !blockJson.containsKey("hex"))
+ Object blockObj = this.rpc("blockchain.block.headers", startHeight, count);
+ if (!(blockObj instanceof JSONObject))
+ return null;
+
+ JSONObject blockJson = (JSONObject) blockObj;
+
+ if (!blockJson.containsKey("count") || !blockJson.containsKey("hex"))
return null;
Long returnedCount = (Long) blockJson.get("count");
@@ -158,8 +168,13 @@ public class ElectrumX {
byte[] scriptHash = Crypto.digest(script);
Bytes.reverse(scriptHash);
- JSONObject balanceJson = (JSONObject) this.rpc("blockchain.scripthash.get_balance", HashCode.fromBytes(scriptHash).toString());
- if (balanceJson == null || !balanceJson.containsKey("confirmed"))
+ Object balanceObj = this.rpc("blockchain.scripthash.get_balance", HashCode.fromBytes(scriptHash).toString());
+ if (!(balanceObj instanceof JSONObject))
+ return null;
+
+ JSONObject balanceJson = (JSONObject) balanceObj;
+
+ if (!balanceJson.containsKey("confirmed"))
return null;
return (Long) balanceJson.get("confirmed");
@@ -183,12 +198,12 @@ public class ElectrumX {
byte[] scriptHash = Crypto.digest(script);
Bytes.reverse(scriptHash);
- JSONArray unspentJson = (JSONArray) this.rpc("blockchain.scripthash.listunspent", HashCode.fromBytes(scriptHash).toString());
- if (unspentJson == null)
+ Object unspentJson = this.rpc("blockchain.scripthash.listunspent", HashCode.fromBytes(scriptHash).toString());
+ if (!(unspentJson instanceof JSONArray))
return null;
List unspentOutputs = new ArrayList<>();
- for (Object rawUnspent : unspentJson) {
+ for (Object rawUnspent : (JSONArray) unspentJson) {
JSONObject unspent = (JSONObject) rawUnspent;
byte[] txHash = HashCode.fromString((String) unspent.get("tx_hash")).asBytes();
@@ -203,11 +218,11 @@ public class ElectrumX {
}
public byte[] getRawTransaction(byte[] txHash) {
- String rawTransactionHex = (String) this.rpc("blockchain.transaction.get", HashCode.fromBytes(txHash).toString());
- if (rawTransactionHex == null)
+ Object rawTransactionHex = this.rpc("blockchain.transaction.get", HashCode.fromBytes(txHash).toString());
+ if (!(rawTransactionHex instanceof String))
return null;
- return HashCode.fromString(rawTransactionHex).asBytes();
+ return HashCode.fromString((String) rawTransactionHex).asBytes();
}
/** Returns list of raw transactions. */
@@ -215,13 +230,13 @@ public class ElectrumX {
byte[] scriptHash = Crypto.digest(script);
Bytes.reverse(scriptHash);
- JSONArray transactionsJson = (JSONArray) this.rpc("blockchain.scripthash.get_history", HashCode.fromBytes(scriptHash).toString());
- if (transactionsJson == null)
+ Object transactionsJson = this.rpc("blockchain.scripthash.get_history", HashCode.fromBytes(scriptHash).toString());
+ if (!(transactionsJson instanceof JSONArray))
return null;
List rawTransactions = new ArrayList<>();
- for (Object rawTransactionInfo : transactionsJson) {
+ for (Object rawTransactionInfo : (JSONArray) transactionsJson) {
JSONObject transactionInfo = (JSONObject) rawTransactionInfo;
// We only want confirmed transactions
@@ -254,11 +269,11 @@ public class ElectrumX {
private Set serverPeersSubscribe() {
Set newServers = new HashSet<>();
- JSONArray peers = (JSONArray) this.connectedRpc("server.peers.subscribe");
- if (peers == null)
+ Object peers = this.connectedRpc("server.peers.subscribe");
+ if (!(peers instanceof JSONArray))
return newServers;
- for (Object rawPeer : peers) {
+ for (Object rawPeer : (JSONArray) peers) {
JSONArray peer = (JSONArray) rawPeer;
if (peer.size() < 3)
continue;
@@ -393,10 +408,12 @@ public class ElectrumX {
if (response.isEmpty())
return null;
- JSONObject responseJson = (JSONObject) JSONValue.parse(response);
- if (responseJson == null)
+ Object responseObj = JSONValue.parse(response);
+ if (!(responseObj instanceof JSONObject))
return null;
+ JSONObject responseJson = (JSONObject) responseObj;
+
return responseJson.get("result");
}