From 0fc17d76aea4114a5cb77052bcc55bb23a6ec549 Mon Sep 17 00:00:00 2001
From: catbref <misc-github@talk2dom.com>
Date: Mon, 2 Jul 2018 18:09:36 +0100
Subject: [PATCH] Added SellNameTransactions + tests

---
 .../transaction/SellNameTransactionData.java  |  42 +++++
 src/qora/naming/Name.java                     |  19 +++
 src/qora/transaction/SellNameTransaction.java | 151 ++++++++++++++++++
 src/qora/transaction/Transaction.java         |   5 +-
 .../HSQLDBSellNameTransactionRepository.java  |  50 ++++++
 .../HSQLDBTransactionRepository.java          |  13 +-
 src/test/LoadTests.java                       |   5 +-
 src/test/SerializationTests.java              |   5 +
 src/test/TransactionTests.java                |  49 +++++-
 .../SellNameTransactionTransformer.java       | 111 +++++++++++++
 .../transaction/TransactionTransformer.java   |  20 ++-
 11 files changed, 461 insertions(+), 9 deletions(-)
 create mode 100644 src/data/transaction/SellNameTransactionData.java
 create mode 100644 src/qora/transaction/SellNameTransaction.java
 create mode 100644 src/repository/hsqldb/transaction/HSQLDBSellNameTransactionRepository.java
 create mode 100644 src/transform/transaction/SellNameTransactionTransformer.java

diff --git a/src/data/transaction/SellNameTransactionData.java b/src/data/transaction/SellNameTransactionData.java
new file mode 100644
index 00000000..f934def9
--- /dev/null
+++ b/src/data/transaction/SellNameTransactionData.java
@@ -0,0 +1,42 @@
+package data.transaction;
+
+import java.math.BigDecimal;
+
+import qora.transaction.Transaction.TransactionType;
+
+public class SellNameTransactionData extends TransactionData {
+
+	// Properties
+	private byte[] ownerPublicKey;
+	private String name;
+	private BigDecimal amount;
+
+	// Constructors
+
+	public SellNameTransactionData(byte[] ownerPublicKey, String name, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
+		super(TransactionType.SELL_NAME, fee, ownerPublicKey, timestamp, reference, signature);
+
+		this.ownerPublicKey = ownerPublicKey;
+		this.name = name;
+		this.amount = amount;
+	}
+
+	public SellNameTransactionData(byte[] ownerPublicKey, String name, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference) {
+		this(ownerPublicKey, name, amount, fee, timestamp, reference, null);
+	}
+
+	// Getters / setters
+
+	public byte[] getOwnerPublicKey() {
+		return this.ownerPublicKey;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public BigDecimal getAmount() {
+		return this.amount;
+	}
+
+}
diff --git a/src/qora/naming/Name.java b/src/qora/naming/Name.java
index 7cc837fe..45ee75ec 100644
--- a/src/qora/naming/Name.java
+++ b/src/qora/naming/Name.java
@@ -2,6 +2,7 @@ package qora.naming;
 
 import data.naming.NameData;
 import data.transaction.RegisterNameTransactionData;
+import data.transaction.SellNameTransactionData;
 import data.transaction.TransactionData;
 import data.transaction.UpdateNameTransactionData;
 import repository.DataException;
@@ -99,4 +100,22 @@ public class Name {
 		this.repository.getNameRepository().save(this.nameData);
 	}
 
+	public void sell(SellNameTransactionData sellNameTransactionData) throws DataException {
+		// Mark as for-sale and set price
+		this.nameData.setIsForSale(true);
+		this.nameData.setSalePrice(sellNameTransactionData.getAmount());
+
+		// Save sale info into repository
+		this.repository.getNameRepository().save(this.nameData);
+	}
+
+	public void unsell(SellNameTransactionData sellNameTransactionData) throws DataException {
+		// Mark not for-sale and unset price
+		this.nameData.setIsForSale(false);
+		this.nameData.setSalePrice(null);
+
+		// Save no-sale info into repository
+		this.repository.getNameRepository().save(this.nameData);
+	}
+
 }
