diff --git a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java
index dfc5dfd8..e8d38703 100644
--- a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java
+++ b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java
@@ -33,9 +33,6 @@ public class CrossChainBuildRequest {
@Schema(description = "Trade time window (minutes) from trade agreement to automatic refund", example = "10080")
public Integer tradeTimeout;
- @Schema(description = "Bitcoin address for receiving", example = "1NCTG9oLk41bU6pcehLNo9DVJup77EHAVx")
- public String receiveAddress;
-
public CrossChainBuildRequest() {
}
diff --git a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java
index 730421a4..f87471d0 100644
--- a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java
+++ b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java
@@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
@XmlAccessorType(XmlAccessType.FIELD)
public class CrossChainCancelRequest {
- @Schema(description = "AT's trade public key", example = "K6wuddsBV3HzRrXFFezE7P5MoRXp5m3mEDokRDGZB6ry")
+ @Schema(description = "AT creator's 'trade' public key", example = "K6wuddsBV3HzRrXFFezE7P5MoRXp5m3mEDokRDGZB6ry")
public byte[] tradePublicKey;
@Schema(description = "Qortal trade AT address")
diff --git a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java
index 52b9f125..7ad825d4 100644
--- a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java
+++ b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java
@@ -8,8 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
@XmlAccessorType(XmlAccessType.FIELD)
public class CrossChainSecretRequest {
- @Schema(description = "Public key to match AT's 'recipient'", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
- public byte[] recipientPublicKey;
+ @Schema(description = "Public key to match AT's trade 'partner'", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
+ public byte[] partnerPublicKey;
@Schema(description = "Qortal AT address")
public String atAddress;
@@ -20,6 +20,9 @@ public class CrossChainSecretRequest {
@Schema(description = "secret-B (32 bytes)", example = "EN2Bgx3BcEMtxFCewmCVSMkfZjVKYhx3KEXC5A21KBGx")
public byte[] secretB;
+ @Schema(description = "Qortal address for receiving QORT from AT")
+ public String receivingAddress;
+
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 3a632bf3..1afd7290 100644
--- a/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java
+++ b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java
@@ -8,13 +8,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
@XmlAccessorType(XmlAccessType.FIELD)
public class CrossChainTradeRequest {
- @Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
+ @Schema(description = "AT creator's 'trade' public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
public byte[] tradePublicKey;
@Schema(description = "Qortal AT address")
public String atAddress;
- @Schema(description = "Signature of trading partner's MESSAGE transaction")
+ @Schema(description = "Signature of trading partner's 'offer' MESSAGE transaction")
public byte[] messageTransactionSignature;
public CrossChainTradeRequest() {
diff --git a/src/main/java/org/qortal/api/model/TradeBotRespondRequest.java b/src/main/java/org/qortal/api/model/TradeBotRespondRequest.java
index 2c319fd9..4e947a9b 100644
--- a/src/main/java/org/qortal/api/model/TradeBotRespondRequest.java
+++ b/src/main/java/org/qortal/api/model/TradeBotRespondRequest.java
@@ -14,6 +14,9 @@ public class TradeBotRespondRequest {
@Schema(description = "Bitcoin BIP32 extended private key", example = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TbTVGajEB55L1HYLg2aQMecZLXLre5YJcawpdFG66STVAWPJ")
public String xprv58;
+ @Schema(description = "Qortal address for receiving QORT from AT")
+ public String receivingAddress;
+
public TradeBotRespondRequest() {
}
diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java
index 73e0e050..dd77ed3b 100644
--- a/src/main/java/org/qortal/api/resource/CrossChainResource.java
+++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java
@@ -59,7 +59,6 @@ import org.qortal.crypto.Crypto;
import org.qortal.data.at.ATData;
import org.qortal.data.crosschain.CrossChainTradeData;
import org.qortal.data.crosschain.TradeBotData;
-import org.qortal.data.crosschain.CrossChainTradeData.Mode;
import org.qortal.data.transaction.BaseTransactionData;
import org.qortal.data.transaction.DeployAtTransactionData;
import org.qortal.data.transaction.MessageTransactionData;
@@ -182,23 +181,11 @@ public class CrossChainResource {
if (tradeRequest.bitcoinAmount <= 0)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
- Address bitcoinReceiveAddress;
- try {
- bitcoinReceiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeRequest.receiveAddress);
- } catch (AddressFormatException e) {
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
- }
- byte[] bitcoinReceivePublicKeyHash = bitcoinReceiveAddress.getHash();
-
- // We only support P2PKH addresses at this time
- if (bitcoinReceiveAddress.getOutputScriptType() != ScriptType.P2PKH)
- throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
-
try (final Repository repository = RepositoryManager.getRepository()) {
PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey);
byte[] creationBytes = BTCACCT.buildQortalAT(creatorAccount.getAddress(), tradeRequest.bitcoinPublicKeyHash, tradeRequest.hashOfSecretB,
- tradeRequest.qortAmount, tradeRequest.bitcoinAmount, tradeRequest.tradeTimeout, bitcoinReceivePublicKeyHash);
+ tradeRequest.qortAmount, tradeRequest.bitcoinAmount, tradeRequest.tradeTimeout);
long txTimestamp = NTP.getTime();
byte[] lastReference = creatorAccount.getLastReference();
@@ -233,11 +220,11 @@ public class CrossChainResource {
}
@POST
- @Path("/tradeoffer/recipient")
+ @Path("/tradeoffer/trademessage")
@Operation(
- 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!
"
+ summary = "Builds raw, unsigned 'trade' 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 signature of 'offer' MESSAGE from trade partner.
"
+ + "AT needs to be in 'offer' mode. Messages sent to an AT in any other mode will be ignored, but still cost fees to send!
"
+ "You need to sign output with trade private key otherwise the MESSAGE transaction will be invalid.",
requestBody = @RequestBody(
required = true,
@@ -259,7 +246,7 @@ public class CrossChainResource {
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
- public String sendTradeRecipient(CrossChainTradeRequest tradeRequest) {
+ public String buildTradeMessage(CrossChainTradeRequest tradeRequest) {
Security.checkApiCallAllowed(request);
byte[] tradePublicKey = tradeRequest.tradePublicKey;
@@ -277,11 +264,11 @@ public class CrossChainResource {
ATData atData = fetchAtDataWithChecking(repository, tradeRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
- if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE)
+ if (crossChainTradeData.mode != BTCACCT.Mode.OFFERING)
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))
+ if (!Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
TransactionData transactionData = repository.getTransactionRepository().fromSignature(tradeRequest.messageTransactionSignature);
@@ -299,7 +286,7 @@ public class CrossChainResource {
// Good to make MESSAGE
- byte[] aliceForeignPublicKeyHash = offerMessageData.recipientBitcoinPKH;
+ byte[] aliceForeignPublicKeyHash = offerMessageData.partnerBitcoinPKH;
byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
int lockTimeA = (int) offerMessageData.lockTimeA;
@@ -316,12 +303,12 @@ public class CrossChainResource {
}
@POST
- @Path("/tradeoffer/secret")
+ @Path("/tradeoffer/redeemmessage")
@Operation(
- summary = "Builds raw, unsigned MESSAGE transaction that sends secrets to AT, releasing funds to recipient",
- description = "Specify address of cross-chain AT that needs to be messaged, and both 32-byte secrets.
"
- + "AT needs to be in 'trade' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
"
- + "You need to sign output with account the AT considers the 'recipient' otherwise the MESSAGE transaction will be invalid.",
+ summary = "Builds raw, unsigned 'redeem' MESSAGE transaction that sends secrets to AT, releasing funds to partner",
+ description = "Specify address of cross-chain AT that needs to be messaged, both 32-byte secrets and an address for receiving QORT from AT.
"
+ + "AT needs to be in 'trade' mode. Messages sent to an AT in any other mode will be ignored, but still cost fees to send!
"
+ + "You need to sign output with account the AT considers the trade 'partner' otherwise the MESSAGE transaction will be invalid.",
requestBody = @RequestBody(
required = true,
content = @Content(
@@ -342,12 +329,12 @@ public class CrossChainResource {
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_DATA, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
- public String sendSecret(CrossChainSecretRequest secretRequest) {
+ public String buildRedeemMessage(CrossChainSecretRequest secretRequest) {
Security.checkApiCallAllowed(request);
- byte[] recipientPublicKey = secretRequest.recipientPublicKey;
+ byte[] partnerPublicKey = secretRequest.partnerPublicKey;
- if (recipientPublicKey == null || recipientPublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
+ if (partnerPublicKey == null || partnerPublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
if (secretRequest.atAddress == null || !Crypto.isValidAtAddress(secretRequest.atAddress))
@@ -359,24 +346,26 @@ public class CrossChainResource {
if (secretRequest.secretB == null || secretRequest.secretB.length != BTCACCT.SECRET_LENGTH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
+ if (secretRequest.receivingAddress == null || !Crypto.isValidAddress(secretRequest.receivingAddress))
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
+
try (final Repository repository = RepositoryManager.getRepository()) {
ATData atData = fetchAtDataWithChecking(repository, secretRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
- if (crossChainTradeData.mode == CrossChainTradeData.Mode.OFFER)
+ if (crossChainTradeData.mode != BTCACCT.Mode.TRADING)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
- PublicKeyAccount recipientAccount = new PublicKeyAccount(repository, recipientPublicKey);
- String recipientAddress = recipientAccount.getAddress();
+ String partnerAddress = Crypto.toAddress(partnerPublicKey);
- // MESSAGE must come from address that AT considers trade partner / 'recipient'
- if (!crossChainTradeData.qortalRecipient.equals(recipientAddress))
+ // MESSAGE must come from address that AT considers trade partner
+ if (!crossChainTradeData.qortalPartnerAddress.equals(partnerAddress))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
// Good to make MESSAGE
- byte[] messageData = BTCACCT.buildRedeemMessage(secretRequest.secretA, secretRequest.secretB);
- byte[] messageTransactionBytes = buildAtMessage(repository, recipientPublicKey, secretRequest.atAddress, messageData);
+ byte[] messageData = BTCACCT.buildRedeemMessage(secretRequest.secretA, secretRequest.secretB, secretRequest.receivingAddress);
+ byte[] messageTransactionBytes = buildAtMessage(repository, partnerPublicKey, secretRequest.atAddress, messageData);
return Base58.encode(messageTransactionBytes);
} catch (DataException e) {
@@ -387,7 +376,7 @@ public class CrossChainResource {
@DELETE
@Path("/tradeoffer")
@Operation(
- summary = "Builds raw, unsigned MESSAGE transaction that cancels cross-chain trade offer",
+ summary = "Builds raw, unsigned 'cancel' 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 trade's private key otherwise the MESSAGE transaction will be invalid.",
@@ -411,7 +400,7 @@ public class CrossChainResource {
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
- public String cancelTradeOffer(CrossChainCancelRequest cancelRequest) {
+ public String buildCancelMessage(CrossChainCancelRequest cancelRequest) {
Security.checkApiCallAllowed(request);
byte[] tradePublicKey = cancelRequest.tradePublicKey;
@@ -426,17 +415,17 @@ public class CrossChainResource {
ATData atData = fetchAtDataWithChecking(repository, cancelRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
- if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE)
+ if (crossChainTradeData.mode != BTCACCT.Mode.OFFERING)
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))
+ if (!Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
// Good to make MESSAGE
String atCreatorAddress = crossChainTradeData.qortalCreator;
- byte[] messageData = BTCACCT.buildRefundMessage(atCreatorAddress);
+ byte[] messageData = BTCACCT.buildCancelMessage(atCreatorAddress);
byte[] messageTransactionBytes = buildAtMessage(repository, tradePublicKey, cancelRequest.atAddress, messageData);
@@ -516,7 +505,7 @@ public class CrossChainResource {
ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
- if (crossChainTradeData.mode == Mode.OFFER)
+ if (crossChainTradeData.mode == BTCACCT.Mode.OFFERING || crossChainTradeData.mode == BTCACCT.Mode.CANCELLED)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
byte[] redeemScriptBytes = BTCP2SH.buildScript(templateRequest.refundPublicKeyHash, lockTimeFn.applyAsInt(crossChainTradeData), templateRequest.redeemPublicKeyHash, hashOfSecretFn.apply(crossChainTradeData));
@@ -599,7 +588,7 @@ public class CrossChainResource {
ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
- if (crossChainTradeData.mode == Mode.OFFER)
+ if (crossChainTradeData.mode == BTCACCT.Mode.OFFERING || crossChainTradeData.mode == BTCACCT.Mode.CANCELLED)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
@@ -732,7 +721,7 @@ public class CrossChainResource {
ATData atData = fetchAtDataWithChecking(repository, refundRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
- if (crossChainTradeData.mode == Mode.OFFER)
+ if (crossChainTradeData.mode == BTCACCT.Mode.OFFERING || crossChainTradeData.mode == BTCACCT.Mode.CANCELLED)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
@@ -874,7 +863,7 @@ public class CrossChainResource {
ATData atData = fetchAtDataWithChecking(repository, redeemRequest.atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
- if (crossChainTradeData.mode == Mode.OFFER)
+ if (crossChainTradeData.mode == BTCACCT.Mode.OFFERING || crossChainTradeData.mode == BTCACCT.Mode.CANCELLED)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
@@ -1031,15 +1020,18 @@ public class CrossChainResource {
if (!BTC.getInstance().isValidXprv(tradeBotRespondRequest.xprv58))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
+ if (tradeBotRespondRequest.receivingAddress == null || !Crypto.isValidAddress(tradeBotRespondRequest.receivingAddress))
+ throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
+
// Extract data from cross-chain trading AT
try (final Repository repository = RepositoryManager.getRepository()) {
ATData atData = fetchAtDataWithChecking(repository, atAddress);
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
- if (crossChainTradeData.mode != Mode.OFFER)
+ if (crossChainTradeData.mode != BTCACCT.Mode.OFFERING)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
- boolean result = TradeBot.startResponse(repository, crossChainTradeData, tradeBotRespondRequest.xprv58);
+ boolean result = TradeBot.startResponse(repository, crossChainTradeData, tradeBotRespondRequest.xprv58, tradeBotRespondRequest.receivingAddress);
return result ? "true" : "false";
} catch (DataException e) {
diff --git a/src/main/java/org/qortal/controller/TradeBot.java b/src/main/java/org/qortal/controller/TradeBot.java
index 5db223ac..8c085256 100644
--- a/src/main/java/org/qortal/controller/TradeBot.java
+++ b/src/main/java/org/qortal/controller/TradeBot.java
@@ -40,6 +40,7 @@ import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.transform.TransformationException;
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
import org.qortal.utils.Amounts;
+import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
public class TradeBot {
@@ -133,7 +134,7 @@ public class TradeBot {
String aTType = "ACCT";
String tags = "ACCT QORT BTC";
byte[] creationBytes = BTCACCT.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, hashOfSecretB, tradeBotCreateRequest.qortAmount,
- tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout, bitcoinReceivePublicKeyHash);
+ tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout);
long amount = tradeBotCreateRequest.fundingQortAmount;
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT);
@@ -150,7 +151,7 @@ public class TradeBot {
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
secretB, hashOfSecretB,
tradeForeignPublicKey, tradeForeignPublicKeyHash,
- tradeBotCreateRequest.bitcoinAmount, null, null, null);
+ tradeBotCreateRequest.bitcoinAmount, null, null, null, bitcoinReceivePublicKeyHash);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
@@ -188,7 +189,7 @@ public class TradeBot {
*
* It is envisaged that the value in xprv58 will actually come from a Qortal-UI-managed wallet. *
- * If sufficient funds are available, this method will actually fund the P2SH-A + * If sufficient funds are available, this method will actually fund the P2SH-A * with the Bitcoin amount expected by 'Bob'. *
* If the Bitcoin transaction is successfully broadcast to the network then the trade-bot entry
@@ -202,7 +203,7 @@ public class TradeBot {
* @return true if P2SH-A funding transaction successfully broadcast to Bitcoin network, false otherwise
* @throws DataException
*/
- public static boolean startResponse(Repository repository, CrossChainTradeData crossChainTradeData, String xprv58) throws DataException {
+ public static boolean startResponse(Repository repository, CrossChainTradeData crossChainTradeData, String xprv58, String receivingAddress) throws DataException {
byte[] tradePrivateKey = generateTradePrivateKey();
byte[] secretA = generateSecret();
byte[] hashOfSecretA = Crypto.hash160(secretA);
@@ -213,6 +214,7 @@ public class TradeBot {
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
+ byte[] receivingPublicKeyHash = Base58.decode(receivingAddress); // Actually the whole address, not just PKH
// We need to generate lockTime-A: halfway of refundTimeout from now
int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L);
@@ -222,7 +224,7 @@ public class TradeBot {
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
secretA, hashOfSecretA,
tradeForeignPublicKey, tradeForeignPublicKeyHash,
- crossChainTradeData.expectedBitcoin, xprv58, null, lockTimeA);
+ crossChainTradeData.expectedBitcoin, xprv58, null, lockTimeA, receivingPublicKeyHash);
// Check we have enough funds via xprv58 to fund both P2SHs to cover expectedBitcoin
String tradeForeignAddress = BTC.getInstance().pkhToAddress(tradeForeignPublicKeyHash);
@@ -499,7 +501,7 @@ public class TradeBot {
if (offerMessageData == null)
continue;
- byte[] aliceForeignPublicKeyHash = offerMessageData.recipientBitcoinPKH;
+ byte[] aliceForeignPublicKeyHash = offerMessageData.partnerBitcoinPKH;
byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
int lockTimeA = (int) offerMessageData.lockTimeA;
// Determine P2SH-A address and confirm funded
@@ -592,11 +594,11 @@ public class TradeBot {
}
// We're waiting for AT to be in TRADE mode
- if (crossChainTradeData.mode != CrossChainTradeData.Mode.TRADE)
+ if (crossChainTradeData.mode != BTCACCT.Mode.TRADING)
return;
// We're expecting AT to be locked to our native trade address
- if (!crossChainTradeData.qortalRecipient.equals(tradeBotData.getTradeNativeAddress())) {
+ if (!crossChainTradeData.qortalPartnerAddress.equals(tradeBotData.getTradeNativeAddress())) {
// AT locked to different address! We shouldn't continue but wait and refund.
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
@@ -604,7 +606,7 @@ public class TradeBot {
LOGGER.warn(() -> String.format("AT %s locked to %s, not us (%s). Refunding %s - aborting trade",
tradeBotData.getAtAddress(),
- crossChainTradeData.qortalRecipient,
+ crossChainTradeData.qortalPartnerAddress,
tradeBotData.getTradeNativeAddress(),
p2shAddress));
@@ -701,7 +703,7 @@ public class TradeBot {
// AT yet to process MESSAGE
return;
- byte[] redeemScriptBytes = BTCP2SH.buildScript(crossChainTradeData.recipientBitcoinPKH, crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB);
+ byte[] redeemScriptBytes = BTCP2SH.buildScript(crossChainTradeData.partnerBitcoinPKH, crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB);
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
Long balance = BTC.getInstance().getBalance(p2shAddress);
@@ -716,7 +718,7 @@ public class TradeBot {
Coin redeemAmount = Coin.ZERO; // The real funds are in P2SH-A
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
List
- * tradeTimeout (minutes) is the time window for the recipient to send the
+ * tradeTimeout (minutes) is the time window for the trade partner to send the
* 32-byte secret to the AT, before the AT automatically refunds the AT's creator.
*
* @param creatorTradeAddress AT creator's trade Qortal address, also used for refunds
@@ -117,7 +154,7 @@ public class BTCACCT {
* @param tradeTimeout suggested timeout for entire trade
* @return
*/
- public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, long qortAmount, long bitcoinAmount, int tradeTimeout, byte[] bitcoinReceivePublicKeyHash) {
+ public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, long qortAmount, long bitcoinAmount, int tradeTimeout) {
// Labels for data segment addresses
int addrCounter = 0;
@@ -138,28 +175,26 @@ public class BTCACCT {
final int addrBitcoinAmount = addrCounter++;
final int addrTradeTimeout = addrCounter++;
- final int addrMessageTxType = addrCounter++;
- final int addrExpectedOfferMessageLength = addrCounter++;
+ final int addrMessageTxnType = addrCounter++;
final int addrExpectedTradeMessageLength = addrCounter++;
+ final int addrExpectedRedeemMessageLength = addrCounter++;
final int addrCreatorAddressPointer = addrCounter++;
final int addrHashOfSecretBPointer = addrCounter++;
- final int addrQortalRecipientPointer = addrCounter++;
+ final int addrQortalPartnerAddressPointer = addrCounter++;
final int addrMessageSenderPointer = addrCounter++;
- final int addrOfferMessageRecipientBitcoinPKHOffset = addrCounter++;
- final int addrRecipientBitcoinPKHPointer = addrCounter++;
- final int addrOfferMessageHashOfSecretAOffset = addrCounter++;
+ final int addrTradeMessagePartnerBitcoinPKHOffset = addrCounter++;
+ final int addrPartnerBitcoinPKHPointer = addrCounter++;
+ final int addrTradeMessageHashOfSecretAOffset = addrCounter++;
final int addrHashOfSecretAPointer = addrCounter++;
- final int addrTradeMessageSecretBOffset = addrCounter++;
+ final int addrRedeemMessageSecretBOffset = addrCounter++;
+ final int addrRedeemMessageReceiveAddressOffset = addrCounter++;
final int addrMessageDataPointer = addrCounter++;
final int addrMessageDataLength = addrCounter++;
- final int addrBitcoinReceivePublicKeyHash = addrCounter;
- addrCounter += 4;
-
final int addrEndOfConstants = addrCounter;
// Variables
@@ -169,18 +204,18 @@ public class BTCACCT {
final int addrCreatorAddress3 = addrCounter++;
final int addrCreatorAddress4 = addrCounter++;
- final int addrQortalRecipient1 = addrCounter++;
- final int addrQortalRecipient2 = addrCounter++;
- final int addrQortalRecipient3 = addrCounter++;
- final int addrQortalRecipient4 = addrCounter++;
+ final int addrQortalPartnerAddress1 = addrCounter++;
+ final int addrQortalPartnerAddress2 = addrCounter++;
+ final int addrQortalPartnerAddress3 = addrCounter++;
+ final int addrQortalPartnerAddress4 = addrCounter++;
final int addrLockTimeA = addrCounter++;
final int addrLockTimeB = addrCounter++;
final int addrRefundTimeout = addrCounter++;
final int addrRefundTimestamp = addrCounter++;
- final int addrLastTxTimestamp = addrCounter++;
+ final int addrLastTxnTimestamp = addrCounter++;
final int addrBlockTimestamp = addrCounter++;
- final int addrTxType = addrCounter++;
+ final int addrTxnType = addrCounter++;
final int addrResult = addrCounter++;
final int addrMessageSender1 = addrCounter++;
@@ -196,13 +231,11 @@ public class BTCACCT {
final int addrHashOfSecretA = addrCounter;
addrCounter += 4;
- final int addrRecipientBitcoinPKH = addrCounter;
+ final int addrPartnerBitcoinPKH = addrCounter;
addrCounter += 4;
final int addrMode = addrCounter++;
- final int addrRedeemFlag = addrCounter++;
-
// Data segment
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
@@ -232,16 +265,16 @@ public class BTCACCT {
dataByteBuffer.putLong(tradeTimeout);
// We're only interested in MESSAGE transactions
- assert dataByteBuffer.position() == addrMessageTxType * MachineState.VALUE_SIZE : "addrMessageTxType incorrect";
+ assert dataByteBuffer.position() == addrMessageTxnType * MachineState.VALUE_SIZE : "addrMessageTxnType incorrect";
dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value);
- // Expected length of OFFER MESSAGE data from AT creator
- assert dataByteBuffer.position() == addrExpectedOfferMessageLength * MachineState.VALUE_SIZE : "addrExpectedOfferMessageLength incorrect";
- dataByteBuffer.putLong(32L + 32L + 32L);
-
- // Expected length of TRADE MESSAGE data from trade partner / "recipient"
+ // Expected length of 'trade' MESSAGE data from AT creator
assert dataByteBuffer.position() == addrExpectedTradeMessageLength * MachineState.VALUE_SIZE : "addrExpectedTradeMessageLength incorrect";
- dataByteBuffer.putLong(32L + 32L);
+ dataByteBuffer.putLong(TRADE_MESSAGE_LENGTH);
+
+ // Expected length of 'redeem' MESSAGE data from trade partner
+ assert dataByteBuffer.position() == addrExpectedRedeemMessageLength * MachineState.VALUE_SIZE : "addrExpectedRedeemMessageLength incorrect";
+ dataByteBuffer.putLong(REDEEM_MESSAGE_LENGTH);
// Index into data segment of AT creator's address, used by GET_B_IND
assert dataByteBuffer.position() == addrCreatorAddressPointer * MachineState.VALUE_SIZE : "addrCreatorAddressPointer incorrect";
@@ -252,56 +285,56 @@ public class BTCACCT {
dataByteBuffer.putLong(addrHashOfSecretB);
// Index into data segment of recipient address, used by SET_B_IND
- assert dataByteBuffer.position() == addrQortalRecipientPointer * MachineState.VALUE_SIZE : "addrQortalRecipientPointer incorrect";
- dataByteBuffer.putLong(addrQortalRecipient1);
+ assert dataByteBuffer.position() == addrQortalPartnerAddressPointer * MachineState.VALUE_SIZE : "addrQortalPartnerAddressPointer incorrect";
+ dataByteBuffer.putLong(addrQortalPartnerAddress1);
// Index into data segment of (temporary) transaction's sender's address, used by GET_B_IND
assert dataByteBuffer.position() == addrMessageSenderPointer * MachineState.VALUE_SIZE : "addrMessageSenderPointer incorrect";
dataByteBuffer.putLong(addrMessageSender1);
- // Offset into OFFER MESSAGE data payload for extracting recipient's Bitcoin PKH
- assert dataByteBuffer.position() == addrOfferMessageRecipientBitcoinPKHOffset * MachineState.VALUE_SIZE : "addrOfferMessageRecipientBitcoinPKHOffset incorrect";
+ // Offset into 'trade' MESSAGE data payload for extracting partner's Bitcoin PKH
+ assert dataByteBuffer.position() == addrTradeMessagePartnerBitcoinPKHOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerBitcoinPKHOffset incorrect";
dataByteBuffer.putLong(32L);
- // Index into data segment of recipient's Bitcoin PKH, used by GET_B_IND
- assert dataByteBuffer.position() == addrRecipientBitcoinPKHPointer * MachineState.VALUE_SIZE : "addrRecipientBitcoinPKHPointer incorrect";
- dataByteBuffer.putLong(addrRecipientBitcoinPKH);
+ // Index into data segment of partner's Bitcoin PKH, used by GET_B_IND
+ assert dataByteBuffer.position() == addrPartnerBitcoinPKHPointer * MachineState.VALUE_SIZE : "addrPartnerBitcoinPKHPointer incorrect";
+ dataByteBuffer.putLong(addrPartnerBitcoinPKH);
- // Offset into OFFER MESSAGE data payload for extracting hash-of-secret-A
- assert dataByteBuffer.position() == addrOfferMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrOfferMessageHashOfSecretAOffset incorrect";
+ // Offset into 'trade' MESSAGE data payload for extracting hash-of-secret-A
+ assert dataByteBuffer.position() == addrTradeMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrTradeMessageHashOfSecretAOffset incorrect";
dataByteBuffer.putLong(64L);
- // Index into data segment of hash of secret A, used by GET_B_IND
+ // Index into data segment to hash of secret A, used by GET_B_IND
assert dataByteBuffer.position() == addrHashOfSecretAPointer * MachineState.VALUE_SIZE : "addrHashOfSecretAPointer incorrect";
dataByteBuffer.putLong(addrHashOfSecretA);
- // Offset into TRADE MESSAGE data payload for extracting secret-B
- assert dataByteBuffer.position() == addrTradeMessageSecretBOffset * MachineState.VALUE_SIZE : "addrTradeMessageSecretBOffset incorrect";
+ // Offset into 'redeem' MESSAGE data payload for extracting secret-B
+ assert dataByteBuffer.position() == addrRedeemMessageSecretBOffset * MachineState.VALUE_SIZE : "addrRedeemMessageSecretBOffset incorrect";
dataByteBuffer.putLong(32L);
+ // Offset into 'redeem' MESSAGE data payload for extracting Qortal receive address
+ assert dataByteBuffer.position() == addrRedeemMessageReceiveAddressOffset * MachineState.VALUE_SIZE : "addrRedeemMessageReceiveAddressOffset incorrect";
+ dataByteBuffer.putLong(64L);
+
// Source location and length for hashing any passed secret
assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect";
dataByteBuffer.putLong(addrMessageData);
assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect";
dataByteBuffer.putLong(32L);
- // Bitcoin receive public key hash
- assert dataByteBuffer.position() == addrBitcoinReceivePublicKeyHash * MachineState.VALUE_SIZE : "addrBitcoinReceivePublicKeyHash incorrect";
- dataByteBuffer.put(Bytes.ensureCapacity(bitcoinReceivePublicKeyHash, 32, 0));
-
assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants";
// Code labels
Integer labelRefund = null;
- Integer labelOfferTxLoop = null;
- Integer labelCheckOfferTx = null;
+ Integer labelTradeTxnLoop = null;
+ Integer labelCheckTradeTxn = null;
- Integer labelCheckNonRefundOfferTx = null;
- Integer labelOfferTxExtract = null;
- Integer labelTradeTxLoop = null;
- Integer labelCheckTradeTx = null;
- Integer labelCheckTradeSender = null;
+ Integer labelCheckNonRefundTradeTxn = null;
+ Integer labelTradeTxnExtract = null;
+ Integer labelRedeemTxnLoop = null;
+ Integer labelCheckRedeemTxn = null;
+ Integer labelCheckRedeemTxnSender = null;
Integer labelCheckSecretB = null;
Integer labelPayout = null;
@@ -315,7 +348,7 @@ public class BTCACCT {
/* Initialization */
// Use AT creation 'timestamp' as starting point for finding transactions sent to AT
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxTimestamp));
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxnTimestamp));
// Load B register with AT creator's address so we can save it into addrCreatorAddress1-4
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B));
@@ -327,26 +360,26 @@ public class BTCACCT {
/* Loop, waiting for message from AT creator's trade address containing trade partner details, or AT owner's address to cancel offer */
/* Transaction processing loop */
- labelOfferTxLoop = codeByteBuffer.position();
+ labelTradeTxnLoop = codeByteBuffer.position();
- // Find next transaction to this AT since the last one (if any)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxTimestamp));
- // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0.
+ // Find next transaction (if any) to this AT since the last one (referenced by addrLastTxnTimestamp)
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp));
+ // If no transaction found, A will be zero. If A is zero, set addrResult to 1, otherwise 0.
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult));
// If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction
- codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckOfferTx)));
+ codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckTradeTxn)));
// Stop and wait for next block
codeByteBuffer.put(OpCode.STP_IMD.compile());
/* Check transaction */
- labelCheckOfferTx = codeByteBuffer.position();
+ labelCheckTradeTxn = codeByteBuffer.position();
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxTimestamp));
- // Extract transaction type (message/payment) from transaction and save type in addrTxType
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxType));
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp));
+ // Extract transaction type (message/payment) from transaction and save type in addrTxnType
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType));
// If transaction type is not MESSAGE type then go look for another transaction
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrMessageTxType, calcOffset(codeByteBuffer, labelOfferTxLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
/* Check transaction's sender. We're expecting AT creator's trade address. */
@@ -355,45 +388,50 @@ public class BTCACCT {
// Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer)
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer));
// Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction.
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorTradeAddress1, calcOffset(codeByteBuffer, labelOfferTxLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelOfferTxLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelOfferTxLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelOfferTxLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorTradeAddress1, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
/* Extract trade partner info from message */
// Extract message from transaction into B register
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B));
- // Save B register into data segment starting at addrQortalRecipient1 (as pointed to by addrQortalRecipientPointer)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalRecipientPointer));
- // Compare each of recipient address with creator's address (for offer-cancel scenario). If they don't match, assume recipient is trade partner.
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx)));
- // Recipient address is AT creator's address, so cancel offer and finish.
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund));
+ // Save B register into data segment starting at addrQortalPartnerAddress1 (as pointed to by addrQortalPartnerAddressPointer)
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalPartnerAddressPointer));
+ // Compare each of partner address with creator's address (for offer-cancel scenario). If they don't match, assume address is trade partner.
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn)));
+ // Partner address is AT creator's address, so cancel offer and finish.
+ codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.CANCELLED.value));
+ // We're finished forever (finishing auto-refunds remaining balance to AT creator)
+ codeByteBuffer.put(OpCode.FIN_IMD.compile());
/* Possible switch-to-trade-mode message */
- labelCheckNonRefundOfferTx = codeByteBuffer.position();
+ labelCheckNonRefundTradeTxn = codeByteBuffer.position();
- // Not off-cancel scenario so check we received expected number of message bytes
+ // Not offer-cancel scenario so check we received expected number of message bytes
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength));
- codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedOfferMessageLength, calcOffset(codeByteBuffer, labelOfferTxExtract)));
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelOfferTxLoop == null ? 0 : labelOfferTxLoop));
+ // If message length matches, branch to info extraction code
+ codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedTradeMessageLength, calcOffset(codeByteBuffer, labelTradeTxnExtract)));
+ // Message length didn't match - go back to finding another 'trade' MESSAGE transaction
+ codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxnLoop == null ? 0 : labelTradeTxnLoop));
- labelOfferTxExtract = codeByteBuffer.position();
+ /* Extracting info from 'trade' MESSAGE transaction */
+ labelTradeTxnExtract = codeByteBuffer.position();
// Message is expected length, grab next 32 bytes
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrOfferMessageRecipientBitcoinPKHOffset));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessagePartnerBitcoinPKHOffset));
- // Extract recipient's Bitcoin PKH (we only really use values from B1-B3)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrRecipientBitcoinPKHPointer));
+ // Extract partner's Bitcoin PKH (we only really use values from B1-B3)
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerBitcoinPKHPointer));
// Also extract lockTimeB
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrLockTimeB));
// Grab next 32 bytes
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrOfferMessageHashOfSecretAOffset));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessageHashOfSecretAOffset));
// Extract hash-of-secret-a (we only really use values from B1-B3)
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrHashOfSecretAPointer));
@@ -404,66 +442,67 @@ public class BTCACCT {
codeByteBuffer.put(OpCode.SET_DAT.compile(addrRefundTimeout, addrLockTimeA)); // refundTimeout = lockTimeA
codeByteBuffer.put(OpCode.SUB_DAT.compile(addrRefundTimeout, addrLockTimeB)); // refundTimeout -= lockTimeB
codeByteBuffer.put(OpCode.DIV_VAL.compile(addrRefundTimeout, 2L * 60L)); // refundTimeout /= 2 * 60
- // Calculate trade timeout refund 'timestamp' by adding addrRefundTimeout minutes to this tx 'timestamp', then save into addrRefundTimestamp
- codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTxTimestamp, addrRefundTimeout));
+ // Calculate trade timeout refund 'timestamp' by adding addrRefundTimeout minutes to this transaction's 'timestamp', then save into addrRefundTimestamp
+ codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTxnTimestamp, addrRefundTimeout));
/* We are in 'trade mode' */
- codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, 1));
+ codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.TRADING.value));
// Set restart position to after this opcode
codeByteBuffer.put(OpCode.SET_PCS.compile());
- /* Loop, waiting for trade timeout or message from Qortal trade recipient containing secret-a and secret-b */
+ /* Loop, waiting for trade timeout or 'redeem' MESSAGE from Qortal trade partner */
// Fetch current block 'timestamp'
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp));
// If we're not past refund 'timestamp' then look for next transaction
- codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelTradeTxLoop)));
+ codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
// We're past refund 'timestamp' so go refund everything back to AT creator
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund));
/* Transaction processing loop */
- labelTradeTxLoop = codeByteBuffer.position();
+ labelRedeemTxnLoop = codeByteBuffer.position();
// Find next transaction to this AT since the last one (if any)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxTimestamp));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp));
// If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0.
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult));
// If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction
- codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckTradeTx)));
+ codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckRedeemTxn)));
// Stop and wait for next block
codeByteBuffer.put(OpCode.STP_IMD.compile());
/* Check transaction */
- labelCheckTradeTx = codeByteBuffer.position();
+ labelCheckRedeemTxn = codeByteBuffer.position();
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxTimestamp));
- // Extract transaction type (message/payment) from transaction and save type in addrTxType
- codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxType));
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp));
+ // Extract transaction type (message/payment) from transaction and save type in addrTxnType
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType));
// If transaction type is not MESSAGE type then go look for another transaction
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrMessageTxType, calcOffset(codeByteBuffer, labelTradeTxLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
/* Check message payload length */
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength));
- codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedTradeMessageLength, calcOffset(codeByteBuffer, labelCheckTradeSender)));
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelOfferTxLoop == null ? 0 : labelOfferTxLoop));
+ // If message length matches, branch to sender checking code
+ codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedRedeemMessageLength, calcOffset(codeByteBuffer, labelCheckRedeemTxnSender)));
+ // Message length didn't match - go back to finding another 'redeem' MESSAGE transaction
+ codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop));
/* Check transaction's sender */
-
- labelCheckTradeSender = codeByteBuffer.position();
+ labelCheckRedeemTxnSender = codeByteBuffer.position();
// Extract sender address from transaction into B register
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B));
// Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer)
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer));
// Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction.
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalRecipient1, calcOffset(codeByteBuffer, labelTradeTxLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalRecipient2, calcOffset(codeByteBuffer, labelTradeTxLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalRecipient3, calcOffset(codeByteBuffer, labelTradeTxLoop)));
- codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalRecipient4, calcOffset(codeByteBuffer, labelTradeTxLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalPartnerAddress1, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalPartnerAddress2, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalPartnerAddress3, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
+ codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalPartnerAddress4, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
- /* Check 'secret-a' in transaction's message */
+ /* Check 'secret-A' in transaction's message */
// Extract secret-A from first 32 bytes of message from transaction into B register
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B));
@@ -476,14 +515,14 @@ public class BTCACCT {
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrResult, addrMessageDataPointer, addrMessageDataLength));
// If hashes don't match, addrResult will be zero so go find another transaction
codeByteBuffer.put(OpCode.BNZ_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckSecretB)));
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxLoop == null ? 0 : labelTradeTxLoop));
+ codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop));
- /* Check 'secret-b' in transaction's message */
+ /* Check 'secret-B' in transaction's message */
labelCheckSecretB = codeByteBuffer.position();
// Extract secret-B from next 32 bytes of message from transaction into B register
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessageSecretBOffset));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageSecretBOffset));
// Save B register into data segment starting at addrMessageData (as pointed to by addrMessageDataPointer)
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageDataPointer));
// Load B register with expected hash result (as pointed to by addrHashOfSecretBPointer)
@@ -493,28 +532,28 @@ public class BTCACCT {
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrResult, addrMessageDataPointer, addrMessageDataLength));
// If hashes don't match, addrResult will be zero so go find another transaction
codeByteBuffer.put(OpCode.BNZ_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelPayout)));
- codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxLoop == null ? 0 : labelTradeTxLoop));
+ codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop));
- /* Success! Pay arranged amount to intended recipient */
+ /* Success! Pay arranged amount to receive address */
labelPayout = codeByteBuffer.position();
- // Load B register with intended recipient address (as pointed to by addrQortalRecipientPointer)
- codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrQortalRecipientPointer));
+ // Extract Qortal receive address from next 32 bytes of message from transaction into B register
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageReceiveAddressOffset));
// Pay AT's balance to recipient
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrQortAmount));
- // Set redeem flag
- codeByteBuffer.put(OpCode.INC_DAT.compile(addrRedeemFlag));
+ // Set redeemed mode
+ codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.REDEEMED.value));
+ // We're finished forever (finishing auto-refunds remaining balance to AT creator)
+ codeByteBuffer.put(OpCode.FIN_IMD.compile());
// Fall-through to refunding any remaining balance back to AT creator
/* Refund balance back to AT creator */
labelRefund = codeByteBuffer.position();
- // Load B register with AT creator's address.
- codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B));
- // Pay AT's balance back to AT's creator.
- codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B));
- // We're finished forever
+ // Set refunded mode
+ codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.REFUNDED.value));
+ // We're finished forever (finishing auto-refunds remaining balance to AT creator)
codeByteBuffer.put(OpCode.FIN_IMD.compile());
} catch (CompilationException e) {
throw new IllegalStateException("Unable to compile BTC-QORT ACCT?", e);
@@ -587,15 +626,16 @@ public class BTCACCT {
// Expected BTC amount
tradeData.expectedBitcoin = dataByteBuffer.getLong();
+ // Trade timeout
tradeData.tradeTimeout = (int) dataByteBuffer.getLong();
// Skip MESSAGE transaction type
dataByteBuffer.position(dataByteBuffer.position() + 8);
- // Skip expected OFFER message length
+ // Skip expected 'trade' message length
dataByteBuffer.position(dataByteBuffer.position() + 8);
- // Skip expected TRADE message length
+ // Skip expected 'redeem' message length
dataByteBuffer.position(dataByteBuffer.position() + 8);
// Skip pointer to creator's address
@@ -604,25 +644,28 @@ public class BTCACCT {
// Skip pointer to hash-of-secret-B
dataByteBuffer.position(dataByteBuffer.position() + 8);
- // Skip pointer to Qortal recipient
+ // Skip pointer to partner's Qortal trade address
dataByteBuffer.position(dataByteBuffer.position() + 8);
// Skip pointer to message sender
dataByteBuffer.position(dataByteBuffer.position() + 8);
- // Skip OFFER message data offset for recipient's bitcoin PKH
+ // Skip 'trade' message data offset for recipient's bitcoin PKH
dataByteBuffer.position(dataByteBuffer.position() + 8);
- // Skip pointer to recipient's bitcoin PKH
+ // Skip pointer to partner's bitcoin PKH
dataByteBuffer.position(dataByteBuffer.position() + 8);
- // Skip OFFER message data offset for hash-of-secret-A
+ // Skip 'trade' message data offset for hash-of-secret-A
dataByteBuffer.position(dataByteBuffer.position() + 8);
// Skip pointer to hash-of-secret-A
dataByteBuffer.position(dataByteBuffer.position() + 8);
- // Skip TRADE message data offset for secret-B
+ // Skip 'redeem' message data offset for secret-B
+ dataByteBuffer.position(dataByteBuffer.position() + 8);
+
+ // Skip 'redeem' message data offset for partner's Qortal receive address
dataByteBuffer.position(dataByteBuffer.position() + 8);
// Skip pointer to message data
@@ -631,17 +674,12 @@ public class BTCACCT {
// Skip message data length
dataByteBuffer.position(dataByteBuffer.position() + 8);
- // Creator's Bitcoin/foreign receiving public key hash
- tradeData.creatorReceiveBitcoinPKH = new byte[20];
- dataByteBuffer.get(tradeData.creatorReceiveBitcoinPKH);
- dataByteBuffer.position(dataByteBuffer.position() + 32 - tradeData.creatorReceiveBitcoinPKH.length); // skip to 32 bytes
-
/* End of constants / begin variables */
// Skip AT creator's address
dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
- // Recipient's trade address (if present)
+ // Partner's trade address (if present)
dataByteBuffer.get(addressBytes);
String qortalRecipient = Base58.encode(addressBytes);
dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length);
@@ -655,7 +693,7 @@ public class BTCACCT {
// AT refund timeout (probably only useful for debugging)
int refundTimeout = (int) dataByteBuffer.getLong();
- // Trade offer timeout (AT 'timestamp' converted to Qortal block height)
+ // Trade-mode refund timestamp (AT 'timestamp' converted to Qortal block height)
long tradeRefundTimestamp = dataByteBuffer.getLong();
// Skip last transaction timestamp
@@ -684,60 +722,58 @@ public class BTCACCT {
dataByteBuffer.get(hashOfSecretA);
dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes
- // Potential recipient's Bitcoin PKH
+ // Potential partner's Bitcoin PKH
byte[] recipientBitcoinPKH = new byte[20];
dataByteBuffer.get(recipientBitcoinPKH);
dataByteBuffer.position(dataByteBuffer.position() + 32 - recipientBitcoinPKH.length); // skip to 32 bytes
- long mode = dataByteBuffer.getLong();
+ long modeValue = dataByteBuffer.getLong();
+ Mode mode = Mode.valueOf((int) (modeValue & 0xffL));
- long redeemFlag = dataByteBuffer.getLong();
-
- if (mode != 0) {
- tradeData.mode = CrossChainTradeData.Mode.TRADE;
+ if (mode != null && mode != Mode.OFFERING) {
+ tradeData.mode = mode;
tradeData.refundTimeout = refundTimeout;
tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight;
- tradeData.qortalRecipient = qortalRecipient;
+ tradeData.qortalPartnerAddress = qortalRecipient;
tradeData.hashOfSecretA = hashOfSecretA;
- tradeData.recipientBitcoinPKH = recipientBitcoinPKH;
+ tradeData.partnerBitcoinPKH = recipientBitcoinPKH;
tradeData.lockTimeA = lockTimeA;
tradeData.lockTimeB = lockTimeB;
- tradeData.hasRedeemed = redeemFlag != 0;
} else {
- tradeData.mode = CrossChainTradeData.Mode.OFFER;
+ tradeData.mode = Mode.OFFERING;
}
return tradeData;
}
- /** Returns trade-info MESSAGE payload for trade partner/recipient to send to AT creator's trade address. */
+ /** Returns 'offer' MESSAGE payload for trade partner to send to AT creator's trade address. */
public static byte[] buildOfferMessage(byte[] recipientBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) {
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
return Bytes.concat(recipientBitcoinPKH, hashOfSecretA, lockTimeABytes);
}
- /** Returns trade-info extracted from MESSAGE payload sent by trade partner/recipient, or null if not valid. */
+ /** Returns info extracted from 'offer' MESSAGE payload sent by trade partner to AT creator's trade address, or null if not valid. */
public static OfferMessageData extractOfferMessageData(byte[] messageData) {
- if (messageData == null || messageData.length != 20 + 20 + 8)
+ if (messageData == null || messageData.length != OFFER_MESSAGE_LENGTH)
return null;
OfferMessageData offerMessageData = new OfferMessageData();
- offerMessageData.recipientBitcoinPKH = Arrays.copyOfRange(messageData, 0, 20);
+ offerMessageData.partnerBitcoinPKH = Arrays.copyOfRange(messageData, 0, 20);
offerMessageData.hashOfSecretA = Arrays.copyOfRange(messageData, 20, 40);
offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 40);
return offerMessageData;
}
- /** Returns trade-info MESSAGE payload for AT creator to send to AT. */
- public static byte[] buildTradeMessage(String recipientQortalAddress, byte[] recipientBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int lockTimeB) {
- byte[] data = new byte[32 + 32 + 32];
- byte[] recipientQortalAddressBytes = Base58.decode(recipientQortalAddress);
+ /** Returns 'trade' MESSAGE payload for AT creator to send to AT. */
+ public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int lockTimeB) {
+ byte[] data = new byte[TRADE_MESSAGE_LENGTH];
+ byte[] recipientQortalAddressBytes = Base58.decode(partnerQortalTradeAddress);
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
byte[] lockTimeBBytes = BitTwiddling.toBEByteArray((long) lockTimeB);
System.arraycopy(recipientQortalAddressBytes, 0, data, 0, recipientQortalAddressBytes.length);
- System.arraycopy(recipientBitcoinPKH, 0, data, 32, recipientBitcoinPKH.length);
+ System.arraycopy(partnerBitcoinPKH, 0, data, 32, partnerBitcoinPKH.length);
System.arraycopy(lockTimeBBytes, 0, data, 56, lockTimeBBytes.length);
System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length);
System.arraycopy(lockTimeABytes, 0, data, 88, lockTimeABytes.length);
@@ -745,9 +781,9 @@ public class BTCACCT {
return data;
}
- /** Returns refund MESSAGE payload for AT creator to cancel trade AT. */
- public static byte[] buildRefundMessage(String creatorQortalAddress) {
- byte[] data = new byte[32];
+ /** Returns 'cancel' MESSAGE payload for AT creator to cancel trade AT. */
+ public static byte[] buildCancelMessage(String creatorQortalAddress) {
+ byte[] data = new byte[CANCEL_MESSAGE_LENGTH];
byte[] creatorQortalAddressBytes = Base58.decode(creatorQortalAddress);
System.arraycopy(creatorQortalAddressBytes, 0, data, 0, creatorQortalAddressBytes.length);
@@ -755,31 +791,33 @@ public class BTCACCT {
return data;
}
- /** Returns redeem MESSAGE payload for trade partner/recipient to send to AT. */
- public static byte[] buildRedeemMessage(byte[] secretA, byte[] secretB) {
- byte[] data = new byte[32 + 32];
+ /** Returns 'redeem' MESSAGE payload for trade partner/ to send to AT. */
+ public static byte[] buildRedeemMessage(byte[] secretA, byte[] secretB, String qortalReceiveAddress) {
+ byte[] data = new byte[REDEEM_MESSAGE_LENGTH];
+ byte[] qortalReceiveAddressBytes = Base58.decode(qortalReceiveAddress);
System.arraycopy(secretA, 0, data, 0, secretA.length);
System.arraycopy(secretB, 0, data, 32, secretB.length);
+ System.arraycopy(qortalReceiveAddressBytes, 0, data, 64, qortalReceiveAddressBytes.length);
return data;
}
- /** Returns P2SH-B lockTime (epoch seconds) based on trade partner/recipient's MESSAGE timestamp and P2SH-A locktime. */
- public static int calcLockTimeB(long recipientMessageTimestamp, int lockTimeA) {
- // lockTimeB is halfway between recipientMessageTimesamp and lockTimeA
- return (int) ((lockTimeA + (recipientMessageTimestamp / 1000L)) / 2L);
+ /** Returns P2SH-B lockTime (epoch seconds) based on trade partner's 'offer' MESSAGE timestamp and P2SH-A locktime. */
+ public static int calcLockTimeB(long offerMessageTimestamp, int lockTimeA) {
+ // lockTimeB is halfway between offerMessageTimesamp and lockTimeA
+ return (int) ((lockTimeA + (offerMessageTimestamp / 1000L)) / 2L);
}
public static byte[] findSecretA(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
String atAddress = crossChainTradeData.qortalAtAddress;
- String redeemerAddress = crossChainTradeData.qortalRecipient;
+ String redeemerAddress = crossChainTradeData.qortalPartnerAddress;
List
*
*
*
*
+ *
+ *
- *
*
*
*
- *
*
- *
*
- *
*
+ *
+ *
- *
*