diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java
index 0c61c83b..40654179 100644
--- a/src/main/java/org/qortal/api/resource/CrossChainResource.java
+++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java
@@ -1011,6 +1011,9 @@ public class CrossChainResource {
 		if (tradeBotCreateRequest.tradeTimeout < 60)
 			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
 
+		if (tradeBotCreateRequest.bitcoinAmount <= 0 || tradeBotCreateRequest.qortAmount <= 0 || tradeBotCreateRequest.fundingQortAmount <= 0)
+			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
+
 		try (final Repository repository = RepositoryManager.getRepository()) {
 			// Do some simple checking first
 			Account creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey);
diff --git a/src/main/java/org/qortal/controller/TradeBot.java b/src/main/java/org/qortal/controller/TradeBot.java
index 74bec0bc..496125b4 100644
--- a/src/main/java/org/qortal/controller/TradeBot.java
+++ b/src/main/java/org/qortal/controller/TradeBot.java
@@ -163,7 +163,7 @@ public class TradeBot {
 		String atAddress = deployAtTransactionData.getAtAddress();
 
 		TradeBotData tradeBotData =  new TradeBotData(tradePrivateKey, TradeBotData.State.BOB_WAITING_FOR_AT_CONFIRM,
-				atAddress,
+				creator.getAddress(), atAddress, timestamp, tradeBotCreateRequest.qortAmount,
 				tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
 				secretB, hashOfSecretB,
 				tradeForeignPublicKey, tradeForeignPublicKeyHash,
@@ -237,7 +237,7 @@ public class TradeBot {
 		int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L);
 
 		TradeBotData tradeBotData =  new TradeBotData(tradePrivateKey, TradeBotData.State.ALICE_WAITING_FOR_P2SH_A,
-				crossChainTradeData.qortalAtAddress,
+				receivingAddress, crossChainTradeData.qortalAtAddress, NTP.getTime(), crossChainTradeData.qortAmount,
 				tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
 				secretA, hashOfSecretA,
 				tradeForeignPublicKey, tradeForeignPublicKeyHash,
@@ -380,6 +380,7 @@ public class TradeBot {
 			return;
 
 		tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_MESSAGE);
+		tradeBotData.setTimestamp(NTP.getTime());
 		repository.getCrossChainRepository().save(tradeBotData);
 		repository.saveChanges();
 
@@ -418,6 +419,7 @@ public class TradeBot {
 		if (atData.getIsFinished()) {
 			// No point sending MESSAGE - might as well wait for refund
 			tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 
@@ -456,6 +458,7 @@ public class TradeBot {
 		}
 
 		tradeBotData.setState(TradeBotData.State.ALICE_WAITING_FOR_AT_LOCK);
+		tradeBotData.setTimestamp(NTP.getTime());
 		repository.getCrossChainRepository().save(tradeBotData);
 		repository.saveChanges();
 
@@ -493,6 +496,7 @@ public class TradeBot {
 		// If AT has finished then Bob likely cancelled his trade offer
 		if (atData.getIsFinished()) {
 			tradeBotData.setState(TradeBotData.State.BOB_REFUNDED);
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 
@@ -563,6 +567,7 @@ public class TradeBot {
 			}
 
 			tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_P2SH_B);
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 
@@ -577,6 +582,7 @@ public class TradeBot {
 
 		// Don't resave if we don't need to
 		if (tradeBotData.getLastTransactionSignature() != originalLastTransactionSignature) {
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 			notifyStateChange(tradeBotData);
@@ -608,6 +614,7 @@ public class TradeBot {
 		// Refund P2SH-A if AT finished (i.e. Bob cancelled trade) or we've passed lockTime-A
 		if (atData.getIsFinished() || NTP.getTime() >= tradeBotData.getLockTimeA() * 1000L) {
 			tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 
@@ -643,6 +650,7 @@ public class TradeBot {
 
 			// There's no P2SH-B at this point, so jump straight to refunding P2SH-A
 			tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 
@@ -700,6 +708,7 @@ public class TradeBot {
 
 		// P2SH-B funded, now we wait for Bob to redeem it
 		tradeBotData.setState(TradeBotData.State.ALICE_WATCH_P2SH_B);
+		tradeBotData.setTimestamp(NTP.getTime());
 		repository.getCrossChainRepository().save(tradeBotData);
 		repository.saveChanges();
 
@@ -730,6 +739,7 @@ public class TradeBot {
 		// If we've passed AT refund timestamp then AT will have finished after auto-refunding
 		if (atData.getIsFinished()) {
 			tradeBotData.setState(TradeBotData.State.BOB_REFUNDED);
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 
@@ -771,6 +781,7 @@ public class TradeBot {
 
 		// P2SH-B redeemed, now we wait for Alice to use secret-A to redeem AT
 		tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_AT_REDEEM);
+		tradeBotData.setTimestamp(NTP.getTime());
 		repository.getCrossChainRepository().save(tradeBotData);
 		repository.saveChanges();
 
@@ -808,6 +819,7 @@ public class TradeBot {
 		// Refund P2SH-B if we've passed lockTime-B
 		if (NTP.getTime() >= crossChainTradeData.lockTimeB * 1000L) {
 			tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_B);
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 
@@ -849,6 +861,7 @@ public class TradeBot {
 		}
 
 		tradeBotData.setState(TradeBotData.State.ALICE_DONE);
+		tradeBotData.setTimestamp(NTP.getTime());
 		repository.getCrossChainRepository().save(tradeBotData);
 		repository.saveChanges();
 
@@ -896,6 +909,7 @@ public class TradeBot {
 		// We check variable in AT that is set when trade successfully completes
 		if (crossChainTradeData.mode != BTCACCT.Mode.REDEEMED) {
 			tradeBotData.setState(TradeBotData.State.BOB_REFUNDED);
+			tradeBotData.setTimestamp(NTP.getTime());
 			repository.getCrossChainRepository().save(tradeBotData);
 			repository.saveChanges();
 
@@ -930,6 +944,7 @@ public class TradeBot {
 		}
 
 		tradeBotData.setState(TradeBotData.State.BOB_DONE);
+		tradeBotData.setTimestamp(NTP.getTime());
 		repository.getCrossChainRepository().save(tradeBotData);
 		repository.saveChanges();
 
@@ -973,7 +988,7 @@ public class TradeBot {
 		}
 
 		tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A);
-
+		tradeBotData.setTimestamp(NTP.getTime());
 		repository.getCrossChainRepository().save(tradeBotData);
 		repository.saveChanges();
 
@@ -1018,7 +1033,7 @@ public class TradeBot {
 		}
 
 		tradeBotData.setState(TradeBotData.State.ALICE_REFUNDED);
-
+		tradeBotData.setTimestamp(NTP.getTime());
 		repository.getCrossChainRepository().save(tradeBotData);
 		repository.saveChanges();
 
diff --git a/src/main/java/org/qortal/data/crosschain/TradeBotData.java b/src/main/java/org/qortal/data/crosschain/TradeBotData.java
index 6544999b..0f57845d 100644
--- a/src/main/java/org/qortal/data/crosschain/TradeBotData.java
+++ b/src/main/java/org/qortal/data/crosschain/TradeBotData.java
@@ -35,8 +35,14 @@ public class TradeBotData {
 	}
 	private State tradeState;
 
+	private String creatorAddress;
 	private String atAddress;
 
+	private long timestamp;
+
+	@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
+	private long qortAmount;
+
 	private byte[] tradeNativePublicKey;
 	private byte[] tradeNativePublicKeyHash;
 	String tradeNativeAddress;
@@ -66,14 +72,18 @@ public class TradeBotData {
 		/* JAXB */
 	}
 
-	public TradeBotData(byte[] tradePrivateKey, State tradeState, String atAddress,
+	public TradeBotData(byte[] tradePrivateKey, State tradeState, String creatorAddress, String atAddress,
+			long timestamp, long qortAmount,
 			byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, String tradeNativeAddress,
 			byte[] secret, byte[] hashOfSecret,
 			byte[] tradeForeignPublicKey, byte[] tradeForeignPublicKeyHash,
 			long bitcoinAmount, String xprv58, byte[] lastTransactionSignature, Integer lockTimeA, byte[] receivingAccountInfo) {
 		this.tradePrivateKey = tradePrivateKey;
 		this.tradeState = tradeState;
+		this.creatorAddress = creatorAddress;
 		this.atAddress = atAddress;
+		this.timestamp = timestamp;
+		this.qortAmount = qortAmount;
 		this.tradeNativePublicKey = tradeNativePublicKey;
 		this.tradeNativePublicKeyHash = tradeNativePublicKeyHash;
 		this.tradeNativeAddress = tradeNativeAddress;
@@ -100,6 +110,10 @@ public class TradeBotData {
 		this.tradeState = state;
 	}
 
+	public String getCreatorAddress() {
+		return this.creatorAddress;
+	}
+
 	public String getAtAddress() {
 		return this.atAddress;
 	}
@@ -108,6 +122,18 @@ public class TradeBotData {
 		this.atAddress = atAddress;
 	}
 
+	public long getTimestamp() {
+		return this.timestamp;
+	}
+
+	public void setTimestamp(long timestamp) {
+		this.timestamp = timestamp;
+	}
+
+	public long getQortAmount() {
+		return this.qortAmount;
+	}
+
 	public byte[] getTradeNativePublicKey() {
 		return this.tradeNativePublicKey;
 	}
diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java
index 0eb1ef00..589ca0a4 100644
--- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java
+++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java
@@ -19,7 +19,8 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
 
 	@Override
 	public TradeBotData getTradeBotData(byte[] tradePrivateKey) throws DataException {
-		String sql = "SELECT trade_state, at_address, "
+		String sql = "SELECT trade_state, creator_address, at_address, "
+				+ "updated_when, qort_amount, "
 				+ "trade_native_public_key, trade_native_public_key_hash, "
 				+ "trade_native_address, secret, hash_of_secret, "
 				+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
@@ -36,24 +37,27 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
 			if (tradeState == null)
 				throw new DataException("Illegal trade-bot trade-state fetched from repository");
 
-			String atAddress = resultSet.getString(2);
-			byte[] tradeNativePublicKey = resultSet.getBytes(3);
-			byte[] tradeNativePublicKeyHash = resultSet.getBytes(4);
-			String tradeNativeAddress = resultSet.getString(5);
-			byte[] secret = resultSet.getBytes(6);
-			byte[] hashOfSecret = resultSet.getBytes(7);
-			byte[] tradeForeignPublicKey = resultSet.getBytes(8);
-			byte[] tradeForeignPublicKeyHash = resultSet.getBytes(9);
-			long bitcoinAmount = resultSet.getLong(10);
-			String xprv58 = resultSet.getString(11);
-			byte[] lastTransactionSignature = resultSet.getBytes(12);
-			Integer lockTimeA = resultSet.getInt(13);
+			String creatorAddress = resultSet.getString(2);
+			String atAddress = resultSet.getString(3);
+			long timestamp = resultSet.getLong(4);
+			long qortAmount = resultSet.getLong(5);
+			byte[] tradeNativePublicKey = resultSet.getBytes(6);
+			byte[] tradeNativePublicKeyHash = resultSet.getBytes(7);
+			String tradeNativeAddress = resultSet.getString(8);
+			byte[] secret = resultSet.getBytes(9);
+			byte[] hashOfSecret = resultSet.getBytes(10);
+			byte[] tradeForeignPublicKey = resultSet.getBytes(11);
+			byte[] tradeForeignPublicKeyHash = resultSet.getBytes(12);
+			long bitcoinAmount = resultSet.getLong(13);
+			String xprv58 = resultSet.getString(14);
+			byte[] lastTransactionSignature = resultSet.getBytes(15);
+			Integer lockTimeA = resultSet.getInt(16);
 			if (lockTimeA == 0 && resultSet.wasNull())
 				lockTimeA = null;
-			byte[] receivingAccountInfo = resultSet.getBytes(14);
+			byte[] receivingAccountInfo = resultSet.getBytes(17);
 
 			return new TradeBotData(tradePrivateKey, tradeState,
-					atAddress,
+					creatorAddress, atAddress, timestamp, qortAmount,
 					tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
 					secret, hashOfSecret,
 					tradeForeignPublicKey, tradeForeignPublicKeyHash,
@@ -65,7 +69,8 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
 
 	@Override
 	public List<TradeBotData> getAllTradeBotData() throws DataException {
-		String sql = "SELECT trade_private_key, trade_state, at_address, "
+		String sql = "SELECT trade_private_key, trade_state, creator_address, at_address, "
+				+ "updated_when, qort_amount, "
 				+ "trade_native_public_key, trade_native_public_key_hash, "
 				+ "trade_native_address, secret, hash_of_secret, "
 				+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
@@ -85,24 +90,27 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
 				if (tradeState == null)
 					throw new DataException("Illegal trade-bot trade-state fetched from repository");
 
-				String atAddress = resultSet.getString(3);
-				byte[] tradeNativePublicKey = resultSet.getBytes(4);
-				byte[] tradeNativePublicKeyHash = resultSet.getBytes(5);
-				String tradeNativeAddress = resultSet.getString(6);
-				byte[] secret = resultSet.getBytes(7);
-				byte[] hashOfSecret = resultSet.getBytes(8);
-				byte[] tradeForeignPublicKey = resultSet.getBytes(9);
-				byte[] tradeForeignPublicKeyHash = resultSet.getBytes(10);
-				long bitcoinAmount = resultSet.getLong(11);
-				String xprv58 = resultSet.getString(12);
-				byte[] lastTransactionSignature = resultSet.getBytes(13);
-				Integer lockTimeA = resultSet.getInt(14);
+				String creatorAddress = resultSet.getString(3);
+				String atAddress = resultSet.getString(4);
+				long timestamp = resultSet.getLong(5);
+				long qortAmount = resultSet.getLong(6);
+				byte[] tradeNativePublicKey = resultSet.getBytes(7);
+				byte[] tradeNativePublicKeyHash = resultSet.getBytes(8);
+				String tradeNativeAddress = resultSet.getString(9);
+				byte[] secret = resultSet.getBytes(10);
+				byte[] hashOfSecret = resultSet.getBytes(11);
+				byte[] tradeForeignPublicKey = resultSet.getBytes(12);
+				byte[] tradeForeignPublicKeyHash = resultSet.getBytes(13);
+				long bitcoinAmount = resultSet.getLong(14);
+				String xprv58 = resultSet.getString(15);
+				byte[] lastTransactionSignature = resultSet.getBytes(16);
+				Integer lockTimeA = resultSet.getInt(17);
 				if (lockTimeA == 0 && resultSet.wasNull())
 					lockTimeA = null;
-				byte[] receivingAccountInfo = resultSet.getBytes(15);
+				byte[] receivingAccountInfo = resultSet.getBytes(18);
 
 				TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState,
-						atAddress,
+						creatorAddress, atAddress, timestamp, qortAmount,
 						tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
 						secret, hashOfSecret,
 						tradeForeignPublicKey, tradeForeignPublicKeyHash,
@@ -122,7 +130,10 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
 
 		saveHelper.bind("trade_private_key", tradeBotData.getTradePrivateKey())
 				.bind("trade_state", tradeBotData.getState().value)
+				.bind("creator_address", tradeBotData.getCreatorAddress())
 				.bind("at_address", tradeBotData.getAtAddress())
+				.bind("updated_when", tradeBotData.getTimestamp())
+				.bind("qort_amount", tradeBotData.getQortAmount())
 				.bind("trade_native_public_key", tradeBotData.getTradeNativePublicKey())
 				.bind("trade_native_public_key_hash", tradeBotData.getTradeNativePublicKeyHash())
 				.bind("trade_native_address", tradeBotData.getTradeNativeAddress())
diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java
index 2706bb5d..4c4d5274 100644
--- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java
+++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java
@@ -4,9 +4,14 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.qortal.utils.Base58;
+
+import com.google.common.hash.HashCode;
 
 public class HSQLDBDatabaseUpdates {
 
@@ -621,7 +626,7 @@ public class HSQLDBDatabaseUpdates {
 				case 20:
 					// Trade bot
 					stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalKeySeed NOT NULL, trade_state TINYINT NOT NULL, "
-							+ "at_address QortalAddress, "
+							+ "creator_address QortalAddress NOT NULL, at_address QortalAddress, updated_when BIGINT NOT NULL, qort_amount QortalAmount NOT NULL, "
 							+ "trade_native_public_key QortalPublicKey NOT NULL, trade_native_public_key_hash VARBINARY(32) NOT NULL, "
 							+ "trade_native_address QortalAddress NOT NULL, secret VARBINARY(32) NOT NULL, hash_of_secret VARBINARY(32) NOT NULL, "
 							+ "trade_foreign_public_key VARBINARY(33) NOT NULL, trade_foreign_public_key_hash VARBINARY(32) NOT NULL, "
@@ -641,6 +646,39 @@ public class HSQLDBDatabaseUpdates {
 					stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS receiving_account_info VARBINARY(32)");
 					break;
 
+				case 23:
+					// XXX for testing/dev only - do not merge into 'master'
+					stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS creator_address QortalAddress BEFORE at_address");
+					// Update Bob bot entries
+					stmt.execute("UPDATE TradeBotStates AS StatesToUpdate "
+							+ "SET (trade_private_key, creator_address) = ("
+								+ "SELECT trade_private_key, accounts.account "
+								+ "FROM TradeBotStates "
+								+ "JOIN ATs USING (at_address) "
+								+ "JOIN Accounts ON Accounts.public_key = ATs.creator "
+								+ "WHERE tradebotstates.trade_private_key = StatesToUpdate.trade_private_key"
+							+ ") WHERE trade_state < 90");
+
+					stmt.execute("SELECT trade_private_key, receiving_account_info FROM TradeBotStates WHERE trade_state >= 90");
+					Map<String, String> aliceAddresses = new HashMap<>();
+					try (ResultSet resultSet = stmt.getResultSet()) {
+						while (resultSet.next()) {
+							byte[] tradePrivateKey = resultSet.getBytes(1);
+							byte[] receivingAccountInfo = resultSet.getBytes(2);
+
+							aliceAddresses.put(HashCode.fromBytes(tradePrivateKey).toString(), Base58.encode(receivingAccountInfo));
+						}
+					}
+					for (Map.Entry<String, String> entry : aliceAddresses.entrySet())
+						stmt.execute("UPDATE TradeBotStates SET creator_address = '" + entry.getValue() + "' WHERE trade_private_key = HEXTORAW('" + entry.getKey() + "')");
+					stmt.execute("COMMIT");
+
+					stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS updated_when BIGINT BEFORE trade_native_public_key");
+					stmt.execute("UPDATE TradeBotStates SET updated_when = UNIX_TIMESTAMP() * 1000");
+
+					stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS qort_amount QortalAmount NOT NULL DEFAULT 12345678 BEFORE trade_native_public_key");
+					break;
+
 				default:
 					// nothing to do
 					return false;