diff --git a/src/qora/transaction/SellNameTransaction.java b/src/qora/transaction/SellNameTransaction.java
new file mode 100644
index 00000000..2498f9bd
--- /dev/null
+++ b/src/qora/transaction/SellNameTransaction.java
@@ -0,0 +1,151 @@
+package qora.transaction;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import data.naming.NameData;
+import data.transaction.SellNameTransactionData;
+import data.transaction.TransactionData;
+import qora.account.Account;
+import qora.account.PublicKeyAccount;
+import qora.assets.Asset;
+import qora.block.BlockChain;
+import qora.naming.Name;
+import repository.DataException;
+import repository.Repository;
+
+public class SellNameTransaction extends Transaction {
+
+	// Properties
+	private SellNameTransactionData sellNameTransactionData;
+
+	// Constructors
+
+	public SellNameTransaction(Repository repository, TransactionData transactionData) {
+		super(repository, transactionData);
+
+		this.sellNameTransactionData = (SellNameTransactionData) this.transactionData;
+	}
+
+	// More information
+
+	@Override
+	public List<Account> getRecipientAccounts() {
+		return new ArrayList<Account>();
+	}
+
+	@Override
+	public boolean isInvolved(Account account) throws DataException {
+		String address = account.getAddress();
+
+		if (address.equals(this.getOwner().getAddress()))
+			return true;
+
+		return false;
+	}
+
+	@Override
+	public BigDecimal getAmount(Account account) throws DataException {
+		String address = account.getAddress();
+		BigDecimal amount = BigDecimal.ZERO.setScale(8);
+
+		if (address.equals(this.getOwner().getAddress()))
+			amount = amount.subtract(this.transactionData.getFee());
+
+		return amount;
+	}
+
+	// Navigation
+
+	public Account getOwner() throws DataException {
+		return new PublicKeyAccount(this.repository, this.sellNameTransactionData.getOwnerPublicKey());
+	}
+
+	// Processing
+
+	@Override
+	public ValidationResult isValid() throws DataException {
+		// Check name size bounds
+		if (sellNameTransactionData.getName().length() < 1 || sellNameTransactionData.getName().length() > Name.MAX_NAME_SIZE)
+			return ValidationResult.INVALID_NAME_LENGTH;
+
+		// Check name is lowercase
+		if (!sellNameTransactionData.getName().equals(sellNameTransactionData.getName().toLowerCase()))
+			return ValidationResult.NAME_NOT_LOWER_CASE;
+
+		NameData nameData = this.repository.getNameRepository().fromName(sellNameTransactionData.getName());
+
+		// Check name exists
+		if (nameData == null)
+			return ValidationResult.NAME_DOES_NOT_EXIST;
+
+		// Check name isn't currently for sale
+		if (nameData.getIsForSale())
+			return ValidationResult.NAME_ALREADY_FOR_SALE;
+
+		// Check transaction's public key matches name's current owner
+		Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
+		if (!owner.getAddress().equals(nameData.getOwner()))
+			return ValidationResult.INVALID_NAME_OWNER;
+
+		// Check amount is positive
+		if (sellNameTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
+			return ValidationResult.NEGATIVE_AMOUNT;
+
+		// Check amount within bounds
+		if (sellNameTransactionData.getAmount().compareTo(BlockChain.MAX_BALANCE) > 0)
+			return ValidationResult.INVALID_AMOUNT;
+
+		// Check fee is positive
+		if (sellNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
+			return ValidationResult.NEGATIVE_FEE;
+
+		// Check reference is correct
+		if (!Arrays.equals(owner.getLastReference(), sellNameTransactionData.getReference()))
+			return ValidationResult.INVALID_REFERENCE;
+
+		// Check issuer has enough funds
+		if (owner.getConfirmedBalance(Asset.QORA).compareTo(sellNameTransactionData.getFee()) == -1)
+			return ValidationResult.NO_BALANCE;
+
+		return ValidationResult.OK;
+
+	}
+
+	@Override
+	public void process() throws DataException {
+		// Update Name
+		Name name = new Name(this.repository, sellNameTransactionData.getName());
+		name.sell(sellNameTransactionData);
+
+		// Save this transaction, now with updated "name reference" to previous transaction that updated name
+		this.repository.getTransactionRepository().save(sellNameTransactionData);
+
+		// Update owner's balance
+		Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
+		owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(sellNameTransactionData.getFee()));
+
+		// Update owner's reference
+		owner.setLastReference(sellNameTransactionData.getSignature());
+	}
+
+	@Override
+	public void orphan() throws DataException {
+		// Revert name
+		Name name = new Name(this.repository, sellNameTransactionData.getName());
+		name.unsell(sellNameTransactionData);
+
+		// Delete this transaction itself
+		this.repository.getTransactionRepository().delete(sellNameTransactionData);
+
+		// Update owner's balance
+		Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
+		owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).add(sellNameTransactionData.getFee()));
+
+		// Update owner's reference
+		owner.setLastReference(sellNameTransactionData.getReference());
+	}
+
+}
diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java
index daedae62..9fe0b541 100644
--- a/src/qora/transaction/Transaction.java
+++ b/src/qora/transaction/Transaction.java
@@ -111,6 +111,9 @@ public abstract class Transaction {
 			case UPDATE_NAME:
 				return new UpdateNameTransaction(repository, transactionData);
 
+			case SELL_NAME:
+				return new SellNameTransaction(repository, transactionData);
+
 			case CREATE_POLL:
 				return new CreatePollTransaction(repository, transactionData);
 
@@ -136,7 +139,7 @@ public abstract class Transaction {
 				return new MessageTransaction(repository, transactionData);
 
 			default:
-				return null;
+				throw new IllegalStateException("Unsupported transaction type [" + transactionData.getType().value + "] during fetch from repository");
 		}
 	}
 
