forked from Qortal/qortal
WIP: more work on trade-bot
This commit is contained in:
parent
faa6e82bef
commit
04d691991a
@ -0,0 +1,33 @@
|
|||||||
|
package org.qortal.api.model;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class TradeBotCreateRequest {
|
||||||
|
|
||||||
|
@Schema(description = "Trade creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
|
||||||
|
public byte[] creatorPublicKey;
|
||||||
|
|
||||||
|
@Schema(description = "QORT amount paid out on successful trade", example = "80.40200000")
|
||||||
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
|
public long qortAmount;
|
||||||
|
|
||||||
|
@Schema(description = "QORT amount funding AT, including covering AT execution fees", example = "81")
|
||||||
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
|
public long fundingQortAmount;
|
||||||
|
|
||||||
|
@Schema(description = "Bitcoin amount wanted in return", example = "0.00864200")
|
||||||
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
|
public long bitcoinAmount;
|
||||||
|
|
||||||
|
@Schema(description = "Trade time window (minutes) from trade agreement to automatic refund", example = "10080")
|
||||||
|
public Integer tradeTimeout;
|
||||||
|
|
||||||
|
public TradeBotCreateRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -38,6 +38,7 @@ import org.qortal.api.ApiExceptionFactory;
|
|||||||
import org.qortal.api.model.CrossChainCancelRequest;
|
import org.qortal.api.model.CrossChainCancelRequest;
|
||||||
import org.qortal.api.model.CrossChainSecretRequest;
|
import org.qortal.api.model.CrossChainSecretRequest;
|
||||||
import org.qortal.api.model.CrossChainTradeRequest;
|
import org.qortal.api.model.CrossChainTradeRequest;
|
||||||
|
import org.qortal.api.model.TradeBotCreateRequest;
|
||||||
import org.qortal.api.model.CrossChainBitcoinP2SHStatus;
|
import org.qortal.api.model.CrossChainBitcoinP2SHStatus;
|
||||||
import org.qortal.api.model.CrossChainBitcoinRedeemRequest;
|
import org.qortal.api.model.CrossChainBitcoinRedeemRequest;
|
||||||
import org.qortal.api.model.CrossChainBitcoinRefundRequest;
|
import org.qortal.api.model.CrossChainBitcoinRefundRequest;
|
||||||
@ -720,6 +721,36 @@ public class CrossChainResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/tradebot")
|
||||||
|
@Operation(
|
||||||
|
summary = "Create a trade offer",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = TradeBotCreateRequest.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
|
public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
byte[] unsignedBytes = TradeBot.createTrade(repository, tradeBotCreateRequest);
|
||||||
|
|
||||||
|
return Base58.encode(unsignedBytes);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/tradebot/{ataddress}")
|
@Path("/tradebot/{ataddress}")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -11,14 +11,23 @@ import org.bitcoinj.core.ECKey;
|
|||||||
import org.bitcoinj.core.LegacyAddress;
|
import org.bitcoinj.core.LegacyAddress;
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
|
import org.qortal.account.PublicKeyAccount;
|
||||||
|
import org.qortal.api.model.TradeBotCreateRequest;
|
||||||
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.crosschain.BTC;
|
import org.qortal.crosschain.BTC;
|
||||||
import org.qortal.crosschain.BTCACCT;
|
import org.qortal.crosschain.BTCACCT;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||||
import org.qortal.data.crosschain.TradeBotData;
|
import org.qortal.data.crosschain.TradeBotData;
|
||||||
|
import org.qortal.data.transaction.BaseTransactionData;
|
||||||
|
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||||
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
|
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
|
||||||
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
public class TradeBot {
|
public class TradeBot {
|
||||||
|
|
||||||
@ -38,6 +47,49 @@ public class TradeBot {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] createTrade(Repository repository, TradeBotCreateRequest tradeBotCreateRequest) {
|
||||||
|
BTC btc = BTC.getInstance();
|
||||||
|
NetworkParameters params = btc.getNetworkParameters();
|
||||||
|
|
||||||
|
byte[] tradePrivateKey = generateTradePrivateKey();
|
||||||
|
byte[] secret = generateSecret();
|
||||||
|
byte[] secretHash = Crypto.digest(secret);
|
||||||
|
|
||||||
|
byte[] tradeNativePublicKey = deriveTradeNativePublicKey(tradePrivateKey);
|
||||||
|
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
|
||||||
|
|
||||||
|
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
|
||||||
|
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
||||||
|
|
||||||
|
PublicKeyAccount creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey);
|
||||||
|
|
||||||
|
// Deploy AT
|
||||||
|
long timestamp = NTP.getTime();
|
||||||
|
byte[] reference = creator.getLastReference();
|
||||||
|
long fee = 0L;
|
||||||
|
byte[] signature = null;
|
||||||
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, creator.getPublicKey(), fee, signature);
|
||||||
|
|
||||||
|
String name = "QORT/BTC ACCT";
|
||||||
|
String description = "QORT/BTC cross-chain trade";
|
||||||
|
String aTType = "ACCT";
|
||||||
|
String tags = "ACCT QORT BTC";
|
||||||
|
byte[] creationBytes = BTCACCT.buildQortalAT(creator.getAddress(), tradeNativePublicKeyHash, secretHash, tradeBotCreateRequest.tradeTimeout, tradeBotCreateRequest.qortAmount, tradeBotCreateRequest.bitcoinAmount);
|
||||||
|
long amount = tradeBotCreateRequest.fundingQortAmount;
|
||||||
|
|
||||||
|
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT);
|
||||||
|
DeployAtTransaction.ensureATAddress(deployAtTransactionData);
|
||||||
|
String atAddress = deployAtTransactionData.getAtAddress();
|
||||||
|
|
||||||
|
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.BOB_WAITING_FOR_MESSAGE,
|
||||||
|
tradeNativePublicKey, tradeNativePublicKeyHash, secret, secretHash,
|
||||||
|
tradeForeignPublicKey, tradeForeignPublicKeyHash, atAddress, null);
|
||||||
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
|
||||||
|
// Return to user for signing and broadcast as we don't have their Qortal private key
|
||||||
|
return DeployAtTransactionTransformer.toBytes(deployAtTransactionData);
|
||||||
|
}
|
||||||
|
|
||||||
public static String startResponse(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
|
public static String startResponse(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
|
||||||
BTC btc = BTC.getInstance();
|
BTC btc = BTC.getInstance();
|
||||||
NetworkParameters params = btc.getNetworkParameters();
|
NetworkParameters params = btc.getNetworkParameters();
|
||||||
@ -53,7 +105,7 @@ public class TradeBot {
|
|||||||
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
||||||
|
|
||||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.ALICE_WAITING_FOR_P2SH_A,
|
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.ALICE_WAITING_FOR_P2SH_A,
|
||||||
tradeNativePublicKey, tradeNativePublicKeyHash, secret, secretHash,
|
tradeNativePublicKey, tradeNativePublicKeyHash, secret, secretHash,
|
||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash, crossChainTradeData.qortalAtAddress, null);
|
tradeForeignPublicKey, tradeForeignPublicKeyHash, crossChainTradeData.qortalAtAddress, null);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
|
|
||||||
@ -92,8 +144,8 @@ public class TradeBot {
|
|||||||
|
|
||||||
for (TradeBotData tradeBotData : allTradeBotData)
|
for (TradeBotData tradeBotData : allTradeBotData)
|
||||||
switch (tradeBotData.getState()) {
|
switch (tradeBotData.getState()) {
|
||||||
case ALICE_START:
|
case BOB_WAITING_FOR_MESSAGE:
|
||||||
handleAliceStart(repository, tradeBotData);
|
handleBobWaitingForMessage(repository, tradeBotData);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -101,7 +153,7 @@ public class TradeBot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAliceStart(Repository repository, TradeBotData tradeBotData) {
|
private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
public class TradeBotData {
|
public class TradeBotData {
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
BOB_START(0), BOB_WAITING_FOR_P2SH_A(10), BOB_WAITING_FOR_P2SH_B(20), BOB_WAITING_FOR_AT_REDEEM(30),
|
BOB_WAITING_FOR_AT_CONFIRM(10), BOB_WAITING_FOR_MESSAGE(20), BOB_WAITING_FOR_P2SH_A(30), BOB_WAITING_FOR_P2SH_B(40), BOB_WAITING_FOR_AT_REDEEM(50),
|
||||||
ALICE_START(100), ALICE_WAITING_FOR_P2SH_A(110), ALICE_WAITING_FOR_AT_LOCK(120), ALICE_WATCH_P2SH_B(130);
|
ALICE_WAITING_FOR_P2SH_A(110), ALICE_WAITING_FOR_AT_LOCK(120), ALICE_WATCH_P2SH_B(130);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
private static final Map<Integer, State> map = stream(State.values()).collect(toMap(state -> state.value, state -> state));
|
private static final Map<Integer, State> map = stream(State.values()).collect(toMap(state -> state.value, state -> state));
|
||||||
|
@ -26,7 +26,7 @@ import com.google.common.base.Utf8;
|
|||||||
public class DeployAtTransaction extends Transaction {
|
public class DeployAtTransaction extends Transaction {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private DeployAtTransactionData deployATTransactionData;
|
private DeployAtTransactionData deployAtTransactionData;
|
||||||
|
|
||||||
// Other useful constants
|
// Other useful constants
|
||||||
public static final int MAX_NAME_SIZE = 200;
|
public static final int MAX_NAME_SIZE = 200;
|
||||||
@ -40,31 +40,31 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
public DeployAtTransaction(Repository repository, TransactionData transactionData) {
|
public DeployAtTransaction(Repository repository, TransactionData transactionData) {
|
||||||
super(repository, transactionData);
|
super(repository, transactionData);
|
||||||
|
|
||||||
this.deployATTransactionData = (DeployAtTransactionData) this.transactionData;
|
this.deployAtTransactionData = (DeployAtTransactionData) this.transactionData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// More information
|
// More information
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getRecipientAddresses() throws DataException {
|
public List<String> getRecipientAddresses() throws DataException {
|
||||||
return Collections.singletonList(this.deployATTransactionData.getAtAddress());
|
return Collections.singletonList(this.deployAtTransactionData.getAtAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns AT version from the header bytes */
|
/** Returns AT version from the header bytes */
|
||||||
private short getVersion() {
|
private short getVersion() {
|
||||||
byte[] creationBytes = deployATTransactionData.getCreationBytes();
|
byte[] creationBytes = deployAtTransactionData.getCreationBytes();
|
||||||
return (short) ((creationBytes[0] << 8) | (creationBytes[1] & 0xff)); // Big-endian
|
return (short) ((creationBytes[0] << 8) | (creationBytes[1] & 0xff)); // Big-endian
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Make sure deployATTransactionData has an ATAddress */
|
/** Make sure deployATTransactionData has an ATAddress */
|
||||||
private void ensureATAddress() throws DataException {
|
public static void ensureATAddress(DeployAtTransactionData deployAtTransactionData) throws DataException {
|
||||||
if (this.deployATTransactionData.getAtAddress() != null)
|
if (deployAtTransactionData.getAtAddress() != null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Use transaction transformer
|
// Use transaction transformer
|
||||||
try {
|
try {
|
||||||
String atAddress = Crypto.toATAddress(TransactionTransformer.toBytesForSigning(this.deployATTransactionData));
|
String atAddress = Crypto.toATAddress(TransactionTransformer.toBytesForSigning(deployAtTransactionData));
|
||||||
this.deployATTransactionData.setAtAddress(atAddress);
|
deployAtTransactionData.setAtAddress(atAddress);
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
throw new DataException("Unable to generate AT address");
|
throw new DataException("Unable to generate AT address");
|
||||||
}
|
}
|
||||||
@ -73,9 +73,9 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
// Navigation
|
// Navigation
|
||||||
|
|
||||||
public Account getATAccount() throws DataException {
|
public Account getATAccount() throws DataException {
|
||||||
ensureATAddress();
|
ensureATAddress(this.deployAtTransactionData);
|
||||||
|
|
||||||
return new Account(this.repository, this.deployATTransactionData.getAtAddress());
|
return new Account(this.repository, this.deployAtTransactionData.getAtAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
@ -83,30 +83,30 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
// Check name size bounds
|
// Check name size bounds
|
||||||
int nameLength = Utf8.encodedLength(this.deployATTransactionData.getName());
|
int nameLength = Utf8.encodedLength(this.deployAtTransactionData.getName());
|
||||||
if (nameLength < 1 || nameLength > MAX_NAME_SIZE)
|
if (nameLength < 1 || nameLength > MAX_NAME_SIZE)
|
||||||
return ValidationResult.INVALID_NAME_LENGTH;
|
return ValidationResult.INVALID_NAME_LENGTH;
|
||||||
|
|
||||||
// Check description size bounds
|
// Check description size bounds
|
||||||
int descriptionlength = Utf8.encodedLength(this.deployATTransactionData.getDescription());
|
int descriptionlength = Utf8.encodedLength(this.deployAtTransactionData.getDescription());
|
||||||
if (descriptionlength < 1 || descriptionlength > MAX_DESCRIPTION_SIZE)
|
if (descriptionlength < 1 || descriptionlength > MAX_DESCRIPTION_SIZE)
|
||||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
||||||
|
|
||||||
// Check AT-type size bounds
|
// Check AT-type size bounds
|
||||||
int atTypeLength = Utf8.encodedLength(this.deployATTransactionData.getAtType());
|
int atTypeLength = Utf8.encodedLength(this.deployAtTransactionData.getAtType());
|
||||||
if (atTypeLength < 1 || atTypeLength > MAX_AT_TYPE_SIZE)
|
if (atTypeLength < 1 || atTypeLength > MAX_AT_TYPE_SIZE)
|
||||||
return ValidationResult.INVALID_AT_TYPE_LENGTH;
|
return ValidationResult.INVALID_AT_TYPE_LENGTH;
|
||||||
|
|
||||||
// Check tags size bounds
|
// Check tags size bounds
|
||||||
int tagsLength = Utf8.encodedLength(this.deployATTransactionData.getTags());
|
int tagsLength = Utf8.encodedLength(this.deployAtTransactionData.getTags());
|
||||||
if (tagsLength < 1 || tagsLength > MAX_TAGS_SIZE)
|
if (tagsLength < 1 || tagsLength > MAX_TAGS_SIZE)
|
||||||
return ValidationResult.INVALID_TAGS_LENGTH;
|
return ValidationResult.INVALID_TAGS_LENGTH;
|
||||||
|
|
||||||
// Check amount is positive
|
// Check amount is positive
|
||||||
if (this.deployATTransactionData.getAmount() <= 0)
|
if (this.deployAtTransactionData.getAmount() <= 0)
|
||||||
return ValidationResult.NEGATIVE_AMOUNT;
|
return ValidationResult.NEGATIVE_AMOUNT;
|
||||||
|
|
||||||
long assetId = this.deployATTransactionData.getAssetId();
|
long assetId = this.deployAtTransactionData.getAssetId();
|
||||||
AssetData assetData = this.repository.getAssetRepository().fromAssetId(assetId);
|
AssetData assetData = this.repository.getAssetRepository().fromAssetId(assetId);
|
||||||
// Check asset even exists
|
// Check asset even exists
|
||||||
if (assetData == null)
|
if (assetData == null)
|
||||||
@ -117,7 +117,7 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
return ValidationResult.ASSET_NOT_SPENDABLE;
|
return ValidationResult.ASSET_NOT_SPENDABLE;
|
||||||
|
|
||||||
// Check asset amount is integer if asset is not divisible
|
// Check asset amount is integer if asset is not divisible
|
||||||
if (!assetData.isDivisible() && this.deployATTransactionData.getAmount() % Amounts.MULTIPLIER != 0)
|
if (!assetData.isDivisible() && this.deployAtTransactionData.getAmount() % Amounts.MULTIPLIER != 0)
|
||||||
return ValidationResult.INVALID_AMOUNT;
|
return ValidationResult.INVALID_AMOUNT;
|
||||||
|
|
||||||
Account creator = this.getCreator();
|
Account creator = this.getCreator();
|
||||||
@ -125,15 +125,15 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
// Check creator has enough funds
|
// Check creator has enough funds
|
||||||
if (assetId == Asset.QORT) {
|
if (assetId == Asset.QORT) {
|
||||||
// Simple case: amount and fee both in QORT
|
// Simple case: amount and fee both in QORT
|
||||||
long minimumBalance = this.deployATTransactionData.getFee() + this.deployATTransactionData.getAmount();
|
long minimumBalance = this.deployAtTransactionData.getFee() + this.deployAtTransactionData.getAmount();
|
||||||
|
|
||||||
if (creator.getConfirmedBalance(Asset.QORT) < minimumBalance)
|
if (creator.getConfirmedBalance(Asset.QORT) < minimumBalance)
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
} else {
|
} else {
|
||||||
if (creator.getConfirmedBalance(Asset.QORT) < this.deployATTransactionData.getFee())
|
if (creator.getConfirmedBalance(Asset.QORT) < this.deployAtTransactionData.getFee())
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
if (creator.getConfirmedBalance(assetId) < this.deployATTransactionData.getAmount())
|
if (creator.getConfirmedBalance(assetId) < this.deployAtTransactionData.getAmount())
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,12 +142,12 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
return ValidationResult.INVALID_CREATION_BYTES;
|
return ValidationResult.INVALID_CREATION_BYTES;
|
||||||
|
|
||||||
// Check creation bytes are valid (for v2+)
|
// Check creation bytes are valid (for v2+)
|
||||||
this.ensureATAddress();
|
ensureATAddress(this.deployAtTransactionData);
|
||||||
|
|
||||||
// Just enough AT data to allow API to query initial balances, etc.
|
// Just enough AT data to allow API to query initial balances, etc.
|
||||||
String atAddress = this.deployATTransactionData.getAtAddress();
|
String atAddress = this.deployAtTransactionData.getAtAddress();
|
||||||
byte[] creatorPublicKey = this.deployATTransactionData.getCreatorPublicKey();
|
byte[] creatorPublicKey = this.deployAtTransactionData.getCreatorPublicKey();
|
||||||
long creation = this.deployATTransactionData.getTimestamp();
|
long creation = this.deployAtTransactionData.getTimestamp();
|
||||||
ATData skeletonAtData = new ATData(atAddress, creatorPublicKey, creation, assetId);
|
ATData skeletonAtData = new ATData(atAddress, creatorPublicKey, creation, assetId);
|
||||||
|
|
||||||
int height = this.repository.getBlockRepository().getBlockchainHeight() + 1;
|
int height = this.repository.getBlockRepository().getBlockchainHeight() + 1;
|
||||||
@ -157,7 +157,7 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance();
|
QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new MachineState(api, loggerFactory, this.deployATTransactionData.getCreationBytes());
|
new MachineState(api, loggerFactory, this.deployAtTransactionData.getCreationBytes());
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// Not valid
|
// Not valid
|
||||||
return ValidationResult.INVALID_CREATION_BYTES;
|
return ValidationResult.INVALID_CREATION_BYTES;
|
||||||
@ -169,25 +169,25 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
@Override
|
@Override
|
||||||
public ValidationResult isProcessable() throws DataException {
|
public ValidationResult isProcessable() throws DataException {
|
||||||
Account creator = getCreator();
|
Account creator = getCreator();
|
||||||
long assetId = this.deployATTransactionData.getAssetId();
|
long assetId = this.deployAtTransactionData.getAssetId();
|
||||||
|
|
||||||
// Check creator has enough funds
|
// Check creator has enough funds
|
||||||
if (assetId == Asset.QORT) {
|
if (assetId == Asset.QORT) {
|
||||||
// Simple case: amount and fee both in QORT
|
// Simple case: amount and fee both in QORT
|
||||||
long minimumBalance = this.deployATTransactionData.getFee() + this.deployATTransactionData.getAmount();
|
long minimumBalance = this.deployAtTransactionData.getFee() + this.deployAtTransactionData.getAmount();
|
||||||
|
|
||||||
if (creator.getConfirmedBalance(Asset.QORT) < minimumBalance)
|
if (creator.getConfirmedBalance(Asset.QORT) < minimumBalance)
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
} else {
|
} else {
|
||||||
if (creator.getConfirmedBalance(Asset.QORT) < this.deployATTransactionData.getFee())
|
if (creator.getConfirmedBalance(Asset.QORT) < this.deployAtTransactionData.getFee())
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
if (creator.getConfirmedBalance(assetId) < this.deployATTransactionData.getAmount())
|
if (creator.getConfirmedBalance(assetId) < this.deployAtTransactionData.getAmount())
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check AT doesn't already exist
|
// Check AT doesn't already exist
|
||||||
if (this.repository.getATRepository().exists(this.deployATTransactionData.getAtAddress()))
|
if (this.repository.getATRepository().exists(this.deployAtTransactionData.getAtAddress()))
|
||||||
return ValidationResult.AT_ALREADY_EXISTS;
|
return ValidationResult.AT_ALREADY_EXISTS;
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
@ -195,40 +195,40 @@ public class DeployAtTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process() throws DataException {
|
public void process() throws DataException {
|
||||||
this.ensureATAddress();
|
ensureATAddress(this.deployAtTransactionData);
|
||||||
|
|
||||||
// Deploy AT, saving into repository
|
// Deploy AT, saving into repository
|
||||||
AT at = new AT(this.repository, this.deployATTransactionData);
|
AT at = new AT(this.repository, this.deployAtTransactionData);
|
||||||
at.deploy();
|
at.deploy();
|
||||||
|
|
||||||
long assetId = this.deployATTransactionData.getAssetId();
|
long assetId = this.deployAtTransactionData.getAssetId();
|
||||||
|
|
||||||
// Update creator's balance regarding initial payment to AT
|
// Update creator's balance regarding initial payment to AT
|
||||||
Account creator = getCreator();
|
Account creator = getCreator();
|
||||||
creator.modifyAssetBalance(assetId, - this.deployATTransactionData.getAmount());
|
creator.modifyAssetBalance(assetId, - this.deployAtTransactionData.getAmount());
|
||||||
|
|
||||||
// Update AT's reference, which also creates AT account
|
// Update AT's reference, which also creates AT account
|
||||||
Account atAccount = this.getATAccount();
|
Account atAccount = this.getATAccount();
|
||||||
atAccount.setLastReference(this.deployATTransactionData.getSignature());
|
atAccount.setLastReference(this.deployAtTransactionData.getSignature());
|
||||||
|
|
||||||
// Update AT's balance
|
// Update AT's balance
|
||||||
atAccount.setConfirmedBalance(assetId, this.deployATTransactionData.getAmount());
|
atAccount.setConfirmedBalance(assetId, this.deployAtTransactionData.getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
// Delete AT from repository
|
// Delete AT from repository
|
||||||
AT at = new AT(this.repository, this.deployATTransactionData);
|
AT at = new AT(this.repository, this.deployAtTransactionData);
|
||||||
at.undeploy();
|
at.undeploy();
|
||||||
|
|
||||||
long assetId = this.deployATTransactionData.getAssetId();
|
long assetId = this.deployAtTransactionData.getAssetId();
|
||||||
|
|
||||||
// Update creator's balance regarding initial payment to AT
|
// Update creator's balance regarding initial payment to AT
|
||||||
Account creator = getCreator();
|
Account creator = getCreator();
|
||||||
creator.modifyAssetBalance(assetId, this.deployATTransactionData.getAmount());
|
creator.modifyAssetBalance(assetId, this.deployAtTransactionData.getAmount());
|
||||||
|
|
||||||
// Delete AT's account (and hence its balance)
|
// Delete AT's account (and hence its balance)
|
||||||
this.repository.getAccountRepository().delete(this.deployATTransactionData.getAtAddress());
|
this.repository.getAccountRepository().delete(this.deployAtTransactionData.getAtAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user