diff --git a/src/repository/hsqldb/transaction/HSQLDBSellNameTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBSellNameTransactionRepository.java
new file mode 100644
index 00000000..7669f0da
--- /dev/null
+++ b/src/repository/hsqldb/transaction/HSQLDBSellNameTransactionRepository.java
@@ -0,0 +1,50 @@
+package repository.hsqldb.transaction;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import data.transaction.SellNameTransactionData;
+import data.transaction.TransactionData;
+import repository.DataException;
+import repository.hsqldb.HSQLDBRepository;
+import repository.hsqldb.HSQLDBSaver;
+
+public class HSQLDBSellNameTransactionRepository extends HSQLDBTransactionRepository {
+
+	public HSQLDBSellNameTransactionRepository(HSQLDBRepository repository) {
+		this.repository = repository;
+	}
+
+	TransactionData fromBase(byte[] signature, byte[] reference, byte[] ownerPublicKey, long timestamp, BigDecimal fee) throws DataException {
+		try {
+			ResultSet rs = this.repository.checkedExecute("SELECT name, amount FROM SellNameTransactions WHERE signature = ?", signature);
+			if (rs == null)
+				return null;
+
+			String name = rs.getString(1);
+			BigDecimal amount = rs.getBigDecimal(2);
+
+			return new SellNameTransactionData(ownerPublicKey, name, amount, fee, timestamp, reference, signature);
+		} catch (SQLException e) {
+			throw new DataException("Unable to fetch sell name transaction from repository", e);
+		}
+	}
+
+	@Override
+	public void save(TransactionData transactionData) throws DataException {
+		SellNameTransactionData sellNameTransactionData = (SellNameTransactionData) transactionData;
+
+		HSQLDBSaver saveHelper = new HSQLDBSaver("SellNameTransactions");
+
+		saveHelper.bind("signature", sellNameTransactionData.getSignature()).bind("owner", sellNameTransactionData.getOwnerPublicKey())
+				.bind("name", sellNameTransactionData.getName()).bind("amount", sellNameTransactionData.getAmount());
+
+		try {
+			saveHelper.execute(this.repository);
+		} catch (SQLException e) {
+			throw new DataException("Unable to save sell name transaction into repository", e);
+		}
+	}
+
+}
diff --git a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java
index ff1c59d6..92dcca7b 100644
--- a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java
+++ b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java
@@ -23,6 +23,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
 	private HSQLDBPaymentTransactionRepository paymentTransactionRepository;
 	private HSQLDBRegisterNameTransactionRepository registerNameTransactionRepository;
 	private HSQLDBUpdateNameTransactionRepository updateNameTransactionRepository;
+	private HSQLDBSellNameTransactionRepository sellNameTransactionRepository;
 	private HSQLDBCreatePollTransactionRepository createPollTransactionRepository;
 	private HSQLDBVoteOnPollTransactionRepository voteOnPollTransactionRepository;
 	private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
@@ -38,6 +39,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
 		this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository);
 		this.registerNameTransactionRepository = new HSQLDBRegisterNameTransactionRepository(repository);
 		this.updateNameTransactionRepository = new HSQLDBUpdateNameTransactionRepository(repository);
+		this.sellNameTransactionRepository = new HSQLDBSellNameTransactionRepository(repository);
 		this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository);
 		this.voteOnPollTransactionRepository = new HSQLDBVoteOnPollTransactionRepository(repository);
 		this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
@@ -102,6 +104,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
 			case UPDATE_NAME:
 				return this.updateNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
 
+			case SELL_NAME:
+				return this.sellNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
+
 			case CREATE_POLL:
 				return this.createPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
 
@@ -127,7 +132,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
 				return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
 
 			default:
-				return null;
+				throw new DataException("Unsupported transaction type [" + type.value + "] during fetch from HSQLDB repository");
 		}
 	}
 
@@ -238,6 +243,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
 				this.updateNameTransactionRepository.save(transactionData);
 				break;
 
+			case SELL_NAME:
+				this.sellNameTransactionRepository.save(transactionData);
+				break;
+
 			case CREATE_POLL:
 				this.createPollTransactionRepository.save(transactionData);
 				break;
@@ -271,7 +280,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
 				break;
 
 			default:
-				throw new DataException("Unsupported transaction type during save into repository");
+				throw new DataException("Unsupported transaction type [" + transactionData.getType().value + "] during save into HSQLDB repository");
 		}
 	}
 
diff --git a/src/test/LoadTests.java b/src/test/LoadTests.java
index 1b40ac75..aa617985 100644
--- a/src/test/LoadTests.java
+++ b/src/test/LoadTests.java
@@ -59,11 +59,14 @@ public class LoadTests extends Common {
 				if (transactionData == null)
 					break;
 
+				if (transactionData.getType() != TransactionType.PAYMENT)
+					break;
+
 				PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
 				System.out.println(PublicKeyAccount.getAddress(paymentTransactionData.getSenderPublicKey()) + " sent " + paymentTransactionData.getAmount()
 						+ " QORA to " + paymentTransactionData.getRecipient());
 
-				signature = paymentTransactionData.getReference();
+				signature = transactionData.getReference();
 			}
 		}
 	}
diff --git a/src/test/SerializationTests.java b/src/test/SerializationTests.java
index d27857ff..0fd4080c 100644
--- a/src/test/SerializationTests.java
+++ b/src/test/SerializationTests.java
@@ -99,6 +99,11 @@ public class SerializationTests extends Common {
 		testSpecificBlockTransactions(673, TransactionType.UPDATE_NAME);
 	}
 
+	@Test
+	public void testSellNameSerialization() throws TransformationException, DataException {
+		testSpecificBlockTransactions(673, TransactionType.SELL_NAME);
+	}
+
 	@Test
 	public void testCreatePollSerialization() throws TransformationException, DataException {
 		// Block 10537 has only create poll transactions
diff --git a/src/test/TransactionTests.java b/src/test/TransactionTests.java
index a20bbb02..b22679ac 100644
--- a/src/test/TransactionTests.java
+++ b/src/test/TransactionTests.java
@@ -19,6 +19,7 @@ import data.naming.NameData;
 import data.transaction.CreatePollTransactionData;
 import data.transaction.PaymentTransactionData;
 import data.transaction.RegisterNameTransactionData;
+import data.transaction.SellNameTransactionData;
 import data.transaction.UpdateNameTransactionData;
 import data.transaction.VoteOnPollTransactionData;
 import data.voting.PollData;
@@ -33,6 +34,7 @@ import qora.block.BlockChain;
 import qora.transaction.CreatePollTransaction;
 import qora.transaction.PaymentTransaction;
 import qora.transaction.RegisterNameTransaction;
+import qora.transaction.SellNameTransaction;
 import qora.transaction.Transaction;
 import qora.transaction.Transaction.ValidationResult;
 import qora.transaction.UpdateNameTransaction;
@@ -208,7 +210,7 @@ public class TransactionTests {
 	}
 
 	@Test
-	public void testUpdateNamesTransaction() throws DataException {
+	public void testUpdateNameTransaction() throws DataException {
 		// Register name using another test
 		testRegisterNameTransaction();
 
@@ -256,6 +258,51 @@ public class TransactionTests {
 		assertEquals(originalNameData.getData(), actualNameData.getData());
 	}
 
+	@Test
+	public void testSellNameTransaction() throws DataException {
+		// Register name using another test
+		testRegisterNameTransaction();
+
+		String name = "test name";
+
+		// Sale price
+		BigDecimal amount = BigDecimal.valueOf(1234L).setScale(8);
+
+		BigDecimal fee = BigDecimal.ONE;
+		long timestamp = parentBlockData.getTimestamp() + 2_000;
+		SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(sender.getPublicKey(), name, amount, fee, timestamp, reference);
+
+		Transaction sellNameTransaction = new SellNameTransaction(repository, sellNameTransactionData);
+		sellNameTransaction.calcSignature(sender);
+		assertTrue(sellNameTransaction.isSignatureValid());
+		assertEquals(ValidationResult.OK, sellNameTransaction.isValid());
+
+		// Forge new block with transaction
+		Block block = new Block(repository, parentBlockData, generator, null, null);
+		block.addTransaction(sellNameTransactionData);
+		block.sign();
+
+		assertTrue("Block signatures invalid", block.isSignatureValid());
+		assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
+
+		block.process();
+		repository.saveChanges();
+
+		// Check name was updated
+		NameData actualNameData = this.repository.getNameRepository().fromName(name);
+		assertTrue(actualNameData.getIsForSale());
+		assertEquals(amount, actualNameData.getSalePrice());
+
+		// Now orphan block
+		block.orphan();
+		repository.saveChanges();
+
+		// Check name has been reverted correctly
+		actualNameData = this.repository.getNameRepository().fromName(name);
+		assertFalse(actualNameData.getIsForSale());
+		assertNull(actualNameData.getSalePrice());
+	}
+
 	@Test
 	public void testCreatePollTransaction() throws DataException {
 		// This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP
diff --git a/src/transform/transaction/SellNameTransactionTransformer.java b/src/transform/transaction/SellNameTransactionTransformer.java
new file mode 100644
index 00000000..2b070beb
--- /dev/null
+++ b/src/transform/transaction/SellNameTransactionTransformer.java
@@ -0,0 +1,111 @@
+package transform.transaction;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+
+import org.json.simple.JSONObject;
+
+import com.google.common.hash.HashCode;
+import com.google.common.primitives.Ints;
+import com.google.common.primitives.Longs;
+
+import data.transaction.SellNameTransactionData;
+import data.transaction.TransactionData;
+import qora.account.PublicKeyAccount;
+import qora.naming.Name;
+import transform.TransformationException;
+import utils.Serialization;
+
+public class SellNameTransactionTransformer extends TransactionTransformer {
+
+	// Property lengths
+	private static final int OWNER_LENGTH = PUBLIC_KEY_LENGTH;
+	private static final int NAME_SIZE_LENGTH = INT_LENGTH;
+	private static final int AMOUNT_LENGTH = BIG_DECIMAL_LENGTH;
+
+	private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + AMOUNT_LENGTH;
+
+	static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
+		if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
+			throw new TransformationException("Byte data too short for SellNameTransaction");
+
+		long timestamp = byteBuffer.getLong();
+
+		byte[] reference = new byte[REFERENCE_LENGTH];
+		byteBuffer.get(reference);
+
+		byte[] ownerPublicKey = Serialization.deserializePublicKey(byteBuffer);
+
+		String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
+
+		// Still need to make sure there are enough bytes left for remaining fields
+		if (byteBuffer.remaining() < AMOUNT_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH)
+			throw new TransformationException("Byte data too short for SellNameTransaction");
+
+		BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
+
+		BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
+
+		byte[] signature = new byte[SIGNATURE_LENGTH];
+		byteBuffer.get(signature);
+
+		return new SellNameTransactionData(ownerPublicKey, name, amount, fee, timestamp, reference, signature);
+	}
+
+	public static int getDataLength(TransactionData transactionData) throws TransformationException {
+		SellNameTransactionData sellNameTransactionData = (SellNameTransactionData) transactionData;
+
+		int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + sellNameTransactionData.getName().length();
+
+		return dataLength;
+	}
+
+	public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
+		try {
+			SellNameTransactionData sellNameTransactionData = (SellNameTransactionData) transactionData;
+
+			ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+
+			bytes.write(Ints.toByteArray(sellNameTransactionData.getType().value));
+			bytes.write(Longs.toByteArray(sellNameTransactionData.getTimestamp()));
+			bytes.write(sellNameTransactionData.getReference());
+
+			bytes.write(sellNameTransactionData.getOwnerPublicKey());
+			Serialization.serializeSizedString(bytes, sellNameTransactionData.getName());
+			Serialization.serializeBigDecimal(bytes, sellNameTransactionData.getAmount());
+
+			Serialization.serializeBigDecimal(bytes, sellNameTransactionData.getFee());
+
+			if (sellNameTransactionData.getSignature() != null)
+				bytes.write(sellNameTransactionData.getSignature());
+
+			return bytes.toByteArray();
+		} catch (IOException | ClassCastException e) {
+			throw new TransformationException(e);
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
+		JSONObject json = TransactionTransformer.getBaseJSON(transactionData);
+
+		try {
+			SellNameTransactionData sellNameTransactionData = (SellNameTransactionData) transactionData;
+
+			byte[] ownerPublicKey = sellNameTransactionData.getOwnerPublicKey();
+
+			json.put("owner", PublicKeyAccount.getAddress(ownerPublicKey));
+			json.put("ownerPublicKey", HashCode.fromBytes(ownerPublicKey).toString());
+
+			json.put("name", sellNameTransactionData.getName());
+			json.put("amount", sellNameTransactionData.getAmount().toPlainString());
+		} catch (ClassCastException e) {
+			throw new TransformationException(e);
+		}
+
+		return json;
+	}
+
+}
diff --git a/src/transform/transaction/TransactionTransformer.java b/src/transform/transaction/TransactionTransformer.java
index d21cbcb1..bcd643a5 100644
--- a/src/transform/transaction/TransactionTransformer.java
+++ b/src/transform/transaction/TransactionTransformer.java
@@ -43,6 +43,9 @@ public class TransactionTransformer extends Transformer {
 			case UPDATE_NAME:
 				return UpdateNameTransactionTransformer.fromByteBuffer(byteBuffer);
 
+			case SELL_NAME:
+				return SellNameTransactionTransformer.fromByteBuffer(byteBuffer);
+
 			case CREATE_POLL:
 				return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
 
@@ -68,7 +71,7 @@ public class TransactionTransformer extends Transformer {
 				return MessageTransactionTransformer.fromByteBuffer(byteBuffer);
 
 			default:
-				throw new TransformationException("Unsupported transaction type");
+				throw new TransformationException("Unsupported transaction type [" + type.value + "] during conversion from bytes");
 		}
 	}
 
@@ -86,6 +89,9 @@ public class TransactionTransformer extends Transformer {
 			case UPDATE_NAME:
 				return UpdateNameTransactionTransformer.getDataLength(transactionData);
 
+			case SELL_NAME:
+				return SellNameTransactionTransformer.getDataLength(transactionData);
+
 			case CREATE_POLL:
 				return CreatePollTransactionTransformer.getDataLength(transactionData);
 
@@ -111,7 +117,7 @@ public class TransactionTransformer extends Transformer {
 				return MessageTransactionTransformer.getDataLength(transactionData);
 
 			default:
-				throw new TransformationException("Unsupported transaction type");
+				throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] when requesting byte length");
 		}
 	}
 
@@ -129,6 +135,9 @@ public class TransactionTransformer extends Transformer {
 			case UPDATE_NAME:
 				return UpdateNameTransactionTransformer.toBytes(transactionData);
 
+			case SELL_NAME:
+				return SellNameTransactionTransformer.toBytes(transactionData);
+
 			case CREATE_POLL:
 				return CreatePollTransactionTransformer.toBytes(transactionData);
 
@@ -154,7 +163,7 @@ public class TransactionTransformer extends Transformer {
 				return MessageTransactionTransformer.toBytes(transactionData);
 
 			default:
-				throw new TransformationException("Unsupported transaction type");
+				throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] during conversion to bytes");
 		}
 	}
 
@@ -172,6 +181,9 @@ public class TransactionTransformer extends Transformer {
 			case UPDATE_NAME:
 				return UpdateNameTransactionTransformer.toJSON(transactionData);
 
+			case SELL_NAME:
+				return SellNameTransactionTransformer.toJSON(transactionData);
+
 			case CREATE_POLL:
 				return CreatePollTransactionTransformer.toJSON(transactionData);
 
@@ -197,7 +209,7 @@ public class TransactionTransformer extends Transformer {
 				return MessageTransactionTransformer.toJSON(transactionData);
 
 			default:
-				throw new TransformationException("Unsupported transaction type");
+				throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] during conversion to JSON");
 		}
 	}