// Use strict
"use strict";


// Classes

// Slate class
class Slate {

	// Public
	
		// Initialize
		static initialize() {
		
			// Initialize request index
			Slate.requestIndex = 0;
		
			// Create worker
			Slate.worker = new Worker(Slate.WORKER_FILE_LOCATION);
			
			// Window before unload event
			$(window).on("beforeunload", function() {
			
				// Get current request index
				var currentRequestIndex = Slate.requestIndex++;
				
				// Check if current request index is at the max safe integer
				if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
				
					// Reset request index
					Slate.requestIndex = 0;
				
				// Send worker an uninitialize request
				Slate.worker.postMessage([
				
					// Request index
					currentRequestIndex,
				
					// Type
					Slate.UNINITIALIZE_REQUEST_TYPE
				]);
			
				// Terminate worker
				Slate.worker.terminate();
			});
		
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Worker on error
				Slate.worker["onerror"] = function() {

					// Reject error
					reject("Failed to create slate worker.");
				};
				
				// Worker on message
				Slate.worker["onmessage"] = function(event) {
				
					// Get message
					var message = event["data"];
					
					// Get message's request index
					var requestIndex = message[Slate.MESSAGE_REQUEST_INDEX_OFFSET];
					
					// Check message's type
					switch(message[Slate.MESSAGE_TYPE_OFFSET]) {
					
						// Initialize request type
						case Slate.INITIALIZE_REQUEST_TYPE:
						
							// Get message's status
							var status = message[Slate.MESSAGE_STATUS_OFFSET];
						
							// Check if worker initialized successfully
							if(status === Slate.STATUS_SUCCESS_VALUE)
						
								// Resolve
								resolve();
							
							// Otherwise
							else
							
								// Reject error
								reject("Failed to initialize slate worker.");
						
							// Break
							break;
						
						// Default
						default:
						
							// Get message's response
							var response = message[Slate.MESSAGE_RESPONSE_OFFSET];
							
							// Trigger response request index event
							$(document).trigger(Slate.RESPONSE_EVENT + requestIndex.toFixed(), [
								
								// Response
								response
							]);
							
							// Break
							break;
					}
				};
				
				// Get current request index
				var currentRequestIndex = Slate.requestIndex++;
				
				// Check if current request index is at the max safe integer
				if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
				
					// Reset request index
					Slate.requestIndex = 0;
				
				// Send worker an initialize request
				Slate.worker.postMessage([
				
					// Request index
					currentRequestIndex,
				
					// Type
					Slate.INITIALIZE_REQUEST_TYPE,
					
					// URL query string
					Common.URL_QUERY_STRING_SEPARATOR + encodeURIComponent(Consensus.OVERRIDE_WALLET_TYPE_URL_PARAMETER_NAME).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR + encodeURIComponent(Consensus.walletTypeToText(Consensus.getWalletType())).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_SEPARATOR + encodeURIComponent(Consensus.OVERRIDE_NETWORK_TYPE_URL_PARAMETER_NAME).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR + encodeURIComponent(Consensus.networkTypeToText(Consensus.getNetworkType())).replace(/%20/ug, "+")
				]);
			});
		}
	
		// Constructor
		constructor(serializedSlateOrAmount, isMainnet, purposeOrFee, initialSendSlateOrheight, lockHeight = Slate.NO_LOCK_HEIGHT, relativeHeight = Slate.NO_RELATIVE_HEIGHT, timeToLiveCutOffHeight = Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT, senderAddress = Slate.NO_SENDER_ADDRESS, receiverAddress = Slate.NO_RECEIVER_ADDRESS, preferredVersions = []) {
		
			// Reset
			this.reset();
			
			// Check if a binary serialize slate is provided
			if(serializedSlateOrAmount instanceof Uint8Array === true) {
			
				// Get serialized slate
				var serializedSlate = serializedSlateOrAmount;
				
				// Get purpose
				var purpose = purposeOrFee;
				
				// Get initial send slate
				var initialSendSlate = initialSendSlateOrheight;
			
				// Unserialize the serialized slate
				this.unserialize(serializedSlate, isMainnet, purpose, initialSendSlate);
			}
			
			// Otherwise check if a serialized slate is provided
			else if(Object.isObject(serializedSlateOrAmount) === true) {
			
				// Get serialized slate
				var serializedSlate = serializedSlateOrAmount;
				
				// Get purpose
				var purpose = purposeOrFee;
				
				// Get initial send slate
				var initialSendSlate = initialSendSlateOrheight;
			
				// Unserialize the serialized slate
				this.unserialize(serializedSlate, isMainnet, purpose, initialSendSlate);
			}
			
			// Otherwise check if arguments are provided
			else if(serializedSlateOrAmount instanceof BigNumber === true) {
			
				// Get amount
				var amount = serializedSlateOrAmount;
				
				// Get fee
				var fee = purposeOrFee;
				
				// Get height
				var height = initialSendSlateOrheight;
			
				// Set amount to provided amount
				this.amount = amount;
				
				// Set fee to provided fee
				this.fee = fee;
				
				// Set height to provided height
				this.height = height;
				
				// Set lock height to provided lock height
				this.lockHeight = lockHeight;
				
				// Set relative height to provided relative height
				this.relativeHeight = relativeHeight;
				
				// Set time to live cut off height to provided time to live cut off height
				this.timeToLiveCutOffHeight = timeToLiveCutOffHeight;
				
				// Set sender address to provided sender address
				this.senderAddress = senderAddress;
				
				// Set receiver address to provided receiver address
				this.receiverAddress = receiverAddress;
				
				// Create kernel
				this.kernels.push(new SlateKernel(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()));
				
				// Set block header version to header version at the height
				this.blockHeaderVersion = Consensus.getHeaderVersion(isMainnet, this.getHeight());
				
				// Set version to the minimum compatible version
				this.version = this.minimumCompatibleVersion(preferredVersions);
				
				// Set original version to version
				this.originalVersion = this.getVersion();
			}
			
			// Otherwise
			else {
			
				// Throw error
				throw "Unsupported slate.";
			}
		}
		
		// Serialize
		serialize(isMainnet, purpose, preferBinary = false) {
			
			// Check version
			switch((this.getVersion() instanceof BigNumber === true) ? this.getVersion().toFixed() : this.getVersion()) {
			
				// Version two and three
				case Slate.VERSION_TWO.toFixed():
				case Slate.VERSION_THREE.toFixed():
				
					// Set self
					var self = this;
		
					// Create serialized slate
					var serializedSlate = {
					
						// Amount
						"amount": this.getAmount().toFixed(),
						
						// Fee
						"fee": this.getFee().toFixed(),
						
						// Height
						"height": this.getHeight().toFixed(),
						
						// ID
						"id": this.getId().serialize(),
						
						// Lock height
						"lock_height": this.getLockHeight().toFixed(),
						
						// Number of participants
						"num_participants": this.getNumberOfParticipants(),
						
						// Participant data
						"participant_data": this.getParticipants().map(function(participant) {
						
							// Return serialized participant
							return participant.serialize(self);
						}),
						
						// Transaction
						"tx": {
						
							// Body
							"body": {
							
								// Inputs
								"inputs": this.getInputs().map(function(input) {
								
									// Return serialized input
									return input.serialize(self);
								}),
								
								// Kernels
								"kernels": this.getKernels().map(function(kernel) {
								
									// Return serialized kernel
									return kernel.serialize(self);
								}),
								
								// Outputs
								"outputs": this.getOutputs().map(function(output) {
								
									// Return serialized output
									return output.serialize(self);
								})
							},
							
							// Offset
							"offset": Common.toHexString(this.getOffset())
						},
						
						// Version info
						"version_info": {
						
							// Block header version
							"block_header_version": this.getBlockHeaderVersion(),
							
							// Original version
							"orig_version": this.getOriginalVersion(),
							
							// Version
							"version": this.getVersion()
						}
					};
					
					// Check if slate's version is at least version three
					if(this.getVersion().isGreaterThanOrEqualTo(Slate.VERSION_THREE) === true) {
					
						// Set serialized slate's time to live cut off height
						serializedSlate["ttl_cutoff_height"] = (this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) ? this.getTimeToLiveCutOffHeight().toFixed() : Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
						
						// Check wallet type
						switch(Consensus.getWalletType()) {
						
							// MWC wallet
							case Consensus.MWC_WALLET_TYPE:
							
								// Set serialized slate's payment proof
								serializedSlate["payment_proof"] = (this.hasPaymentProof() === true) ? {
								
									// Receiver address
									"receiver_address": this.getReceiverAddress(),
									
									// Receiver signature
									"receiver_signature": (this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) ? Common.toHexString(this.getReceiverSignature()) : null,
									
									// Sender address
									"sender_address": this.getSenderAddress()
								
								} : Slate.NO_PAYMENT_PROOF;
						
								// Set serialized slate's coin type
								serializedSlate["coin_type"] = Slate.COIN_TYPE;
								
								// Set serialized slate's network type
								serializedSlate["network_type"] = Slate.getNetworkType(isMainnet);
								
								// Break
								break;
							
							// EPIC wallet
							case Consensus.EPIC_WALLET_TYPE:
							
								// Set serialized slate's payment proof
								serializedSlate["payment_proof"] = (this.hasPaymentProof() === true) ? {
								
									// Receiver address
									"receiver_address": Common.toHexString(Tor.torAddressToPublicKey(this.getReceiverAddress())),
									
									// Receiver signature
									"receiver_signature": (this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) ? Common.toHexString(this.getReceiverSignature()) : null,
									
									// Sender address
									"sender_address": Common.toHexString(Tor.torAddressToPublicKey(this.getSenderAddress()))
								
								} : Slate.NO_PAYMENT_PROOF;
						
								// Break
								break;
						}
					}
					
					// Return serialized slate
					return serializedSlate;
				
				// Version Slatepack
				case Slate.VERSION_SLATEPACK:
				
					// Initialize bit writer
					var bitWriter = new BitWriter();
					
					// Try
					try {
					
						// Write purpose
						bitWriter.setBits(purpose, Slate.COMPACT_SLATE_PURPOSE_LENGTH);
						
						// Write ID
						bitWriter.setBytes(this.getId().getData());
						
						// Write mainnet
						bitWriter.setBits(isMainnet ? Slate.COMPACT_BOOLEAN_TRUE : Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
						
						// Check if purpose is send initial
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
						
							// Write amount
							Slate.compactUint64(this.getAmount(), true, bitWriter);
							
							// Write fee
							Slate.compactUint64(this.getFee(), true, bitWriter);
						}
						
						// Write height
						Slate.compactUint64(this.getHeight(), false, bitWriter);
						
						// Write lock height
						Slate.compactUint64(this.getLockHeight(), false, bitWriter);
						
						// Check if time to live cut off height exists
						if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) {
						
							// Write time to live cut off height exists
							bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
							
							// Write time to live cut off height
							Slate.compactUint64(this.getTimeToLiveCutOffHeight(), false, bitWriter);
						}
						
						// Otherwise
						else {
						
							// Write time to live cut off height doesn't exist
							bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
						}
						
						// Check if purpose is send initial
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
						
							// Write participant
							this.getParticipant(SlateParticipant.SENDER_ID).serialize(this, bitWriter);
							
							// Check if has payment proof
							if(this.hasPaymentProof() === true) {
							
								// Write payment proof exists
								bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
								
								// Write sender address
								Slate.compactProofAddress(this.getSenderAddress(), isMainnet, bitWriter);
								
								// Write receiver address
								Slate.compactProofAddress(this.getReceiverAddress(), isMainnet, bitWriter);
							}
							
							// Otherwise
							else {
							
								// Write payment proof doesn't exist
								bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
							}
						}
						
						// Check if purpose is send response
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
						
							// Write offset
							bitWriter.setBytes(this.getOffset());
						
							// Go through all outputs
							for(var i = 0; i < this.getOutputs()["length"]; ++i) {
							
								// Get output
								var output = this.getOutputs()[i];
								
								// Write output
								output.serialize(this, bitWriter);
								
								// Check if not at the last output
								if(i !== this.getOutputs()["length"] - 1) {
								
									// Write continue to next output
									bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
								}
								
								// Otherwise
								else {
								
									// Write no more outputs
									bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
								}
							}
							
							// Go through all kernels
							for(var i = 0; i < this.getKernels()["length"]; ++i) {
							
								// Get kernel
								var kernel = this.getKernels()[i];
								
								// Write kernel
								kernel.serialize(this, bitWriter);
								
								// Check if not at the last kernel
								if(i !== this.getKernels()["length"] - 1) {
								
									// Write continue to next kernel
									bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
								}
								
								// Otherwise
								else {
								
									// Write no more kernels
									bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
								}
							}
						
							// Write participant
							this.getParticipant(SlateParticipant.SENDER_ID.plus(1)).serialize(this, bitWriter);
							
							// Check if has payment proof
							if(this.hasPaymentProof() === true) {
							
								// Write payment proof exists
								bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
								
								// Write sender address
								Slate.compactProofAddress(this.getSenderAddress(), isMainnet, bitWriter);
								
								// Write receiver address
								Slate.compactProofAddress(this.getReceiverAddress(), isMainnet, bitWriter);
								
								// Check if receiver signature exists
								if(this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) {
								
									// Write receiver signature exists
									bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
									
									// Check if receiver signature is too short or too long
									if(this.getReceiverSignature()["length"] < Crypto.ED25519_SIGNATURE_LENGTH || this.getReceiverSignature()["length"] - Crypto.ED25519_SIGNATURE_LENGTH >= Math.pow(2, Slate.COMPACT_PROOF_SIGNATURE_LENGTH_LENGTH)) {
									
										// Throw error
										throw "Receiver signature is too short or too long.";
									}
									
									// Write receiver signature length
									bitWriter.setBits(this.getReceiverSignature()["length"] - Crypto.ED25519_SIGNATURE_LENGTH, Slate.COMPACT_PROOF_SIGNATURE_LENGTH_LENGTH);
									
									// Write receiver signature
									bitWriter.setBytes(this.getReceiverSignature());
								}
								
								// Otherwise
								else {
								
									// Write receiver signature doesn't exist
									bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
								}
							}
							
							// Otherwise
							else {
							
								// Write payment proof doesn't exist
								bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
								
								// Write receiver signature doesn't exist
								bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
							}
						}
						
						// Return serialized slate
						return bitWriter.getBytes();
					}
					
					// Catch errors
					catch(error) {
					
						// Throw error
						throw "Compacting slate failed.";
					}
					
					// Break
					break;
				
				// Version four
				case Slate.VERSION_FOUR.toFixed():
				
					// Check if prefer binary
					if(preferBinary === true) {
					
						// Initialize bit writer
						var bitWriter = new BitWriter();
						
						// Try
						try {
						
							// Write version
							bitWriter.setBytes(this.getVersion().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT16));
							
							// Write block header version
							bitWriter.setBytes(this.getBlockHeaderVersion().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT16));
							
							// Write ID
							bitWriter.setBytes(this.getId().getData());
							
							// Write purpose
							bitWriter.setBytes([purpose + 1]);
							
							// Write offset
							bitWriter.setBytes((purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) ? Slate.ZERO_OFFSET : this.getOffset());
							
							// Initialize optional fields
							var optionalFields = 0;
							
							// Check if number of participants isn't the default
							if(this.getNumberOfParticipants().isEqualTo(Slate.DEFAULT_NUMBER_OF_PARTICIPANTS) === false) {
							
								// Set that optional fields includes number of participants
								optionalFields |= 0b00000001;
							}
							
							// Check if purpose is send initial
							if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
							
								// Check if amount isn't zero
								if(this.getAmount().isZero() === false) {
								
									// Set that optional fields includes amount
									optionalFields |= 0b00000010;
								}
								
								// Check if fee isn't zero
								if(this.getFee().isZero() === false) {
								
									// Set that optional fields includes fee
									optionalFields |= 0b00000100;
								}
							}
							
							// Check if kernel features isn't plain
							if(this.getKernelFeatures() !== SlateKernel.PLAIN_FEATURES) {
							
								// Set that optional fields includes features
								optionalFields |= 0b00001000;
							}
							
							// Check if time to live cut off height exists
							if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) {
							
								// Set that optional fields includes time to live cut off height
								optionalFields |= 0b00010000;
							}
							
							// Write optional fields
							bitWriter.setBytes([optionalFields]);
							
							// Check if optional fields includes number of participants
							if((optionalFields & 0b00000001) !== 0) {
							
								// Write number of participants
								bitWriter.setBytes(this.getNumberOfParticipants().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT8));
							}
							
							// Check if optional fields includes amount
							if((optionalFields & 0b00000010) !== 0) {
							
								// Write amount
								bitWriter.setBytes(this.getAmount().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64));
							}
							
							// Check if optional fields includes fee
							if((optionalFields & 0b00000100) !== 0) {
							
								// Write fee
								bitWriter.setBytes(this.getFee().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64));
							}
							
							// Check if optional fields includes features
							if((optionalFields & 0b00001000) !== 0) {
							
								// Write features
								bitWriter.setBytes([this.getKernelFeatures()]);
							}
							
							// Check if optional fields includes time to live cut off height
							if((optionalFields & 0b00010000) !== 0) {
							
								// Write time to live cut off height
								bitWriter.setBytes(this.getTimeToLiveCutOffHeight().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64));
							}
							
							// Write participants length
							bitWriter.setBytes([1]);
							
							// Check if purpose is send initial
							if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
							
								// Write sender participant
								this.getParticipant(SlateParticipant.SENDER_ID).serialize(this, bitWriter);
							}
							
							// Otherwise
							else {
							
								// Write receiver participant
								this.getParticipant(SlateParticipant.SENDER_ID.plus(1)).serialize(this, bitWriter);
							}
							
							// Initialize component fields
							var componentFields = 0;
							
							// Check if purpose is send response
							if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
							
								// Check if inputs or outputs exists
								if(this.getInputs()["length"] + this.getOutputs()["length"] !== 0) {
								
									// Set that component fields includes inputs and outputs
									componentFields |= 0b00000001;
								}
							}
							
							// Check if has payment proof
							if(this.hasPaymentProof() === true) {
							
								// Set that component fields includes payment proof
								componentFields |= 0b00000010;
							}
							
							// Write component fields
							bitWriter.setBytes([componentFields]);
							
							// Check if component fields includes inputs and outputs
							if((componentFields & 0b00000001) !== 0) {
							
								// Write inputs and outputs length
								bitWriter.setBytes((new BigNumber(this.getInputs()["length"] + this.getOutputs()["length"])).toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT16));
								
								// Go through all inputs
								for(var i = 0; i < this.getInputs()["length"]; ++i) {
								
									// Get input
									var input = this.getInputs()[i];
									
									// Write input or output is input
									bitWriter.setBytes([0]);
									
									// Write input
									input.serialize(this, bitWriter);
								}
								
								// Go through all outputs
								for(var i = 0; i < this.getOutputs()["length"]; ++i) {
								
									// Get output
									var output = this.getOutputs()[i];
									
									// Write input or output is output
									bitWriter.setBytes([1]);
									
									// Write output
									output.serialize(this, bitWriter);
								}
							}
							
							// Check if component fields includes payment proof
							if((componentFields & 0b00000010) !== 0) {
							
								// Write sender address
								bitWriter.setBytes(Slatepack.slatepackAddressToPublicKey(this.getSenderAddress()));
								
								// Write receiver address
								bitWriter.setBytes(Slatepack.slatepackAddressToPublicKey(this.getReceiverAddress()));
								
								// Check if receiver signature exists
								if(this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) {
								
									// Write receiver signature exists
									bitWriter.setBytes([1]);
									
									// Write receiver signature
									bitWriter.setBytes(this.getReceiverSignature());
								}
								
								// Otherwise
								else {
								
									// Write receiver signature doesn't exists
									bitWriter.setBytes([0]);
								}
							}
							
							// Check kernel features
							switch(this.getKernelFeatures()) {
							
								// Plain features
								case SlateKernel.PLAIN_FEATURES:
								
									// Break
									break;
								
								// Height locked features
								case SlateKernel.HEIGHT_LOCKED_FEATURES:
								
									// Write lock height
									bitWriter.setBytes(this.getLockHeight().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64));
								
									// Break
									break;
									
								// Default
								default:
								
									// Throw error
									throw "Unsupported features.";
							}
							
							// Return serialized slate
							return bitWriter.getBytes();
						}
						
						// Catch errors
						catch(error) {
						
							// Throw error
							throw "Compacting slate failed.";
						}
					}
					
					// Otherwise
					else {
				
						// Create serialized slate
						var serializedSlate = {
						
							// ID
							"id": this.getId().serialize(),
							
							// Purpose
							"sta": Slate.purposeToText(purpose),
							
							// Version
							"ver": this.getVersion().toFixed() + Slate.VERSION_SEPARATOR + this.getBlockHeaderVersion().toFixed()
						};
						
						// Check if number of participants isn't the default
						if(this.getNumberOfParticipants().isEqualTo(Slate.DEFAULT_NUMBER_OF_PARTICIPANTS) === false) {
						
							// Set serialized slate's number of participants
							serializedSlate["num_parts"] = this.getNumberOfParticipants();
						}
						
						// Check if time to live cut off height exists
						if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) {
						
							// Set serialized slate's time to live cut off height
							serializedSlate["ttl"] = this.getTimeToLiveCutOffHeight().toFixed();
						}
						
						// Check kernel features
						switch(this.getKernelFeatures()) {
						
							// Plain features
							case SlateKernel.PLAIN_FEATURES:
							
								// Break
								break;
							
							// Height locked features
							case SlateKernel.HEIGHT_LOCKED_FEATURES:
							
								// Set serialized slate's features
								serializedSlate["feat"] = this.getKernelFeatures();
								
								// Set serialized slate's features arguments
								serializedSlate["feat_args"] = {
								
									// Lock height
									"lock_hgt": this.getLockHeight().toFixed()
								};
							
								// Break
								break;
								
							// Default
							default:
							
								// Throw error
								throw "Unsupported features.";
						}
						
						// Check if has payment proof
						if(this.hasPaymentProof() === true) {
						
							// Set serialized slate's payment proof
							serializedSlate["proof"] = {
						
								// Receiver address
								"raddr": Common.toHexString(Slatepack.slatepackAddressToPublicKey(this.getReceiverAddress())),
							
								// Sender address
								"saddr": Common.toHexString(Slatepack.slatepackAddressToPublicKey(this.getSenderAddress()))
							};
							
							// Check if receiver signature exists
							if(this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) {
							
								// Set payment proof's receiver signature
								serializedSlate["proof"]["rsig"] = Common.toHexString(this.getReceiverSignature());
							}
						}
						
						// Check if purpose is send initial
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
						
							// Set serialized slate's amount
							serializedSlate["amt"] = this.getAmount().toFixed();
							
							// Set serialized slate's fee
							serializedSlate["fee"] = this.getFee().toFixed();
							
							// Set serialized slate's participants
							serializedSlate["sigs"] = [this.getParticipant(SlateParticipant.SENDER_ID).serialize(this)];
						}
						
						// Check if purpose is send response
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
						
							// Set serialized slate's offset
							serializedSlate["off"] = Common.toHexString(this.getOffset());
							
							// Set serialized slate's participants
							serializedSlate["sigs"] = [this.getParticipant(SlateParticipant.SENDER_ID.plus(1)).serialize(this)];
							
							// Set serialized slate's inputs and outputs
							serializedSlate["coms"] = [];
							
							// Go through all inputs
							for(var i = 0; i < this.getInputs()["length"]; ++i) {
							
								// Get input
								var input = this.getInputs()[i];
									
								// Add serialized input to the serialized slate's inputs and outputs
								serializedSlate["coms"].push(input.serialize(this));
							}
							
							// Go through all outputs
							for(var i = 0; i < this.getOutputs()["length"]; ++i) {
							
								// Get output
								var output = this.getOutputs()[i];
									
								// Add serialized output to the serialized slate's inputs and outputs
								serializedSlate["coms"].push(output.serialize(this));
							}
						}
						
						// Return serialzied slate
						return serializedSlate;
					}
					
					// Break
					break;
				
				// Default
				default:
				
					// Throw error
					throw "Unsupported slate version.";
			}
		}
		
		// Get transaction
		getTransaction() {
		
			// Return transaction
			return {
		
				// Body
				"body": {
				
					// Inputs
					"inputs": this.getInputs().map(function(input) {
					
						// Return input's transaction
						return input.getTransaction();
					}),
					
					// Kernels
					"kernels": this.getKernels().map(function(kernel) {
					
						// Return kernel's transaction
						return kernel.getTransaction();
					}),
					
					// Outputs
					"outputs": this.getOutputs().map(function(output) {
					
						// Return output's transaction
						return output.getTransaction();
					})
				},
				
				// Offset
				"offset": Common.toHexString(this.getOffset())
			};
		}
		
		// Get number of participants
		getNumberOfParticipants() {
		
			// Return number of participants
			return this.numberOfParticipants;
		}
		
		// Get ID
		getId() {
		
			// Return ID
			return this.id;
		}
		
		// Change ID
		changeId() {
		
			// Set ID to a random UUID
			this.id = new Uuid(Uuid.randomSerializedUuid());
		}
		
		// Get offset
		getOffset() {
		
			// Return offset
			return this.offset;
		}
		
		// Get inputs
		getInputs() {
		
			// Return inputs
			return this.inputs;
		}
		
		// Get outputs
		getOutputs() {
		
			// Return outputs
			return this.outputs;
		}
		
		// Get kernels
		getKernels() {
		
			// Return kernels
			return this.kernels;
		}
		
		// Get amount
		getAmount() {
		
			// Return amount
			return this.amount;
		}
		
		// Get fee
		getFee() {
		
			// Return fee
			return this.fee;
		}
		
		// Get height
		getHeight() {
		
			// Return height
			return this.height;
		}
		
		// Get lock height
		getLockHeight() {
		
			// Return lock height
			return this.lockHeight;
		}
		
		// Get relative height
		getRelativeHeight() {
		
			// Return relative height
			return this.relativeHeight;
		}
		
		// Get time to live cut off height
		getTimeToLiveCutOffHeight() {
		
			// Return time to live cut off height
			return this.timeToLiveCutOffHeight;
		}
		
		// Get participants
		getParticipants() {
		
			// Return participants
			return this.participants;
		}
		
		// Get version
		getVersion() {
		
			// Return version
			return this.version;
		}
		
		// Get original version
		getOriginalVersion() {
		
			// Return original version
			return this.originalVersion;
		}
		
		// Get block header version
		getBlockHeaderVersion() {
		
			// Return block header version
			return this.blockHeaderVersion;
		}
		
		// Get receiver address
		getReceiverAddress() {
		
			// Return receiver address
			return this.receiverAddress;
		}
		
		// Get receiver signature
		getReceiverSignature() {
		
			// Return receiver signature
			return this.receiverSignature;
		}
		
		// Set receiver address
		setReceiverAddress(receiverAddress) {
		
			// Set receiver address
			this.receiverAddress = receiverAddress;
		}
		
		// Set receiver signature
		setReceiverSignature(receiverSignature, isMainnet) {
		
			// Set receiver signature
			this.receiverSignature = receiverSignature;
			
			// Check if receiver signature failed to be verified
			if(this.verifyReceiverSignature(isMainnet) === false) {
			
				// Return false
				return false;
			}
			
			// Return true
			return true;
		}
		
		// Get sender address
		getSenderAddress() {
		
			// Return sender address
			return this.senderAddress;
		}
		
		// Get participant
		getParticipant(participantId) {
		
			// Go through all participants
			for(var i = 0; i < this.getParticipants()["length"]; ++i) {
			
				// Get participant
				var participant = this.getParticipants()[i];
				
				// Check if participant has the specified participant ID
				if(participant.getId().isEqualTo(participantId) === true) {
				
					// Return participant
					return participant;
				}
			}
			
			// Return no participant
			return Slate.NO_PARTICIPANT;
		}
		
		// Add outputs
		addOutputs(outputs, updateKernel = true) {
		
			// Check if updating kernel
			if(updateKernel === true) {
		
				// Update kernel
				this.updateKernel();
			}
			
			// Go through all outputs
			for(var i = 0; i < outputs["length"]; ++i) {
			
				// Get output
				var output = outputs[i];
			
				// Append output to list
				this.outputs.push(output);
			}
			
			// Check if sorting failed
			if(this.sort() === false) {
			
				// Return false
				return false;
			}
			
			// Check if verifying weight failed
			if(this.verifyWeight() === false) {
			
				// Return false
				return false;
			}
			
			// Check if verifying sorted and unique failed
			if(this.verifySortedAndUnique() === false) {
			
				// Return false
				return false;
			}
			
			// Check if verifying no cut through failed
			if(this.verifyNoCutThrough() === false) {
			
				// Return false
				return false;
			}
			
			// Return true
			return true;
		}
		
		// Add inputs
		addInputs(inputs, updateKernel = true, expectedNumberOfOutputs = 0) {
		
			// Check if updating kernel
			if(updateKernel === true) {
		
				// Update kernel
				this.updateKernel();
			}
			
			// Go through all inputs
			for(var i = 0; i < inputs["length"]; ++i) {
			
				// Get input
				var input = inputs[i];
			
				// Append input to list
				this.inputs.push(input);
			}
			
			// Check if sorting failed
			if(this.sort() === false) {
			
				// Return false
				return false;
			}
			
			// Check if verifying weight failed
			if(this.verifyWeight(expectedNumberOfOutputs) === false) {
			
				// Return false
				return false;
			}
			
			// Check if verifying sorted and unique failed
			if(this.verifySortedAndUnique() === false) {
			
				// Return false
				return false;
			}
			
			// Check if verifying no cut through failed
			if(this.verifyNoCutThrough() === false) {
			
				// Return false
				return false;
			}
			
			// Return true
			return true;
		}
		
		// Create offset
		createOffset() {
		
			// While offset isn't a valid secret key
			do {
			
				// Fill offset with random values
				crypto.getRandomValues(this.offset);
				
			} while(Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true);
		}
		
		// Apply offset
		applyOffset(secretKeyOrHardwareWallet, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
		
			// Set self
			var self = this;
			
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Check if a secret key is provided
				if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
				
					// Get secret key
					var secretKey = secretKeyOrHardwareWallet;
				
					// Check if secret key isn't a valid secret key
					if(Secp256k1Zkp.isValidSecretKey(secretKey) !== true) {
					
						// Reject error
						reject("Secret key isn't a valid secret key.");
					}
					
					// Otherwise
					else {
					
						// Check if getting blind offset from secret key and offset failed
						var blindOffset = Secp256k1Zkp.blindSum([
						
							// Secret key
							secretKey
							
						], [
						
							// Offset
							self.getOffset()
						]);
						
						if(blindOffset === Secp256k1Zkp.OPERATION_FAILED) {
						
							// Reject error
							reject("Getting blind offset from secret key and offset failed.");
						}
						
						// Otherwise check if blind offset isn't a valid secret key
						else if(Secp256k1Zkp.isValidSecretKey(blindOffset) !== true) {
						
							// Securely clear blind offset
							blindOffset.fill(0);
						
							// Reject error
							reject("Blind offset isn't a valid secret key.");
						}
						
						// Otherwise
						else {
						
							// Resolve blind offset
							resolve(blindOffset);
						}
					}
				}
				
				// Otherwise
				else {
				
					// Get hardware wallet
					var hardwareWallet = secretKeyOrHardwareWallet;
					
					// Return applying offset to the transaction with the hardware wallet
					return hardwareWallet.applyOffsetToTransaction(self.getOffset(), hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(secretNonceIndex) {
					
						// Resolve secret nonce index
						resolve(secretNonceIndex);
					
					// Catch errors
					}).catch(function(error) {
					
						// Reject error
						reject(error);
					});
				}
			});
		}
		
		// Combine offsets
		combineOffsets(offset) {
		
			// Check if updating offset with the offset failed
			this.offset = Secp256k1Zkp.blindSum([
							
				// Offset
				this.getOffset(),
				
				// Offset
				offset
			], []);
			
			if(this.getOffset() === Secp256k1Zkp.OPERATION_FAILED) {
							
				// Return false
				return false;
			}
			
			// Otherwise check if the result isn't a valid secret key
			else if(Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true) {
			
				// Return false
				return false;
			}
			
			// Otherwise
			else {
			
				// Return true
				return true;
			}
		}
		
		// Add participant
		addParticipant(secretKeyOrHardwareWallet, secretNonce, message, isMainnet, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
		
			// Set self
			var self = this;
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Set participant ID to the sender ID
				var participantId = SlateParticipant.SENDER_ID;
				
				// While participant ID exists
				while(self.getParticipant(participantId) !== Slate.NO_PARTICIPANT) {
				
					// Increment participant ID
					participantId = participantId.plus(1);
				}
				
				// Get public blind excess
				var getPublicBlindExcess = function() {
				
					// Return promise
					return new Promise(function(resolve, reject) {
				
						// Check if a secret key is provided
						if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
						
							// Get secret key
							var secretKey = secretKeyOrHardwareWallet;
						
							// Check if getting public blind excess from secret key failed
							var publicBlindExcess = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
							
							if(publicBlindExcess === Secp256k1Zkp.OPERATION_FAILED) {
							
								// Reject error
								reject("Getting public blind excess failed.");
							}
							
							// Otherwise
							else {
							
								// Resolve public blind excess
								resolve(publicBlindExcess);
							}
						}
						
						// Otherwise
						else {
						
							// Get hardware wallet
							var hardwareWallet = secretKeyOrHardwareWallet;
							
							// Return getting the transaction public key with the hardware wallet
							return hardwareWallet.getTransactionPublicKey(hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(publicBlindExcess) {
							
								// Resolve public blind excess
								resolve(publicBlindExcess);
							
							// Catch errors
							}).catch(function(error) {
							
								// Reject error
								reject(error);
							});
						}
					});
				};
				
				// Return getting public blind excess
				return getPublicBlindExcess().then(function(publicBlindExcess) {
				
					// Get message signature
					var getMessageSignature = function() {
					
						// Return promise
						return new Promise(function(resolve, reject) {
				
							// Check if a message exists
							if(message !== SlateParticipant.NO_MESSAGE) {
							
								// Check if a secret key is provided
								if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
								
									// Get secret key
									var secretKey = secretKeyOrHardwareWallet;
									
									// Get message's hash
									var messageHash = Blake2b.compute(Crypto.SINGLE_SIGNER_MESSAGE_LENGTH, (new TextEncoder()).encode(message), new Uint8Array());
									
									// Check if getting message's hash failed
									if(messageHash === Blake2b.OPERATION_FAILED) {
									
										// Reject error
										reject("Getting message's hash failed.");
										
										// Return
										return;
									}
								
									// Check if creating message signature from the message hash, secret key, and public blind excess failed
									var messageSignature = Secp256k1Zkp.createSingleSignerSignature(messageHash, secretKey, Secp256k1Zkp.NO_SECRET_NONCE, publicBlindExcess, Secp256k1Zkp.NO_PUBLIC_NONCE, Secp256k1Zkp.NO_PUBLIC_NONCE_TOTAL);
									
									if(messageSignature === Secp256k1Zkp.OPERATION_FAILED) {
									
										// Reject error
										reject("Creating message signature.");
										
										// Return
										return;
									}
									
									// Resolve message signature
									resolve(messageSignature);
								}
								
								// Otherwise
								else {
								
									// Get hardware wallet
									var hardwareWallet = secretKeyOrHardwareWallet;
									
									// Return getting the transaction message signature with the hardware wallet
									return hardwareWallet.getTransactionMessageSignature(message, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(messageSignature) {
									
										// Resolve message signature
										resolve(messageSignature);
									
									// Catch errors
									}).catch(function(error) {
									
										// Reject error
										reject(error);
									});
								}
							}
							
							// Otherwise
							else {
							
								// Resolve no message signature
								resolve(SlateParticipant.NO_MESSAGE_SIGNATURE);
							}
						});
					};
					
					// Return getting message signature
					return getMessageSignature().then(function(messageSignature) {
					
						// Get public nonce
						var getPublicNonce = function() {
						
							// Return promise
							return new Promise(function(resolve, reject) {
							
								// Check if a secret key is provided
								if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
								
									// Check if getting a public nonce from secret nonce failed
									var publicNonce = Secp256k1Zkp.publicKeyFromSecretKey(secretNonce);
									
									if(publicNonce === Secp256k1Zkp.OPERATION_FAILED) {
									
										// Reject error
										reject("Getting a public nonce failed.");
									}
									
									// Otherwise
									else {
									
										// Resolve public nonce
										resolve(publicNonce);
									}
								}
								
								// Otherwise
								else {
								
									// Get hardware wallet
									var hardwareWallet = secretKeyOrHardwareWallet;
									
									// Return getting the transaction public nonce with the hardware wallet
									return hardwareWallet.getTransactionPublicNonce(hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(publicNonce) {
									
										// Resolve public nonce
										resolve(publicNonce);
									
									// Catch errors
									}).catch(function(error) {
									
										// Reject error
										reject(error);
									});
								}
							});
						};
						
						// Return getting public nonce
						return getPublicNonce().then(function(publicNonce) {
				
							// Save receiver signature
							var oldReceiverSignature = self.getReceiverSignature();
						
							// Get partial signature
							var getPartialSignature = function() {
							
								// Return promise
								return new Promise(function(resolve, reject) {
						
									// Check if participant ID is the sender ID
									if(participantId.isEqualTo(SlateParticipant.SENDER_ID) === true) {
									
										// Set no partial signature
										resolve(SlateParticipant.NO_PARTIAL_SIGNATURE);
									}
									
									// Otherwise
									else {
									
										// Return creating partial signature
										return self.createPartialSignature(secretKeyOrHardwareWallet, secretNonce, true, isMainnet, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(partialSignature) {
										
											// Resolve partial signature
											resolve(partialSignature);
										
										// Catch errors
										}).catch(function(error) {
										
											// Reject error
											reject(error);
										});
									}
								});
							};
							
							// Return getting partial signature
							return getPartialSignature().then(function(partialSignature) {
							
								// Try
								try {
								
									// Create participant
									var participant = new SlateParticipant(participantId, publicBlindExcess, publicNonce, partialSignature, message, messageSignature);
								}
								
								// Catch errors
								catch(error) {
								
									// Restore old receiver signature
									self.setReceiverSignature(oldReceiverSignature);
								
									// Reject error
									reject("Creating participant failed.");
									
									// Return
									return;
								}
								
								// Add participant to list
								self.getParticipants().push(participant);
								
								// Check if partial signatures failed to be verified
								if(self.verifyPartialSignatures() === false) {
								
									// Remove participant from list
									self.getParticipants().pop();
									
									// Restore old receiver signature
									self.setReceiverSignature(oldReceiverSignature);
								
									// Reject error
									reject("Verifying partial signature failed.");
								}
								
								// Otherwise
								else {
								
									// Resolve
									resolve();
								}
							
							// Catch errors
							}).catch(function(error) {
							
								// Reject error
								reject(error);
							});
						
						// Catch errors
						}).catch(function(error) {
						
							// Reject error
							reject(error);
						});
					
					// Catch errors
					}).catch(function(error) {
					
						// Reject error
						reject(error);
					});
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Finalize
		finalize(secretKeyOrHardwareWallet, secretNonce, baseFee, isMainnet, verifyAsynchronous = false, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
		
			// Set self
			var self = this;
			
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Return creating partial signature
				return self.createPartialSignature(secretKeyOrHardwareWallet, secretNonce, false, isMainnet, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(partialSignature) {
				
					// Get sender participant
					var senderParticipant = self.getParticipant(SlateParticipant.SENDER_ID);
					
					// Save sender participant's partial signature
					var oldPartialSignature = senderParticipant.getPartialSignature();
					
					// Set sender participant's partial signature
					senderParticipant.setPartialSignature(partialSignature);
					
					// Check if partial signatures failed to be verified
					if(self.verifyPartialSignatures() === false) {
					
						// Restore sender participant's old partial signature
						senderParticipant.setPartialSignature(oldPartialSignature);
					
						// Reject error
						reject("Verifying partial signature failed.");
					}
					
					// Otherwise
					else {
				
						// Initialize partial signatures
						var partialSignatures = [];
						
						// Go through all participants
						for(var i = 0; i < self.getParticipants()["length"]; ++i) {
						
							// Get participant
							var participant = self.getParticipants()[i];
							
							// Check if participant is complete
							if(participant.isComplete() === true) {
							
								// Add participant's partial signature to list
								partialSignatures.push(participant.getPartialSignature());
							}
							
							// Otherwise
							else {
							
								// Restore sender participant's old partial signature
								senderParticipant.setPartialSignature(oldPartialSignature);
							
								// Reject error
								reject("Participant isn't complete.");
								
								// Return
								return;
							}
						}
					
						// Get public nonce sum from combining participant's public nonces
						var publicNonceSum = Secp256k1Zkp.combinePublicKeys(self.getParticipants().map(function(participant) {
						
							// Return participant's public nonce
							return participant.getPublicNonce();
						}));
						
						// Check if getting public nonce sum failed
						if(publicNonceSum === Secp256k1Zkp.OPERATION_FAILED) {
						
							// Restore sender participant's old partial signature
							senderParticipant.setPartialSignature(oldPartialSignature);
						
							// Reject error
							reject("Getting public nonce sum failed.");
						}
						
						// Otherwise
						else {
						
							// Get public blind excess sum from combining participant's public blind excesses
							var publicBlindExcessSum = Secp256k1Zkp.combinePublicKeys(self.getParticipants().map(function(participant) {
							
								// Return participant's public blind excess
								return participant.getPublicBlindExcess();
							}));
							
							// Check if getting public blind excess sum failed
							if(publicBlindExcessSum === Secp256k1Zkp.OPERATION_FAILED) {
							
								// Restore sender participant's old partial signature
								senderParticipant.setPartialSignature(oldPartialSignature);
							
								// Reject error
								reject("Getting public blind excess sum failed.");
							}
							
							// Otherwise
							else {
							
								// Get final signature from adding partial signatures and inclusing the public nonce sum
								var finalSignature = Secp256k1Zkp.addSingleSignerSignatures(partialSignatures, publicNonceSum);
								
								// Check if getting final signature failed
								if(finalSignature === Secp256k1Zkp.OPERATION_FAILED) {
								
									// Restore sender participant's old partial signature
									senderParticipant.setPartialSignature(oldPartialSignature);
								
									// Reject error
									reject("Getting final signature failed.");
								}
								
								// Otherwise
								else {
								
									// Try
									try {
									
										// Get message to sign
										var messageToSign = SlateKernel.signatureMessage(self.getKernelFeatures(), self.getFee(), self.getLockHeight(), self.getRelativeHeight());
									}
									
									// Catch errors
									catch(error) {
									
										// Restore sender participant's old partial signature
										senderParticipant.setPartialSignature(oldPartialSignature);
									
										// Reject error
										reject("Getting message to sign failed.");
										
										// Return
										return;
									}
									
									// Check if final signature doesn't verify the slate
									if(Secp256k1Zkp.verifySingleSignerSignature(finalSignature, messageToSign, Secp256k1Zkp.NO_PUBLIC_NONCE, publicBlindExcessSum, publicBlindExcessSum, false) !== true) {
									
										// Restore sender participant's old partial signature
										senderParticipant.setPartialSignature(oldPartialSignature);
									
										// Reject error
										reject("Verifying final signature failed.");
									}
									
									// Otherwise check if only one kernel doesn't exist
									else if(self.getKernels()["length"] !== 1) {
									
										// Restore sender participant's old partial signature
										senderParticipant.setPartialSignature(oldPartialSignature);
									
										// Reject error
										reject("Only one kernel doesn't exist.");
									}
									
									// Otherwise check if kernel is already complete
									else if(self.getKernels()[0].isComplete() === true) {
									
										// Restore sender participant's old partial signature
										senderParticipant.setPartialSignature(oldPartialSignature);
									
										// Reject error
										reject("Kernel is already complete.");
									}
										
									// Otherwise
									else {
									
										// Save kernel's excess
										var oldExcess = self.getKernels()[0].getExcess();
									
										// Set kernel's excess
										self.getKernels()[0].setExcess(self.getExcess());
										
										// Save kernel's excess signature
										var oldExcessSignature = self.getKernels()[0].getExcessSignature();
										
										// Check if setting kernel's excess signature failed
										if(self.getKernels()[0].setExcessSignature(finalSignature) === false) {
										
											// Restore kernel's old excess and excess signature
											self.getKernels()[0].setExcess(oldExcess);
											self.getKernels()[0].setExcessSignature(oldExcessSignature);
										
											// Restore sender participant's old partial signature
											senderParticipant.setPartialSignature(oldPartialSignature);
										
											// Reject error
											reject("Setting kernel's excess signature failed.");
										}
										
										// Otherwise
										else {
										
											// Check if verify asynchronous
											if(verifyAsynchronous === true) {
											
												// Return verifying after finalize asynchronous
												return Slate.verifyAfterFinalizeAsynchronous(self, baseFee, isMainnet).then(function() {
												
													// Resolve
													resolve();
												
												// Catch errors
												}).catch(function(error) {
												
													// Restore kernel's old excess and excess signature
													self.getKernels()[0].setExcess(oldExcess);
													self.getKernels()[0].setExcessSignature(oldExcessSignature);
												
													// Restore sender participant's old partial signature
													senderParticipant.setPartialSignature(oldPartialSignature);
												
													// Reject error
													reject("Verifying after finalize failed.");
												});
											}
											
											// Otherwise
											else {
										
												// Check if verifying after finalize failed
												if(self.verifyAfterFinalize(baseFee, isMainnet) === false) {
												
													// Restore kernel's old excess and excess signature
													self.getKernels()[0].setExcess(oldExcess);
													self.getKernels()[0].setExcessSignature(oldExcessSignature);
												
													// Restore sender participant's old partial signature
													senderParticipant.setPartialSignature(oldPartialSignature);
												
													// Reject error
													reject("Verifying after finalize failed.");
												}
												
												// Otherwise
												else {
												
													// Resolve
													resolve();
												}
											}
										}
									}
								}
							}
						}
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Get offset excess
		getOffsetExcess() {
		
			// Check if performing Pedersen commit with the offset and zero failed
			var offsetExcess = Secp256k1Zkp.pedersenCommit(this.getOffset(), (new BigNumber(0)).toFixed());
			
			if(offsetExcess === Secp256k1Zkp.OPERATION_FAILED) {
			
				// Throw error
				throw "Performing Pedersen commit with the offset and zero failed.";
			}
			
			// Return offset excess
			return offsetExcess;
		}
		
		// Get excess
		getExcess(publicBlindExcess = Slate.NO_PUBLIC_BLIND_EXCESS) {
		
			// Check if compact
			if(this.isCompact() === true) {
			
				// Get participants public blind excesses
				var publicBlindExcesses = this.getParticipants().map(function(participant) {
				
					// Return participant's public blind excess
					return participant.getPublicBlindExcess();
				});
				
				// Check if a public blind excess is provided
				if(publicBlindExcess !== Slate.NO_PUBLIC_BLIND_EXCESS) {
				
					// Append public blind excess to list
					publicBlindExcesses.push(publicBlindExcess);
				}
				
				// Get public blind excess sum from combining participant's public blind excesses
				var publicBlindExcessSum = Secp256k1Zkp.combinePublicKeys(publicBlindExcesses);
				
				// Check if getting public blind excess sum failed
				if(publicBlindExcessSum === Secp256k1Zkp.OPERATION_FAILED) {
				
					// Throw error
					throw "Getting public blind excess sum failed.";
				}
				
				// Check if getting excess from public blind excess sum failed
				var excess = Secp256k1Zkp.publicKeyToPedersenCommit(publicBlindExcessSum);
				
				if(excess === Secp256k1Zkp.OPERATION_FAILED) {
				
					// Throw error
					throw "Getting excess from public blind excess sum failed.";
				}
				
				// Return excess
				return excess;
			}
			
			// Otherwise
			else {
		
				// Try
				try {
				
					// Get offset excess
					var offsetExcess = this.getOffsetExcess();
				}
				
				// Catch errors
				catch(error) {
				
					// Throw error
					throw error;
				}
				
				// Check if getting transaction excess from commits sum failed
				var transactionExcess = this.getCommitsSum();
				
				if(transactionExcess === false) {
				
					// Throw error
					throw "Getting transaction excess from commits sum failed.";
				}
				
				// Check if getting excess from transaction excess and offset excess failed
				var excess = Secp256k1Zkp.pedersenCommitSum([
				
					// Transaction excess
					transactionExcess
					
				], [
				
					// Offset excess
					offsetExcess
				]);
				
				if(excess === Secp256k1Zkp.OPERATION_FAILED) {
				
					// Throw error
					throw "Getting excess from transaction excess and offset excess failed.";
				}
				
				// Return excess
				return excess;
			}
		}
		
		// Is equal to
		isEqualTo(slate) {
		
			// Check if ID aren't equal
			if(Common.arraysAreEqual(this.getId().getData(), slate.getId().getData()) === false)
			
				// Return false
				return false;
			
			// Check if amounts aren't equal
			if(this.getAmount().isEqualTo(slate.getAmount()) === false)
			
				// Return false
				return false;
			
			// Check if fees aren't equal
			if(this.getFee().isEqualTo(slate.getFee()) === false)
			
				// Return false
				return false;
			
			// Check if lock heights aren't equal
			if(this.getLockHeight().isEqualTo(slate.getLockHeight()) === false)
			
				// Return false
				return false;
			
			// Check if relative heights aren't equal
			if((this.getRelativeHeight() === Slate.NO_RELATIVE_HEIGHT && slate.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT) || (this.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT && slate.getRelativeHeight() === Slate.NO_RELATIVE_HEIGHT) || (this.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT && this.getRelativeHeight().isEqualTo(slate.getRelativeHeight()) === false))
			
				// Return false
				return false;
			
			// Check if heights aren't equal
			if((this.getHeight() === Slate.UNKNOWN_HEIGHT && slate.getHeight() !== Slate.UNKNOWN_HEIGHT) || (this.getHeight() !== Slate.UNKNOWN_HEIGHT && slate.getHeight() === Slate.UNKNOWN_HEIGHT) || (this.getHeight() !== Slate.UNKNOWN_HEIGHT && this.getHeight().isEqualTo(slate.getHeight()) === false))
			
				// Return false
				return false;
			
			// Check if time to live cutoff heights aren't equal
			if((this.getTimeToLiveCutOffHeight() === Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && slate.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) || (this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && slate.getTimeToLiveCutOffHeight() === Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) || (this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && this.getTimeToLiveCutOffHeight().isEqualTo(slate.getTimeToLiveCutOffHeight()) === false))
			
				// Return false
				return false;
			
			// Check if number of participants aren't equal
			if(this.getNumberOfParticipants().isEqualTo(slate.getNumberOfParticipants()) === false)
			
				// Return false
				return false;
			
			// Check if not compact
			if(this.isCompact() === false) {
			
				// Check if offsets aren't equal
				if(Common.arraysAreEqual(this.getOffset(), slate.getOffset()) === false)
				
					// Return false
					return false;
			}
			
			// Otherwise
			else {
			
				// Check if offsets are equal
				if(Common.arraysAreEqual(this.getOffset(), slate.getOffset()) === true)
				
					// Return false
					return false;
			}
			
			// Check if block header versions aren't equal
			if(this.getBlockHeaderVersion().isEqualTo(slate.getBlockHeaderVersion()) === false)
			
				// Return false
				return false;
			
			// Check if original versions aren't equal
			if((this.getOriginalVersion() instanceof BigNumber === true && (slate.getOriginalVersion() instanceof BigNumber === false || this.getOriginalVersion().isEqualTo(slate.getOriginalVersion()) === false)) || (typeof this.getOriginalVersion() === "string" && (typeof slate.getOriginalVersion() !== "string" || this.getOriginalVersion() !== slate.getOriginalVersion()))) {
			
				// Check if orignal version difference isn't allowed
				if(this.getOriginalVersion() instanceof BigNumber === false || slate.getOriginalVersion() instanceof BigNumber === false || this.getOriginalVersion().isEqualTo(Slate.VERSION_TWO) === false || slate.getOriginalVersion().isEqualTo(Slate.VERSION_THREE) === false) {
				
					// Return false
					return false;
				}
			}
			
			// Check if versions aren't compatible
			if((this.getVersion() instanceof BigNumber === true && (slate.getVersion() instanceof BigNumber === false || this.getVersion().isLessThan(slate.getVersion()) === true)) || (typeof this.getVersion() === "string" && (typeof slate.getVersion() !== "string" || this.getVersion() !== slate.getVersion())))
			
				// Return false
				return false;
			
			// Check if receiver addresses aren't equal
			if(this.getReceiverAddress() !== slate.getReceiverAddress())
			
				// Return false
				return false;
			
			// Check if sender addresses aren't equal
			if(this.getSenderAddress() !== slate.getSenderAddress())
			
				// Return false
				return false;
			
			// Check if number of inputs changed
			if(this.getInputs()["length"] !== slate.getInputs()["length"])
			
				// Return false
				return false;
			
			// Go through all inputs
			for(var i = 0; i < this.getInputs()["length"]; ++i) {
			
				// Check if inputs aren't equal
				if(this.getInputs()[i].isEqualTo(slate.getInputs()[i]) === false)
				
					// Return false
					return false;
			}
			
			// Check if number of kernels changed
			if(this.getKernels()["length"] !== slate.getKernels()["length"])
			
				// Return false
				return false;
			
			// Go through all kernels
			for(var i = 0; i < this.getKernels()["length"]; ++i) {
			
				// Check if kernels aren't equal
				if(this.getKernels()[i].isEqualTo(slate.getKernels()[i]) === false)
				
					// Return false
					return false;
			}
			
			// Go through all outputs
			for(var i = 0; i < this.getOutputs()["length"]; ++i) {
			
				// Set output found to false
				var outputFound = false;
			
				// Go through all slate's outputs
				for(var j = 0; j < slate.getOutputs()["length"]; ++j) {
			
					// Check if outputs are equal
					if(this.getOutputs()[i].isEqualTo(slate.getOutputs()[j]) === true) {
					
						// Set output found
						outputFound = true;
						
						// Break
						break;
					}
				}
				
				// Check if output wasn't found
				if(outputFound === false)
				
					// Return falswe
					return false;
			}
			
			// Go through all participants
			for(var i = 0; i < this.getParticipants()["length"]; ++i) {
			
				// Set participant found to false
				var participantFound = false;
			
				// Go through all slate's participants
				for(var j = 0; j < slate.getParticipants()["length"]; ++j) {
			
					// Check if participants are equal
					if(this.getParticipants()[i].isEqualTo(slate.getParticipants()[j]) === true) {
					
						// Set participant found
						participantFound = true;
						
						// Break
						break;
					}
				}
				
				// Check if participant wasn't found
				if(participantFound === false)
				
					// Return falswe
					return false;
			}
			
			// Return true
			return true;
		}
		
		// Is compact
		isCompact() {
		
			// Return if version is Slatepack version or version four
			return this.getVersion() === Slate.VERSION_SLATEPACK || (this.getVersion() instanceof BigNumber === true && this.getVersion().isGreaterThanOrEqualTo(Slate.VERSION_FOUR) === true);
		}
		
		// Get display kernel features
		getDisplayKernelFeatures() {
		
			// Check kernel features
			switch(this.getKernelFeatures()) {
			
				// Plain features
				case SlateKernel.PLAIN_FEATURES:
				
					// Return display kernel features
					return Language.getDefaultTranslation('plain');
				
				// Coinbase features
				case SlateKernel.COINBASE_FEATURES:
				
					// Return display kernel features
					return Language.getDefaultTranslation('coinbase');
				
				// Height locked features
				case SlateKernel.HEIGHT_LOCKED_FEATURES:
				
					// Return display kernel features
					return Language.getDefaultTranslation('height locked');
				
				// No recent duplicate features
				case SlateKernel.NO_RECENT_DUPLICATE_FEATURES:
				
					// Return display kernel features
					return Language.getDefaultTranslation('no recent duplicate');
			}
		}
		
		// Get payment proof message
		static getPaymentProofMessage(amount, commit, senderAddress) {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC wallet
				case Consensus.MWC_WALLET_TYPE:
				
					// Return creating message from commit, sender address, and amount
					return (new TextEncoder()).encode(Common.toHexString(commit) + senderAddress + amount.toFixed());
				
				// GRIN wallet
				case Consensus.GRIN_WALLET_TYPE:
		
					// Return creating message from amount, commit, and sender address
					return Common.mergeArrays([
					
						// Amount
						amount.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64),
						
						// Commit
						commit,
						
						// Sender address
						Slatepack.slatepackAddressToPublicKey(senderAddress)
					]);
				
				// EPIC wallet
				case Consensus.EPIC_WALLET_TYPE:
		
					// Return creating message from amount, commit, and sender address
					return Common.mergeArrays([
					
						// Amount
						amount.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64),
						
						// Commit
						commit,
						
						// Sender address
						Tor.torAddressToPublicKey(senderAddress)
					]);
			}
		}
		
		// Supported versions
		static get SUPPORTED_VERSIONS() {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC wallet
				case Consensus.MWC_WALLET_TYPE:
		
					// Return supported versions
					return [
					
						// Version Slatepack
						Slate.VERSION_SLATEPACK,
						
						// Version three B
						"V" + Slate.VERSION_THREE.toFixed() + "B",
						
						// Version three
						"V" + Slate.VERSION_THREE.toFixed(),
											
						// Version two
						"V" + Slate.VERSION_TWO.toFixed()
					];
				
				// GRIN wallet
				case Consensus.GRIN_WALLET_TYPE:
		
					// Return supported versions
					return [
					
						// Version four
						"V" + Slate.VERSION_FOUR.toFixed()
					];
				
				// EPIC wallet
				case Consensus.EPIC_WALLET_TYPE:
		
					// Return supported versions
					return [
					
						// Version three
						"V" + Slate.VERSION_THREE.toFixed(),
											
						// Version two
						"V" + Slate.VERSION_TWO.toFixed()
					];
			}
		}
		
		// Get required fee
		static getRequiredFee(numberOfInputs, numberOfOutputs, numberOfKernels, baseFee) {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC or EPIC wallet
				case Consensus.MWC_WALLET_TYPE:
				case Consensus.EPIC_WALLET_TYPE:
				
					// Get body weight from the number of inputs, outputs, and kernels
					//TODO mwc-wallet changed this to use weight_by_iok when it should still be using old_weight_by_iok until hard fork
					var bodyWeight = new BigNumber(numberOfOutputs).multipliedBy(Slate.BODY_WEIGHT_OUTPUT_FACTOR).plus(Math.max(numberOfKernels, 1)).minus(numberOfInputs);
					
					// Check if body weight is less than the minimum body weight
					if(bodyWeight.isLessThan(Slate.MINIMUM_BODY_WEIGHT) === true)
					
						// Set body weight to the minimum body weight
						bodyWeight = new BigNumber(Slate.MINIMUM_BODY_WEIGHT);
					
					// Break
					break;
				
				// GRIN wallet
				case Consensus.GRIN_WALLET_TYPE:
				
					// Get body weight from the number of inputs, outputs, and kernels
					var bodyWeight = Slate.getWeight(numberOfInputs, numberOfOutputs, numberOfKernels);
				
					// Break
					break;
			}
			
			// Get transaction fee from body weight and the base fee
			var transactionFee = bodyWeight.multipliedBy(baseFee);
			
			// Return transaction fee
			return transactionFee;
		}
		
		// Uncompact uint64
		static uncompactUint64(bitReader, hasHundreds) {
		
			// Check if has hundreds
			if(hasHundreds === true) {
		
				// Get number of hundreds
				var numberOfhundreds = bitReader.getBits(Slate.COMPACT_NUMBER_OF_HUNDREDS_LENGTH);
			}
			
			// Get number of digits
			var numberOfDigits = bitReader.getBits(Slate.COMPACT_NUMBER_OF_DIGITS_LENGTH) + 1;
			
			// Initialize digit bytes
			var digitBytes = new Uint8Array(1 + Math.floor((numberOfDigits - 1) / Common.BITS_IN_A_BYTE));
			
			// Go through all digits
			for(var i = 0, j = numberOfDigits; j > 0; ++i, j -= Common.BITS_IN_A_BYTE) {
			
				// Get digit byte
				digitBytes[i] = bitReader.getBits(Math.min(j, Common.BITS_IN_A_BYTE));
			}
			
			// Check if number of digits isn't an exact number of bytes
			if(numberOfDigits > Common.BITS_IN_A_BYTE && numberOfDigits % Common.BITS_IN_A_BYTE !== 0) {
			
				// Go through all digit bytes backwards
				for(var i = digitBytes["length"] - 1; i >= 0; --i) {
				
					// Check if byte isn't the last byte
					if(i !== digitBytes["length"] - 1) {
					
						// Shift bits in the byte right
						digitBytes[i] >>>= Common.BITS_IN_A_BYTE - numberOfDigits % Common.BITS_IN_A_BYTE;
					}
					
					// Check if byte isn't the first byte
					if(i !== 0) {
					
						// Update byte with shifted bits from the next byte
						digitBytes[i] |= digitBytes[i - 1] << (numberOfDigits % Common.BITS_IN_A_BYTE);
					}
				}
			}
			
			// Get result of digit bytes
			var result = new BigNumber(Common.HEX_PREFIX + Common.toHexString(digitBytes));
			
			// Check if has hundreds
			if(hasHundreds === true) {
			
				// Go through all hundreds
				for(var i = 0; i < numberOfhundreds; ++i) {
				
					// Update result
					result = result.multipliedBy(Slate.COMPACT_HUNDREDS_SCALING_FACTOR);
				}
			}
			
			// Return result
			return result;
		}
		
		// Compact uint64
		static compactUint64(value, hasHundreds, bitWriter) {
		
			// Check if has hundreds
			if(hasHundreds === true) {
			
				// Initialize number of hundreds
				var numberOfhundreds = 0;
				
				// Go through all hundreds in the value
				while(value.modulo(Slate.COMPACT_HUNDREDS_SCALING_FACTOR).isZero() === true && numberOfhundreds < Math.pow(2, Slate.COMPACT_NUMBER_OF_HUNDREDS_LENGTH) - 1) {
				
					// Remove hundred from the value
					value = value.dividedToIntegerBy(Slate.COMPACT_HUNDREDS_SCALING_FACTOR);
					
					// Increment number of hundreds
					++numberOfhundreds;
				}
			}
			
			// Initialize number of digits
			var numberOfDigits = 1;
			
			// Go through all digits in the value
			for(var i = new BigNumber(1); numberOfDigits < Math.pow(2, Slate.COMPACT_NUMBER_OF_DIGITS_LENGTH) && i.isLessThan(value) === true; i = i.multipliedBy(2)) {
			
				// Increment number of digits
				++numberOfDigits;
			}
			
			// Check if has hundreds
			if(hasHundreds === true) {
			
				// Write number of hundreds
				bitWriter.setBits(numberOfhundreds, Slate.COMPACT_NUMBER_OF_HUNDREDS_LENGTH);
			}
			
			// Write number of digits
			bitWriter.setBits(numberOfDigits - 1, Slate.COMPACT_NUMBER_OF_DIGITS_LENGTH);
			
			// Write value
			var bytes = value.toBytes(BigNumber.BIG_ENDIAN, Math.ceil(numberOfDigits / Common.BITS_IN_A_BYTE));
			
			// Go through all bytes
			for(var i = 0; i < bytes["length"]; ++i) {
			
				// Check if there isn't an exact number of bytes
				if(numberOfDigits % Common.BITS_IN_A_BYTE !== 0) {
				
					// Check if not the last byte
					if(i !== bytes["length"] - 1) {
					
						// Remove upper bits
						bytes[i] <<= Common.BITS_IN_A_BYTE - numberOfDigits % Common.BITS_IN_A_BYTE;
					
						// Include lower bits from next byte
						bytes[i] |= bytes[i + 1] >>> (numberOfDigits % Common.BITS_IN_A_BYTE);
						
						// Write byte
						bitWriter.setBits(bytes[i], Common.BITS_IN_A_BYTE);
					}
					
					// Otherwise
					else {
					
						// Write byte
						bitWriter.setBits(bytes[i], numberOfDigits % Common.BITS_IN_A_BYTE);
					}
				}
				
				// Otherwise
				else {
				
					// Write byte
					bitWriter.setBits(bytes[i], Common.BITS_IN_A_BYTE);
				}
			}
		}
		
		// Uncompact public key
		static uncompactPublicKey(bitReader) {
		
			// Get number of bytes
			var numberOfBytes = bitReader.getBits(Slate.COMPACT_PUBLIC_KEY_LENGTH_LENGTH);
			
			// Return public key
			return bitReader.getBytes(numberOfBytes);
		}
		
		// Compact public key
		static compactPublicKey(publicKey, bitWriter) {
		
			// Check if public key is too long
			if(publicKey["length"] >= Math.pow(2, Slate.COMPACT_PUBLIC_KEY_LENGTH_LENGTH)) {
			
				// Throw error
				throw "Public key is too long.";
			}
		
			// Write number of bytes
			bitWriter.setBits(publicKey["length"], Slate.COMPACT_PUBLIC_KEY_LENGTH_LENGTH);
			
			// Write public key
			bitWriter.setBytes(publicKey);
		}
		
		// Parse slate asynchronous
		static parseSlateAsynchronous(serializedSlate, isMainnet, purpose, initialSendSlate) {
		
			// Check if serialized slate is binary
			if(serializedSlate instanceof Uint8Array === true) {
		
				// Get serialized slate data
				var serializedSlateData = serializedSlate.slice()["buffer"];
			}
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Return sending parse slate request
				return Slate.sendRequest([
				
					// Type
					Slate.PARSE_SLATE_REQUEST_TYPE,
					
					// Serialized slate
					(serializedSlate instanceof Uint8Array === true) ? serializedSlateData : Common.serializeObject(serializedSlate),
					
					// Is mainnet
					isMainnet,
					
					// Purpose
					purpose,
					
					// Initial send slate
					Common.serializeObject(initialSendSlate)
				
				], (serializedSlate instanceof Uint8Array === true) ? [
				
					// Serialized slate data
					serializedSlateData
				
				] : []).then(function(response) {
				
					// Check if response is valid
					if(response !== Slate.STATUS_FAILED_VALUE) {
					
						// Resolve response
						resolve(Common.unserializeObject(response));
					}
					
					// Otherwise
					else {
					
						// Reject
						reject();
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Add outputs asynchronous
		static addOutputsAsynchronous(slate, outputs, updateKernel = true) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Return sending add outputs request
				return Slate.sendRequest([
				
					// Type
					Slate.ADD_OUTPUTS_REQUEST_TYPE,
					
					// Slate
					Common.serializeObject(slate),
					
					// Outputs
					Common.serializeObject(outputs),
					
					// Update kernel
					updateKernel
				
				]).then(function(response) {
				
					// Check if response is valid
					if(response !== Slate.STATUS_FAILED_VALUE) {
					
						// Resolve response
						resolve(Common.unserializeObject(response));
					}
					
					// Otherwise
					else {
					
						// Reject
						reject();
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Add inputs asynchronous
		static addInputsAsynchronous(slate, inputs, updateKernel = true, expectedNumberOfOutputs = 0) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Return sending add inputs request
				return Slate.sendRequest([
				
					// Type
					Slate.ADD_INPUTS_REQUEST_TYPE,
					
					// Slate
					Common.serializeObject(slate),
					
					// Inputs
					Common.serializeObject(inputs),
					
					// Update kernel
					updateKernel,
					
					// Expected number of outputs
					expectedNumberOfOutputs
				
				]).then(function(response) {
				
					// Check if response is valid
					if(response !== Slate.STATUS_FAILED_VALUE) {
					
						// Resolve response
						resolve(Common.unserializeObject(response));
					}
					
					// Otherwise
					else {
					
						// Reject
						reject();
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Verify after finalize asynchronous
		static verifyAfterFinalizeAsynchronous(slate, baseFee, isMainnet) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Return sending verify after finalize request
				return Slate.sendRequest([
				
					// Type
					Slate.VERIFY_AFTER_FINALIZE_REQUEST_TYPE,
					
					// Slate
					Common.serializeObject(slate),
					
					// Base fee
					Common.serializeObject(baseFee),
					
					// Is mainnet
					isMainnet
				
				]).then(function(response) {
				
					// Check if response is valid
					if(response !== Slate.STATUS_FAILED_VALUE) {
					
						// Resolve
						resolve();
					}
					
					// Otherwise
					else {
					
						// Reject
						reject();
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// No time to live cut off height
		static get NO_TIME_TO_LIVE_CUT_OFF_HEIGHT() {
		
			// Return no time to live cut off height
			return null;
		}
		
		// No payment proof
		static get NO_PAYMENT_PROOF() {
		
			// Return payment proof
			return null;
		}
		
		// Newest version
		static get NEWEST_VERSION() {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC wallet
				case Consensus.MWC_WALLET_TYPE:
		
					// Return newest version
					return Slate.VERSION_SLATEPACK;
				
				// GRIN wallet
				case Consensus.GRIN_WALLET_TYPE:
				
					// Return newest version
					return Slate.VERSION_FOUR;
				
				// EPIC wallet
				case Consensus.EPIC_WALLET_TYPE:
				
					// Return newest version
					return Slate.VERSION_THREE;
			}
		}
		
		// No secret nonce
		static get NO_SECRET_NONCE() {
		
			// Return no secret nonce
			return null;
		}
		
		// Unknown height
		static get UNKNOWN_HEIGHT() {
		
			// Return unknown height
			return null;
		}
		
		// Minimum fee
		static get MINIMUM_FEE() {
		
			// Return minimum fee
			return 1;
		}
		
		// Maximum fee
		static get MAXIMUM_FEE() {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC or EPIC wallet
				case Consensus.MWC_WALLET_TYPE:
				case Consensus.EPIC_WALLET_TYPE:
		
					// Return maximum fee
					return Number.POSITIVE_INFINITY;
				
				// GRIN wallet
				case Consensus.GRIN_WALLET_TYPE:
				
					// Return maximum fee
					return Math.pow(2, 40) - 1;
			}
		}
		
		// Initialize request type
		static get INITIALIZE_REQUEST_TYPE() {
		
			// Return initialize request type
			return 0;
		}
		
		// Uninitialize request type
		static get UNINITIALIZE_REQUEST_TYPE() {
		
			// Return uninitialize request type
			return Slate.INITIALIZE_REQUEST_TYPE + 1;
		}
		
		// Parse slate request type
		static get PARSE_SLATE_REQUEST_TYPE() {
		
			// Return parse slate request type
			return Slate.UNINITIALIZE_REQUEST_TYPE + 1;
		}
		
		// Add outputs request type
		static get ADD_OUTPUTS_REQUEST_TYPE() {
		
			// Return add outputs request type
			return Slate.PARSE_SLATE_REQUEST_TYPE + 1;
		}
		
		// Add inputs request type
		static get ADD_INPUTS_REQUEST_TYPE() {
		
			// Return add inputs request type
			return Slate.ADD_OUTPUTS_REQUEST_TYPE + 1;
		}
		
		// Verify after finalize request type
		static get VERIFY_AFTER_FINALIZE_REQUEST_TYPE() {
		
			// Return verify after finalize request type
			return Slate.ADD_INPUTS_REQUEST_TYPE + 1;
		}
		
		// Message request index offset
		static get MESSAGE_REQUEST_INDEX_OFFSET() {
		
			// Return message request index offset
			return 0;
		}
		
		// Message type offset
		static get MESSAGE_TYPE_OFFSET() {
		
			// Return message type offset
			return Slate.MESSAGE_REQUEST_INDEX_OFFSET + 1;
		}
		
		// Message initialize URL query string offset
		static get MESSAGE_INITIALIZE_URL_QUERY_STRING_OFFSET() {
		
			// Return message initialize URL query string offset
			return Slate.MESSAGE_TYPE_OFFSET + 1;
		}
		
		// Message parse slate serialized slate offset
		static get MESSAGE_PARSE_SLATE_SERIALIZED_SLATE_OFFSET() {
		
			// Return message parse slate serialized slate offset
			return Slate.MESSAGE_TYPE_OFFSET + 1;
		}
		
		// Message parse slate is mainnet offset
		static get MESSAGE_PARSE_SLATE_IS_MAINNET_OFFSET() {
		
			// Return message parse slate is mainnet offset
			return Slate.MESSAGE_PARSE_SLATE_SERIALIZED_SLATE_OFFSET + 1;
		}
		
		// Message parse slate purpose offset
		static get MESSAGE_PARSE_SLATE_PURPOSE_OFFSET() {
		
			// Return message parse slate purpose offset
			return Slate.MESSAGE_PARSE_SLATE_IS_MAINNET_OFFSET + 1;
		}
		
		// Message parse slate initial send slate offset
		static get MESSAGE_PARSE_SLATE_INITIAL_SEND_SLATE_OFFSET() {
		
			// Return message parse slate initial send slate offset
			return Slate.MESSAGE_PARSE_SLATE_PURPOSE_OFFSET + 1;
		}
		
		// Message add outputs slate offset
		static get MESSAGE_ADD_OUTPUTS_SLATE_OFFSET() {
		
			// Return message add outputs slate offset
			return Slate.MESSAGE_TYPE_OFFSET + 1;
		}
		
		// Message add outputs outputs offset
		static get MESSAGE_ADD_OUTPUTS_OUTPUTS_OFFSET() {
		
			// Return message add outputs outputs offset
			return Slate.MESSAGE_ADD_OUTPUTS_SLATE_OFFSET + 1;
		}
		
		// Message add outputs update kernel offset
		static get MESSAGE_ADD_OUTPUTS_UPDATE_KERNEL_OFFSET() {
		
			// Return message add outputs update kernel offset
			return Slate.MESSAGE_ADD_OUTPUTS_OUTPUTS_OFFSET + 1;
		}
		
		// Message add inputs slate offset
		static get MESSAGE_ADD_INPUTS_SLATE_OFFSET() {
		
			// Return message add inputs slate offset
			return Slate.MESSAGE_TYPE_OFFSET + 1;
		}
		
		// Message add inputs inputs offset
		static get MESSAGE_ADD_INPUTS_INPUTS_OFFSET() {
		
			// Return message add inputs inputs offset
			return Slate.MESSAGE_ADD_INPUTS_SLATE_OFFSET + 1;
		}
		
		// Message add inputs update kernel offset
		static get MESSAGE_ADD_INPUTS_UPDATE_KERNEL_OFFSET() {
		
			// Return message add inputs update kernel offset
			return Slate.MESSAGE_ADD_INPUTS_INPUTS_OFFSET + 1;
		}
		
		// Message add inputs expected number of outputs offset
		static get MESSAGE_ADD_INPUTS_EXPECTED_NUMBER_OF_OUTPUTS_OFFSET() {
		
			// Return message add inputs expected number of outputs offset
			return Slate.MESSAGE_ADD_INPUTS_UPDATE_KERNEL_OFFSET + 1;
		}
		
		// Message verify after finalize slate offset
		static get MESSAGE_VERIFY_AFTER_FINALIZE_SLATE_OFFSET() {
		
			// Return message verify after finalize slate offset
			return Slate.MESSAGE_TYPE_OFFSET + 1;
		}
		
		// Message verify after finalize base fee offset
		static get MESSAGE_VERIFY_AFTER_FINALIZE_BASE_FEE_OFFSET() {
		
			// Return message verify after finalize base fee offset
			return Slate.MESSAGE_VERIFY_AFTER_FINALIZE_SLATE_OFFSET + 1;
		}
		
		// Message verify after finalize is mainnet offset
		static get MESSAGE_VERIFY_AFTER_FINALIZE_IS_MAINNET_OFFSET() {
		
			// Return message verify after finalize is mainnet offset
			return Slate.MESSAGE_VERIFY_AFTER_FINALIZE_BASE_FEE_OFFSET + 1;
		}
		
		// Status success value
		static get STATUS_SUCCESS_VALUE() {
		
			// Return status success value
			return true;
		}
		
		// Status failed value
		static get STATUS_FAILED_VALUE() {
		
			// Return status failed value
			return false;
		}
	
	// Private
	
		// Reset
		reset() {
		
			// Set version to newest version
			this.version = Slate.NEWEST_VERSION;
			
			// Set original version to version
			this.originalVersion = this.getVersion();
			
			// Set block header version to the first header version
			this.blockHeaderVersion = Consensus.LEGACY_HEADER_VERSION;
		
			// Set number of participants to the default number of participants
			this.numberOfParticipants = Slate.DEFAULT_NUMBER_OF_PARTICIPANTS;
			
			// Set offset to zero offset
			this.offset = Slate.ZERO_OFFSET;
			
			// Set inputs to nothing
			this.inputs = [];
			
			// Set outputs to nothing
			this.outputs = [];
			
			// Set kernels to nothing
			this.kernels = [];
			
			// Set amount to minimum amount
			this.amount = new BigNumber(Slate.MINIMUM_AMOUNT);
			
			// Set fee to minimum fee
			this.fee = new BigNumber(Slate.MINIMUM_FEE);
			
			// Set height to unknown height
			this.height = Slate.UNKNOWN_HEIGHT;
			
			// Set lock height to no lock height
			this.lockHeight = Slate.NO_LOCK_HEIGHT;
			
			// Set relative height to no relative height
			this.relativeHeight = Slate.NO_RELATIVE_HEIGHT;
			
			// Set time to live cutoff height to no time to live cut off height
			this.timeToLiveCutOffHeight = Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
			
			// Set participants to nothing
			this.participants = [];
			
			// Set receiver address to no receiver address
			this.receiverAddress = Slate.NO_RECEIVER_ADDRESS;
			
			// Set receiver signature to no receiver signature
			this.receiverSignature = Slate.NO_RECEIVER_SIGNATURE;
			
			// Set sender address to no sender address
			this.senderAddress = Slate.NO_SENDER_ADDRESS;
			
			// Change ID
			this.changeId();
		}
		
		// Unserialize
		unserialize(serializedSlate, isMainnet, purpose, initialSendSlate) {
		
			// Detect slate's version
			var detectedVersion = Slate.detectVersion(serializedSlate, isMainnet);
			
			// Check if version is unknown or not supported
			if(detectedVersion === Slate.UNKNOWN_VERSION || Slate.SUPPORTED_VERSIONS.indexOf((detectedVersion instanceof BigNumber === true) ? "V" + detectedVersion.toFixed() : detectedVersion) === Common.INDEX_NOT_FOUND) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check slate's version
			switch((detectedVersion instanceof BigNumber === true) ? detectedVersion.toFixed() : detectedVersion) {
			
				// Version two and three
				case Slate.VERSION_TWO.toFixed():
				case Slate.VERSION_THREE.toFixed():
				
					// Check if serialized slate's version info isn't supported
					if("version_info" in serializedSlate === false || Object.isObject(serializedSlate["version_info"]) === false) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Check if version info's version isn't supported
					if("version" in serializedSlate["version_info"] === false || (Common.isNumberString(serializedSlate["version_info"]["version"]) === false && serializedSlate["version_info"]["version"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["version_info"]["version"])).isInteger() === false || (new BigNumber(serializedSlate["version_info"]["version"])).isLessThan(Slate.VERSION_ONE) === true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set version to serialized slate's version
					this.version = new BigNumber(serializedSlate["version_info"]["version"]);
					
					// Check if serialized slate's number of participants isn't supported
					if("num_participants" in serializedSlate === false || (Common.isNumberString(serializedSlate["num_participants"]) === false && serializedSlate["num_participants"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["num_participants"])).isInteger() === false || (new BigNumber(serializedSlate["num_participants"])).isLessThan(Slate.MINIMUM_NUMBER_OF_PARTICIPANTS) === true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set number of participants to serialized slate's number of participants
					this.numberOfParticipants = new BigNumber(serializedSlate["num_participants"]);
					
					// Check if serialized slate's amount isn't supported
					if("amount" in serializedSlate === false || (Common.isNumberString(serializedSlate["amount"]) === false && serializedSlate["amount"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["amount"])).isInteger() === false || (new BigNumber(serializedSlate["amount"])).isLessThan(Slate.MINIMUM_AMOUNT) === true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set amount to serialized slate's amount
					this.amount = new BigNumber(serializedSlate["amount"]);
					
					// Check if serialized slate's fee isn't supported
					if("fee" in serializedSlate === false || (Common.isNumberString(serializedSlate["fee"]) === false && serializedSlate["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["fee"])).isInteger() === false || (new BigNumber(serializedSlate["fee"])).isLessThan(Slate.MINIMUM_FEE) === true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set fee to serialized slate's fee
					this.fee = new BigNumber(serializedSlate["fee"]);
					
					// Check if serialized slate's height isn't supported
					if("height" in serializedSlate === false || (Common.isNumberString(serializedSlate["height"]) === false && serializedSlate["height"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["height"])).isInteger() === false || (new BigNumber(serializedSlate["height"])).isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set height to serialized slate's height
					this.height = new BigNumber(serializedSlate["height"]);
					
					// Check if serialized slate's lock height isn't supported
					if("lock_height" in serializedSlate === false || (Common.isNumberString(serializedSlate["lock_height"]) === false && serializedSlate["lock_height"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["lock_height"])).isInteger() === false || (new BigNumber(serializedSlate["lock_height"])).isLessThan(Slate.NO_LOCK_HEIGHT) === true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set lock height to serialized slate's lock height
					this.lockHeight = new BigNumber(serializedSlate["lock_height"]);
					
					// Check if version info's original version isn't supported
					if("orig_version" in serializedSlate["version_info"] === false || (Common.isNumberString(serializedSlate["version_info"]["orig_version"]) === false && serializedSlate["version_info"]["orig_version"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["version_info"]["orig_version"])).isInteger() === false || (new BigNumber(serializedSlate["version_info"]["orig_version"])).isLessThan(Slate.VERSION_ONE) === true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set original version to serialized slate's original version
					this.originalVersion = new BigNumber(serializedSlate["version_info"]["orig_version"]);
					
					// Check if version info's block header version isn't supported
					if("block_header_version" in serializedSlate["version_info"] === false || (Common.isNumberString(serializedSlate["version_info"]["block_header_version"]) === false && serializedSlate["version_info"]["block_header_version"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["version_info"]["block_header_version"])).isInteger() === false || Consensus.isValidHeaderVersion(isMainnet, this.getHeight(), new BigNumber(serializedSlate["version_info"]["block_header_version"])) === false) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set block header version to serialized slate's block header version
					this.blockHeaderVersion = new BigNumber(serializedSlate["version_info"]["block_header_version"]);
					
					// Check if version is at least version three
					if(this.getVersion().isGreaterThanOrEqualTo(Slate.VERSION_THREE) === true) {
					
						// Check if serialized slate's time to live cut off height isn't supported
						if("ttl_cutoff_height" in serializedSlate === false || (serializedSlate["ttl_cutoff_height"] !== null && ((Common.isNumberString(serializedSlate["ttl_cutoff_height"]) === false && serializedSlate["ttl_cutoff_height"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["ttl_cutoff_height"])).isInteger() === false || (new BigNumber(serializedSlate["ttl_cutoff_height"])).isLessThanOrEqualTo(this.getHeight()) === true || (new BigNumber(serializedSlate["ttl_cutoff_height"])).isLessThan(this.getLockHeight()) === true))) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Set time to live cut off height to serialized slate's time to live cut off height
						this.timeToLiveCutOffHeight = (serializedSlate["ttl_cutoff_height"] !== null) ? new BigNumber(serializedSlate["ttl_cutoff_height"]) : Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
						
						// Check if serialized slate's payment proof isn't supported
						if("payment_proof" in serializedSlate === false || (serializedSlate["payment_proof"] !== null && Object.isObject(serializedSlate["payment_proof"]) === false)) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if payment proof's receiver address isn't supported
						if(serializedSlate["payment_proof"] !== null && ("receiver_address" in serializedSlate["payment_proof"] === false || typeof serializedSlate["payment_proof"]["receiver_address"] !== "string")) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if serialized slate provided a payment proof
						if(serializedSlate["payment_proof"] !== null) {
						
							// Check wallet type
							switch(Consensus.getWalletType()) {
							
								// MWC wallet
								case Consensus.MWC_WALLET_TYPE:
						
									// Check receiver address's length
									switch(serializedSlate["payment_proof"]["receiver_address"]["length"]) {
									
										// MQS address length
										case Mqs.ADDRESS_LENGTH:
										
											// Try
											try {
											
												// Get public key from receiver's MQS address
												Mqs.mqsAddressToPublicKey(serializedSlate["payment_proof"]["receiver_address"], isMainnet);
											}
											
											// Catch errors
											catch(error) {
											
												// Throw error
												throw "Unsupported slate.";
											}
											
											// Break
											break;
										
										// Tor address length
										case Tor.ADDRESS_LENGTH:
										
											// Try
											try {
											
												// Get public key from receiver's Tor address
												Tor.torAddressToPublicKey(serializedSlate["payment_proof"]["receiver_address"]);
											}
											
											// Catch errors
											catch(error) {
											
												// Throw error
												throw "Unsupported slate.";
											}
										
											// Break
											break;
										
										// Default
										default:
										
											// Throw error
											throw "Unsupported slate.";
									}
									
									// Set receiver address to serialized slate's receiver address
									this.receiverAddress = serializedSlate["payment_proof"]["receiver_address"];
									
									// Break
									break;
								
								// EPIC wallet
								case Consensus.EPIC_WALLET_TYPE:
						
									// Check if payment proof's receiver address isn't supported
									if(Common.isHexString(serializedSlate["payment_proof"]["receiver_address"]) === false || Common.hexStringLength(serializedSlate["payment_proof"]["receiver_address"]) !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
									
										// Throw error
										throw "Unsupported slate.";
									}
									
									// Try
									try {
									
										// Set receiver address to the Tor address created from the receiver's public key
										this.receiverAddress = Tor.publicKeyToTorAddress(Common.fromHexString(serializedSlate["payment_proof"]["receiver_address"]));
									}
										
									// Catch errors
									catch(error) {
									
										// Throw error
										throw "Unsupported slate.";
									}
									
									// Break
									break;
							}
						}
						
						// Otherwise
						else {
						
							// Set receiver address to no receiver address
							this.receiverAddress = Slate.NO_RECEIVER_ADDRESS;
						}
						
						// Check if payment proof's receiver signature isn't supported
						if(serializedSlate["payment_proof"] !== null && ("receiver_signature" in serializedSlate["payment_proof"] === false || (serializedSlate["payment_proof"]["receiver_signature"] !== null && Common.isHexString(serializedSlate["payment_proof"]["receiver_signature"]) === false))) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if serialized slate provided a payment proof and a serialized signature
						if(serializedSlate["payment_proof"] !== null && serializedSlate["payment_proof"]["receiver_signature"] !== null) {
						
							// Check wallet type
							switch(Consensus.getWalletType()) {
							
								// MWC wallet
								case Consensus.MWC_WALLET_TYPE:
								
									// Check if payment proof's receiver signature isn't supported
									if(Common.hexStringLength(serializedSlate["payment_proof"]["receiver_signature"]) > Crypto.MAXIMUM_MESSAGE_HASH_SIGNATURE_LENGTH && Common.hexStringLength(serializedSlate["payment_proof"]["receiver_signature"]) !== Crypto.ED25519_SIGNATURE_LENGTH) {
									
										// Throw error
										throw "Unsupported slate.";
									}
									
									// Break
									break;
								
								// EPIC wallet
								case Consensus.EPIC_WALLET_TYPE:
								
									// Check if payment proof's receiver signature isn't supported
									if(Common.hexStringLength(serializedSlate["payment_proof"]["receiver_signature"]) !== Crypto.ED25519_SIGNATURE_LENGTH) {
									
										// Throw error
										throw "Unsupported slate.";
									}
									
									// Break
									break;
							}
						}
						
						// Set receiver signature to serialized slate's receiver signature
						this.receiverSignature = (serializedSlate["payment_proof"] !== null && serializedSlate["payment_proof"]["receiver_signature"] !== null) ? Common.fromHexString(serializedSlate["payment_proof"]["receiver_signature"]) : Slate.NO_RECEIVER_SIGNATURE;
						
						// Check if payment proof's sender address isn't supported
						if(serializedSlate["payment_proof"] !== null && ("sender_address" in serializedSlate["payment_proof"] === false || typeof serializedSlate["payment_proof"]["sender_address"] !== "string")) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if serialized slate provided a payment proof
						if(serializedSlate["payment_proof"] !== null) {
						
							// Check wallet type
							switch(Consensus.getWalletType()) {
							
								// MWC wallet
								case Consensus.MWC_WALLET_TYPE:
						
									// Check sender address's length
									switch(serializedSlate["payment_proof"]["sender_address"]["length"]) {
									
										// MQS address length
										case Mqs.ADDRESS_LENGTH:
										
											// Try
											try {
											
												// Get public key from sender's MQS address
												Mqs.mqsAddressToPublicKey(serializedSlate["payment_proof"]["sender_address"], isMainnet);
											}
											
											// Catch errors
											catch(error) {
											
												// Throw error
												throw "Unsupported slate.";
											}
											
											// Break
											break;
										
										// Tor address length
										case Tor.ADDRESS_LENGTH:
										
											// Try
											try {
											
												// Get public key from sender's Tor address
												Tor.torAddressToPublicKey(serializedSlate["payment_proof"]["sender_address"]);
											}
											
											// Catch errors
											catch(error) {
											
												// Throw error
												throw "Unsupported slate.";
											}
										
											// Break
											break;
										
										// Default
										default:
										
											// Throw error
											throw "Unsupported slate.";
									}
									
									// Set sender address to serialized slate's sender address
									this.senderAddress = serializedSlate["payment_proof"]["sender_address"];
									
									// Break
									break;
								
								// EPIC wallet
								case Consensus.EPIC_WALLET_TYPE:
						
									// Check if payment proof's sender address isn't supported
									if(Common.isHexString(serializedSlate["payment_proof"]["sender_address"]) === false || Common.hexStringLength(serializedSlate["payment_proof"]["sender_address"]) !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
									
										// Throw error
										throw "Unsupported slate.";
									}
									
									// Try
									try {
									
										// Set sender address to the Tor address created from the sender's public key
										this.senderAddress = Tor.publicKeyToTorAddress(Common.fromHexString(serializedSlate["payment_proof"]["sender_address"]));
									}
										
									// Catch errors
									catch(error) {
									
										// Throw error
										throw "Unsupported slate.";
									}
									
									// Break
									break;
							}
						}
						
						// Otherwise
						else {
						
							// Set sender address to no sender address
							this.senderAddress = Slate.NO_SENDER_ADDRESS;
						}
					}
					
					// Check if serialized slate's ID isn't supported
					if("id" in serializedSlate === false || typeof serializedSlate["id"] !== "string") {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Try
					try {
					
						// Set ID to serialized slate's ID
						this.id = new Uuid(serializedSlate["id"]);
					
						// Check if ID isn't a random UUID
						if(this.getId().isRandom() === false) {
						
							// Throw error
							throw "Unsupported slate.";
						}
					}
					
					// Catch errors
					catch(error) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Check if serialized slate's transaction isn't supported
					if("tx" in serializedSlate === false || Object.isObject(serializedSlate["tx"]) === false) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Check if transaction's body isn't supported
					if("body" in serializedSlate["tx"] === false || Object.isObject(serializedSlate["tx"]["body"]) === false) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Check if body's inputs are invalid
					if("inputs" in serializedSlate["tx"]["body"] === false || Array.isArray(serializedSlate["tx"]["body"]["inputs"]) === false) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Try
					try {
					
						// Set self
						var self = this;
					
						// Set inputs to serialized slate's inputs
						this.inputs = serializedSlate["tx"]["body"]["inputs"].map(function(input) {
						
							// Check if input isn't supported
							if(Object.isObject(input) === false) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						
							// Return slate input
							return new SlateInput(input, self);
						});
					}
					
					// Catch errors
					catch(error) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Check if no inputs exist
					if(this.getInputs()["length"] === 0) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Check if body's outputs are invalid
					if("outputs" in serializedSlate["tx"]["body"] === false || Array.isArray(serializedSlate["tx"]["body"]["outputs"]) === false) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Try
					try {
					
						// Set outputs to serialized slate's outputs
						this.outputs = serializedSlate["tx"]["body"]["outputs"].map(function(output) {
						
							// Check if output isn't supported
							if(Object.isObject(output) === false) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						
							// Return slate output
							return new SlateOutput(output, self);
						});
					}
					
					// Catch errors
					catch(error) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Check if body's kernels are invalid
					if("kernels" in serializedSlate["tx"]["body"] === false || Array.isArray(serializedSlate["tx"]["body"]["kernels"]) === false) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Try
					try {
					
						// Set kernels to serialized slate's kernels
						this.kernels = serializedSlate["tx"]["body"]["kernels"].map(function(kernel) {
						
							// Check if kernel isn't supported
							if(Object.isObject(kernel) === false) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						
							// Return slate kernel
							return new SlateKernel(kernel, self, isMainnet);
						});
					}
					
					// Catch errors
					catch(error) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Check if transaction's offset isn't supported
					if("offset" in serializedSlate["tx"] === false || Common.isHexString(serializedSlate["tx"]["offset"]) === false || Common.hexStringLength(serializedSlate["tx"]["offset"]) !== Crypto.BLINDING_FACTOR_LENGTH || Secp256k1Zkp.isValidSecretKey(Common.fromHexString(serializedSlate["tx"]["offset"])) !== true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Set offset to serialized slate's offset
					this.offset = Common.fromHexString(serializedSlate["tx"]["offset"]);
					
					// Check if serialized slate's participant data isn't supported
					if("participant_data" in serializedSlate === false || Array.isArray(serializedSlate["participant_data"]) === false || this.getNumberOfParticipants().isLessThan(serializedSlate["participant_data"]["length"]) === true) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Try
					try {
					
						// Set participants to serialized slate's participant data
						this.participants = serializedSlate["participant_data"].map(function(participant) {
						
							// Check if participant isn't supported
							if(Object.isObject(participant) === false) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						
							// Return slate participant
							return new SlateParticipant(participant, self);
						});
					}
					
					// Catch errors
					catch(error) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Break
					break;
				
				// Version Slatepack
				case Slate.VERSION_SLATEPACK:
				
					// Initialize bit reader for the serialized slate
					var bitReader = new BitReader(serializedSlate);
					
					// Try
					try {
					
						// Set version to detected version
						this.version = detectedVersion;
						
						// Set original version to version
						this.originalVersion = this.getVersion();
					
						// Check if serialized slate's purpose isn't correct
						if(bitReader.getBits(Slate.COMPACT_SLATE_PURPOSE_LENGTH) !== purpose) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Set ID to serialized slate's ID
						this.id = new Uuid(Uuid.serializeData(bitReader.getBytes(Uuid.BYTE_LENGTH)));
					
						// Check if ID isn't a random UUID
						if(this.getId().isRandom() === false) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Get serialized slate's mainnet
						var mainnet = bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE;
						
						// Check if network isn't supported
						if(mainnet !== isMainnet) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if purpose is send initial
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
						
							// Set amount to serialized slate's amount
							this.amount = Slate.uncompactUint64(bitReader, true);
							
							// Check if serialized slate's amount isn't supported
							if(this.getAmount().isLessThan(Slate.MINIMUM_AMOUNT) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set fee to serialized slate's fee
							this.fee = Slate.uncompactUint64(bitReader, true);
							
							// Check if serialized slate's fee isn't supported
							if(this.getFee().isLessThan(Slate.MINIMUM_FEE) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						}
						
						// Set height to serialized slate's height
						this.height = Slate.uncompactUint64(bitReader, false);
						
						// Check if serialized slate's height isn't supported
						if(this.getHeight().isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Set block header version to header version at the height
						this.blockHeaderVersion = Consensus.getHeaderVersion(isMainnet, this.getHeight());
						
						// Set lock height to serialized slate's lock height
						this.lockHeight = Slate.uncompactUint64(bitReader, false);
						
						// Check if serialized slate's lock height isn't supported
						if(this.getLockHeight().isLessThan(Slate.NO_LOCK_HEIGHT) === true) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if serialized slate contains a time to live cut off height
						if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
						
							// Set time to live cut off height to serialized slate's time to live cut off height
							this.timeToLiveCutOffHeight = Slate.uncompactUint64(bitReader, false);
							
							// Check if serialized slate's time to live cut off height isn't supported
							if(this.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(this.getHeight()) === true || this.getTimeToLiveCutOffHeight().isLessThan(this.getLockHeight()) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						}
						
						// Otherwise
						else {
						
							// Set time to live cut off height to serialized slate's time to live cut off height
							this.timeToLiveCutOffHeight = Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
						}
						
						// Check if purpose is send initial
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
						
							// Add serialized slate's participant to the participants
							this.participants.push(new SlateParticipant(bitReader, this));
						
							// Check if serialized slate contains proof addresses
							if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
							
								// Set sender address to serialized slate's sender address
								this.senderAddress = Slate.uncompactProofAddress(bitReader, isMainnet);
								
								// Check sender address's length
								switch(this.getSenderAddress()["length"]) {
								
									// MQS address length
									case Mqs.ADDRESS_LENGTH:
									
										// Try
										try {
										
											// Get public key from sender's MQS address
											Mqs.mqsAddressToPublicKey(this.getSenderAddress(), isMainnet);
										}
										
										// Catch errors
										catch(error) {
										
											// Throw error
											throw "Unsupported slate.";
										}
										
										// Break
										break;
									
									// Tor address length
									case Tor.ADDRESS_LENGTH:
									
										// Try
										try {
										
											// Get public key from sender's Tor address
											Tor.torAddressToPublicKey(this.getSenderAddress());
										}
										
										// Catch errors
										catch(error) {
										
											// Throw error
											throw "Unsupported slate.";
										}
									
										// Break
										break;
									
									// Default
									default:
									
										// Throw error
										throw "Unsupported slate.";
								}
								
								// Set receiver address to serialized slate's receiver address
								this.receiverAddress = Slate.uncompactProofAddress(bitReader, isMainnet);
								
								// Check receiver address's length
								switch(this.getReceiverAddress()["length"]) {
								
									// MQS address length
									case Mqs.ADDRESS_LENGTH:
									
										// Try
										try {
										
											// Get public key from receiver's MQS address
											Mqs.mqsAddressToPublicKey(this.getReceiverAddress(), isMainnet);
										}
										
										// Catch errors
										catch(error) {
										
											// Throw error
											throw "Unsupported slate.";
										}
										
										// Break
										break;
									
									// Tor address length
									case Tor.ADDRESS_LENGTH:
									
										// Try
										try {
										
											// Get public key from receiver's Tor address
											Tor.torAddressToPublicKey(this.getReceiverAddress());
										}
										
										// Catch errors
										catch(error) {
										
											// Throw error
											throw "Unsupported slate.";
										}
									
										// Break
										break;
									
									// Default
									default:
									
										// Throw error
										throw "Unsupported slate.";
								}
							}
							
							// Otherwise
							else {
							
								// Set receiver address to serialized slate's receiver address
								this.receiverAddress = Slate.NO_RECEIVER_ADDRESS;
								
								// Set sender address to serialized slate's sender address
								this.senderAddress = Slate.NO_SENDER_ADDRESS;
							}
							
							// Set kernels to serialized slate's kernels
							this.kernels.push(new SlateKernel(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()));
						}
						
						// Check if purpose is send response
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
						
							// Set number of participants to the initial send slate's number of participants
							this.numberOfParticipants = initialSendSlate.getNumberOfParticipants();
						
							// Set amount to inital send slate's amount
							this.amount = initialSendSlate.getAmount();
							
							// Set fee to inital send slate's fee
							this.fee = initialSendSlate.getFee();
							
							// Set inputs to initial send slate's inputs
							this.inputs = initialSendSlate.getInputs().map(function(input) {
							
								// Return input
								return new SlateInput(input.getFeatures(), input.getCommit());
							});
							
							// Set outputs to initial send slate's outputs
							this.outputs = initialSendSlate.getOutputs().map(function(output) {
							
								// Return output
								return new SlateOutput(output.getFeatures(), output.getCommit(), output.getProof());
							});
							
							// Set participants to initial send slate's participants
							this.participants = initialSendSlate.getParticipants().map(function(participant) {
							
								// Return participant
								return new SlateParticipant(participant.getId(), participant.getPublicBlindExcess(), participant.getPublicNonce(), participant.getPartialSignature(), participant.getMessage(), participant.getMessageSignature());
							});
						
							// Set offset to serialized slate's offset
							this.offset = bitReader.getBytes(Crypto.BLINDING_FACTOR_LENGTH);
							
							// Check if serialized slate's offset isn't supported
							if(Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Go through all serialized slate's outputs
							var outputs = [];
							do {
							
								// Append serialized slate's output to outputs
								outputs.push(new SlateOutput(bitReader, this));
							
							} while(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE);
							
							// Add outputs
							this.addOutputs(outputs, false);
							
							// Go through all serialized slate's kernels
							do {
							
								// Append serialized slate's kernel to kernels
								this.kernels.push(new SlateKernel(bitReader, this, isMainnet));
							
							} while(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE);
							
							// Add serialized slate's participant to the participants
							this.participants.push(new SlateParticipant(bitReader, this));
							
							// Check if serialized slate contains proof addresses
							if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
							
								// Set sender address to serialized slate's sender address
								this.senderAddress = Slate.uncompactProofAddress(bitReader, isMainnet);
								
								// Check sender address's length
								switch(this.getSenderAddress()["length"]) {
								
									// MQS address length
									case Mqs.ADDRESS_LENGTH:
									
										// Try
										try {
										
											// Get public key from sender's MQS address
											Mqs.mqsAddressToPublicKey(this.getSenderAddress(), isMainnet);
										}
										
										// Catch errors
										catch(error) {
										
											// Throw error
											throw "Unsupported slate.";
										}
										
										// Break
										break;
									
									// Tor address length
									case Tor.ADDRESS_LENGTH:
									
										// Try
										try {
										
											// Get public key from sender's Tor address
											Tor.torAddressToPublicKey(this.getSenderAddress());
										}
										
										// Catch errors
										catch(error) {
										
											// Throw error
											throw "Unsupported slate.";
										}
									
										// Break
										break;
									
									// Default
									default:
									
										// Throw error
										throw "Unsupported slate.";
								}
								
								// Set receiver address to serialized slate's receiver address
								this.receiverAddress = Slate.uncompactProofAddress(bitReader, isMainnet);
								
								// Check receiver address's length
								switch(this.getReceiverAddress()["length"]) {
								
									// MQS address length
									case Mqs.ADDRESS_LENGTH:
									
										// Try
										try {
										
											// Get public key from receiver's MQS address
											Mqs.mqsAddressToPublicKey(this.getReceiverAddress(), isMainnet);
										}
										
										// Catch errors
										catch(error) {
										
											// Throw error
											throw "Unsupported slate.";
										}
										
										// Break
										break;
									
									// Tor address length
									case Tor.ADDRESS_LENGTH:
									
										// Try
										try {
										
											// Get public key from receiver's Tor address
											Tor.torAddressToPublicKey(this.getReceiverAddress());
										}
										
										// Catch errors
										catch(error) {
										
											// Throw error
											throw "Unsupported slate.";
										}
									
										// Break
										break;
									
									// Default
									default:
									
										// Throw error
										throw "Unsupported slate.";
								}
							}
							
							// Otherwise
							else {
							
								// Set receiver address to serialized slate's receiver address
								this.receiverAddress = Slate.NO_RECEIVER_ADDRESS;
								
								// Set sender address to serialized slate's sender address
								this.senderAddress = Slate.NO_SENDER_ADDRESS;
							}
							
							// Check if serialized slate contains a proof signature
							if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
							
								// Check if doesn't have a payment proof
								if(this.hasPaymentProof() === false) {
								
									// Throw error
									throw "Unsupported slate.";
								}
							
								// Get serialized slate's receiver signature length
								var receiverSignatureLength = bitReader.getBits(Slate.COMPACT_PROOF_SIGNATURE_LENGTH_LENGTH) + Crypto.ED25519_SIGNATURE_LENGTH;
								
								// Check if serialized slate's receiver signature length isn't supported
								if(receiverSignatureLength > Crypto.MAXIMUM_MESSAGE_HASH_SIGNATURE_LENGTH && receiverSignatureLength !== Crypto.ED25519_SIGNATURE_LENGTH) {
								
									// Throw error
									throw "Unsupported slate.";
								}
								
								// Set receiver signature to serialized slate's receiver signature
								this.receiverSignature = bitReader.getBytes(receiverSignatureLength);
							}
							
							// Otherwise
							else {
							
								// Set receiver signature to serialized slate's receiver signature
								this.receiverSignature = Slate.NO_RECEIVER_SIGNATURE;
							}
						}
					}
					
					// Catch errors
					catch(error) {
					
						// Throw error
						throw "Unsupported slate.";
					}
					
					// Break
					break;
				
				// Version four
				case Slate.VERSION_FOUR.toFixed():
				
					// Set version to detected version
					this.version = detectedVersion;
					
					// Set original version to version
					this.originalVersion = this.getVersion();
					
					// Check if serialized slate is binary
					if(serializedSlate instanceof Uint8Array === true) {
					
						// Initialize bit reader for the serialized slate
						var bitReader = new BitReader(serializedSlate);
						
						// Try
						try {
						
							// Skip serialized slate's version
							bitReader.getBytes(Common.BYTES_IN_A_UINT16);
							
							// Set block header version to serialized slate's block header version
							this.blockHeaderVersion = new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT16)));
							
							// Set ID to serialized slate's ID
							this.id = new Uuid(Uuid.serializeData(bitReader.getBytes(Uuid.BYTE_LENGTH)));
							
							// Check if ID isn't a random UUID
							if(this.getId().isRandom() === false) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Check if serialized slate's purpose isn't correct
							if(bitReader.getBytes(1)[0] !== purpose + 1) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set offset to serialized slate's offset
							this.offset = bitReader.getBytes(Crypto.BLINDING_FACTOR_LENGTH);
							
							// Check if serialized slate's offset isn't supported
							if((purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL && Common.arraysAreEqual(this.getOffset(), Slate.ZERO_OFFSET) === false && Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true) || (purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE && Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true)) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Get serialized slate's optional fields
							var optionalFields = bitReader.getBytes(1)[0];
							
							// Set number of participants to serialized slate's number of participants if it includes it
							this.numberOfParticipants = ((optionalFields & 0b00000001) !== 0) ? new BigNumber(bitReader.getBytes(1)[0]) : Slate.DEFAULT_NUMBER_OF_PARTICIPANTS;
							
							// Check if serialized slate's number of participants isn't supported
							if(this.getNumberOfParticipants().isLessThan(Slate.MINIMUM_NUMBER_OF_PARTICIPANTS) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set amount to serialized slate's amount if it includes it
							this.amount = ((optionalFields & 0b00000010) !== 0) ? new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT64))) : ((purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) ? new BigNumber(0) : initialSendSlate.getAmount());
							
							// Check if serialized slate's amount isn't supported
							if(this.getAmount().isLessThan(Slate.MINIMUM_AMOUNT) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set fee to serialized slate's fee if it includes it
							this.fee = ((optionalFields & 0b00000100) !== 0) ? new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT64))) : ((purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) ? new BigNumber(0) : initialSendSlate.getFee());
							
							// Check if serialized slate's fee isn't supported
							if(this.getFee().isLessThan(Slate.MINIMUM_FEE) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Get serialized slate's features if it includes it
							var features = ((optionalFields & 0b00001000) !== 0) ? bitReader.getBytes(1)[0] : SlateKernel.PLAIN_FEATURES;
							
							// Set time to live cut off height to serialized slate's time to live cut off height if it includes it
							this.timeToLiveCutOffHeight = ((optionalFields & 0b00010000) !== 0) ? new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT64))) : Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
							
							// Get serialized sate's participants length
							var participantsLength = bitReader.getBytes(1)[0];
							
							// Check if purpose is send response
							if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
							
								// Set height to initial send slate's height
								this.height = initialSendSlate.getHeight();
								
								// Set inputs to initial send slate's inputs
								this.inputs = initialSendSlate.getInputs().map(function(input) {
								
									// Return input
									return new SlateInput(input.getFeatures(), input.getCommit());
								});
								
								// Set outputs to initial send slate's outputs
								this.outputs = initialSendSlate.getOutputs().map(function(output) {
								
									// Return output
									return new SlateOutput(output.getFeatures(), output.getCommit(), output.getProof());
								});
								
								// Set participants to initial send slate's participants
								this.participants = initialSendSlate.getParticipants().map(function(participant) {
								
									// Return participant
									return new SlateParticipant(participant.getId(), participant.getPublicBlindExcess(), participant.getPublicNonce(), participant.getPartialSignature(), participant.getMessage(), participant.getMessageSignature());
								});
							}
							
							// Check if serialized slate's participants isn't supported
							if(this.getNumberOfParticipants().isLessThan(participantsLength + this.getParticipants()["length"]) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Go through all of the serialized slate's participants
							for(var i = 0; i < participantsLength; ++i) {
							
								// Add serialized slate's participant to the participants
								this.participants.push(new SlateParticipant(bitReader, this));
							}
							
							// Get serialized slate's component fields
							var componentFields = bitReader.getBytes(1)[0];
							
							// Check if serialized slate includes inputs and outputs
							if((componentFields & 0b00000001) !== 0) {
							
								// Get serialized slate's inputs and outputs length
								var inputsAndOutputsLength = (new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT16)))).toNumber();
								
								// Go through all of the serialized slate's inputs and outputs
								var inputs = [];
								var outputs = [];
								for(var i = 0; i < inputsAndOutputsLength; ++i) {
								
									// Check if input or output is an input
									if(bitReader.getBytes(1)[0] === 0) {
									
										// Append serialized slate's input to inputs
										inputs.push(new SlateInput(bitReader, this));
									}
									
									// Otherwise
									else {
									
										// Append serialized slate's output to outputs
										outputs.push(new SlateOutput(bitReader, this));
									}
								}
								
								// Check if purpose is send response
								if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
								
									// Add inputs
									this.addInputs(inputs, false, outputs["length"]);
									
									// Add outputs
									this.addOutputs(outputs, false);
								}
							}
							
							// Check if serialized slate includes payment proof
							if((componentFields & 0b00000010) !== 0) {
							
								// Set sender address to the Slatepack address created from the serialized slate's sender public key
								this.senderAddress = Slatepack.publicKeyToSlatepackAddress(bitReader.getBytes(Crypto.ED25519_PUBLIC_KEY_LENGTH));
								
								// Set receiver address to the Slatepack address created from the serialized slate's receiver public key
								this.receiverAddress = Slatepack.publicKeyToSlatepackAddress(bitReader.getBytes(Crypto.ED25519_PUBLIC_KEY_LENGTH));
								
								// Check if serialized slate includes a payment proof's receiver signature
								if(bitReader.getBytes(1)[0] !== 0) {
								
									// Set receiver signature to serialized slate's receiver signature
									this.receiverSignature = bitReader.getBytes(Crypto.ED25519_SIGNATURE_LENGTH);
								}
							}
							
							// Check serialized slate's features
							switch(features) {
							
								// Plain features
								case SlateKernel.PLAIN_FEATURES:
								
									// Break
									break;
								
								// Height locked features
								case SlateKernel.HEIGHT_LOCKED_FEATURES:
								
									// Set lock height to serialized slate's lock height
									this.lockHeight = new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT64)));
									
									// Check if serialized slate's lock height isn't supported
									if(this.getLockHeight().isLessThan(Slate.NO_LOCK_HEIGHT) === true) {
									
										// Throw error
										throw "Unsupported slate.";
									}
								
									// Break
									break;
							
								// Default
								default:
								
									// Throw error
									throw "Unsupported slate.";
							}
							
							// Check if serialized slate's time to live cut off height isn't supported
							if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && (this.getTimeToLiveCutOffHeight().isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true || (this.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false && this.getTimeToLiveCutOffHeight().isLessThan(this.getLockHeight()) === true))) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set kernels to serialized slate's kernels
							this.kernels.push(new SlateKernel(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()));
						}
					
						// Catch errors
						catch(error) {
						
							// Throw error
							throw "Unsupported slate.";
						}
					}
					
					// Otherwise
					else {
					
						// Check if serialized slate's purpose isn't correct
						if("sta" in serializedSlate === false || typeof serializedSlate["sta"] !== "string" || serializedSlate["sta"] !== Slate.purposeToText(purpose)) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if serialized slate's number of participants isn't supported
						if("num_parts" in serializedSlate === true && ((Common.isNumberString(serializedSlate["num_parts"]) === false && serializedSlate["num_parts"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["num_parts"])).isInteger() === false || (new BigNumber(serializedSlate["num_parts"])).isLessThan(Slate.MINIMUM_NUMBER_OF_PARTICIPANTS) === true)) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Set number of participants to serialized slate's number of participants
						this.numberOfParticipants = ("num_parts" in serializedSlate === true) ? new BigNumber(serializedSlate["num_parts"]) : Slate.DEFAULT_NUMBER_OF_PARTICIPANTS;
						
						// Check if serialized slate's ID isn't supported
						if("id" in serializedSlate === false || typeof serializedSlate["id"] !== "string") {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Try
						try {
						
							// Set ID to serialized slate's ID
							this.id = new Uuid(serializedSlate["id"]);
						
							// Check if ID isn't a random UUID
							if(this.getId().isRandom() === false) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						}
						
						// Catch errors
						catch(error) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if serialized slate's block header version isn't supported
						if("ver" in serializedSlate === false || typeof serializedSlate["ver"] !== "string" || Slate.VERSION_PATTERN.test(serializedSlate["ver"]) === false) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Set block header version to serialized slate's block header version
						this.blockHeaderVersion = new BigNumber(serializedSlate["ver"].split(Slate.VERSION_SEPARATOR)[1]);
						
						// Check if serialzed slate contains features
						if("feat" in serializedSlate === true) {
						
							// Check if serialized slate's features isn't supported
							if((Common.isNumberString(serializedSlate["feat"]) === false && serializedSlate["feat"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["feat"])).isGreaterThan(Number.MAX_SAFE_INTEGER) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						
							// Check serialized slate's features
							switch((new BigNumber(serializedSlate["feat"])).toNumber()) {
							
								// Plain features
								case SlateKernel.PLAIN_FEATURES:
								
									// Break
									break;
								
								// Height locked features
								case SlateKernel.HEIGHT_LOCKED_FEATURES:
								
									// Check if serialized slate's lock height isn't supported
									if("feat_args" in serializedSlate === false || serializedSlate["feat_args"] === null || Object.isObject(serializedSlate["feat_args"]) === false || "lock_hgt" in serializedSlate["feat_args"] === false || (Common.isNumberString(serializedSlate["feat_args"]["lock_hgt"]) === false && serializedSlate["feat_args"]["lock_hgt"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["feat_args"]["lock_hgt"])).isInteger() === false || (new BigNumber(serializedSlate["feat_args"]["lock_hgt"])).isLessThan(Slate.NO_LOCK_HEIGHT) === true) {
									
										// Throw error
										throw "Unsupported slate.";
									}
									
									// Set lock height to serialized slate's lock height
									this.lockHeight = new BigNumber(serializedSlate["feat_args"]["lock_hgt"]);
								
									// Break
									break;
							
								// Default
								default:
								
									// Throw error
									throw "Unsupported slate.";
							}
						}
						
						// Otherwise
						else {
						
							// Check if serialized slate's features arguments isn't supported
							if("feat_args" in serializedSlate === true && serializedSlate["feat_args"] !== null) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						}
					
						// Check if serialized slate's time to live cut off height isn't supported
						if("ttl" in serializedSlate === true && serializedSlate["ttl"] !== null && ((Common.isNumberString(serializedSlate["ttl"]) === false && serializedSlate["ttl"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["ttl"])).isInteger() === false || (new BigNumber(serializedSlate["ttl"])).isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true || (this.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false && (new BigNumber(serializedSlate["ttl"])).isLessThan(this.getLockHeight()) === true))) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Set time to live cut off height to serialized slate's time to live cut off height
						this.timeToLiveCutOffHeight = ("ttl" in serializedSlate === true && serializedSlate["ttl"] !== null) ? new BigNumber(serializedSlate["ttl"]) : Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
						
						// Check if purpose is send response
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
						
							// Set participants to initial send slate's participants
							this.participants = initialSendSlate.getParticipants().map(function(participant) {
							
								// Return participant
								return new SlateParticipant(participant.getId(), participant.getPublicBlindExcess(), participant.getPublicNonce(), participant.getPartialSignature(), participant.getMessage(), participant.getMessageSignature());
							});
						}
						
						// Check if serialized slate's participants isn't supported
						if("sigs" in serializedSlate === false || Array.isArray(serializedSlate["sigs"]) === false || this.getNumberOfParticipants().isLessThan(serializedSlate["sigs"]["length"] + this.getParticipants()["length"]) === true) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Try
						try {
						
							// Go through all of the serialized slate's participants
							for(var i = 0; i < serializedSlate["sigs"]["length"]; ++i) {
							
								// Get participant
								var participant = serializedSlate["sigs"][i];
								
								// Check if participant isn't supported
								if(Object.isObject(participant) === false) {
								
									// Throw error
									throw "Unsupported slate.";
								}
							
								// Append slate participant to participants
								this.participants.push(new SlateParticipant(participant, this));
							}
						}
						
						// Catch errors
						catch(error) {
						
							// Throw error
							throw "Unsupported slate.";
						}
						
						// Check if serialized slate contains a payment proof
						if("proof" in serializedSlate === true && serializedSlate["proof"] !== null) {
						
							// Check if serialized slate's proof isn't supported
							if(Object.isObject(serializedSlate["proof"]) === false) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Check if payment proof's receiver address isn't supported
							if("raddr" in serializedSlate["proof"] === false || Common.isHexString(serializedSlate["proof"]["raddr"]) === false || Common.hexStringLength(serializedSlate["proof"]["raddr"]) !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Try
							try {
							
								// Set receiver address to the Slatepack address created from the receiver's public key
								this.receiverAddress = Slatepack.publicKeyToSlatepackAddress(Common.fromHexString(serializedSlate["proof"]["raddr"]));
							}
							
							// Catch errors
							catch(error) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Check if payment proof's receiver signature isn't supported
							if("rsig" in serializedSlate["proof"] === true && serializedSlate["proof"]["rsig"] !== null && (Common.isHexString(serializedSlate["proof"]["rsig"]) === false || Common.hexStringLength(serializedSlate["proof"]["rsig"]) !== Crypto.ED25519_SIGNATURE_LENGTH)) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set receiver signature to serialized slate's receiver signature
							this.receiverSignature = ("rsig" in serializedSlate["proof"] === true && serializedSlate["proof"]["rsig"] !== null) ? Common.fromHexString(serializedSlate["proof"]["rsig"]) : Slate.NO_RECEIVER_SIGNATURE;
							
							// Check if payment proof's sender address isn't supported
							if("saddr" in serializedSlate["proof"] === false || Common.isHexString(serializedSlate["proof"]["saddr"]) === false || Common.hexStringLength(serializedSlate["proof"]["saddr"]) !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Try
							try {
							
								// Set sender address to the Slatepack address created from the sender's public key
								this.senderAddress = Slatepack.publicKeyToSlatepackAddress(Common.fromHexString(serializedSlate["proof"]["saddr"]));
							}
							
							// Catch errors
							catch(error) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						}
						
						// Check if purpose is send initial
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
						
							// Check if serialized slate contains inputs and outputs isn't supported
							if("coms" in serializedSlate === true && serializedSlate["coms"] !== null) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						
							// Check if serialized slate's amount isn't supported
							if("amt" in serializedSlate === false || (Common.isNumberString(serializedSlate["amt"]) === false && serializedSlate["amt"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["amt"])).isInteger() === false || (new BigNumber(serializedSlate["amt"])).isLessThan(Slate.MINIMUM_AMOUNT) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set amount to serialized slate's amount
							this.amount = new BigNumber(serializedSlate["amt"]);
							
							// Check if serialized slate's fee isn't supported
							if("fee" in serializedSlate === false || (Common.isNumberString(serializedSlate["fee"]) === false && serializedSlate["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["fee"])).isInteger() === false || (new BigNumber(serializedSlate["fee"])).isLessThan(Slate.MINIMUM_FEE) === true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set fee to serialized slate's fee
							this.fee = new BigNumber(serializedSlate["fee"]);
						
							// Check if serialized slate's offset isn't supported
							if("off" in serializedSlate === true && (Common.isHexString(serializedSlate["off"]) === false || Common.arraysAreEqual(Common.fromHexString(serializedSlate["off"]), Slate.ZERO_OFFSET) === false)) {
							
								// Throw error
								throw "Unsupported slate.";
							}
						}
						
						// Check if purpose is send response
						if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
						
							// Set height to initial send slate's height
							this.height = initialSendSlate.getHeight();
						
							// Set inputs to initial send slate's inputs
							this.inputs = initialSendSlate.getInputs().map(function(input) {
							
								// Return input
								return new SlateInput(input.getFeatures(), input.getCommit());
							});
							
							// Set outputs to initial send slate's outputs
							this.outputs = initialSendSlate.getOutputs().map(function(output) {
							
								// Return output
								return new SlateOutput(output.getFeatures(), output.getCommit(), output.getProof());
							});
							
							// Check if serialized slate contains inputs and outputs isn't supported
							if("coms" in serializedSlate === false || serializedSlate["coms"] === null || Array.isArray(serializedSlate["coms"]) === false) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Initialize inputs and outputs
							var inputs = [];
							var outputs = [];
							
							// Try
							try {
							
								// Go through all serialized slate's input and outputs
								for(var i = 0; i < serializedSlate["coms"]["length"]; ++i) {
								
									// Get serialize slate's value
									var value = serializedSlate["coms"][i];
									
									// Check if value isn't supported
									if(Object.isObject(value) === false) {
									
										// Throw error
										throw "Unsupported slate.";
									}
									
									// Check if value is an output
									if("p" in value === true && value["p"] !== null) {
									
										// Add value to list of outputs
										outputs.push(new SlateOutput(value, this));
									}
									
									// Otherwise
									else {
									
										// Add value to list of inputs
										inputs.push(new SlateInput(value, this));
									}
								}
							}
							
							// Catch errors
							catch(error) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Add inputs
							this.addInputs(inputs, false, outputs["length"]);
							
							// Add outputs
							this.addOutputs(outputs, false);
						
							// Check if serialized slate's amount isn't supported
							if("amt" in serializedSlate === true && ((Common.isNumberString(serializedSlate["amt"]) === false && serializedSlate["amt"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["amt"])).isInteger() === false || (new BigNumber(serializedSlate["amt"])).isLessThan(Slate.MINIMUM_AMOUNT) === true)) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set amount to serialized slate's amount
							this.amount = ("amt" in serializedSlate === true) ? new BigNumber(serializedSlate["amt"]) : initialSendSlate.getAmount();
							
							// Check if serialized slate's fee isn't supported
							if("fee" in serializedSlate === true && ((Common.isNumberString(serializedSlate["fee"]) === false && serializedSlate["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["fee"])).isInteger() === false || (new BigNumber(serializedSlate["fee"])).isLessThan(Slate.MINIMUM_FEE) === true)) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set fee to serialized slate's fee
							this.fee = ("fee" in serializedSlate === true) ? new BigNumber(serializedSlate["fee"]) : initialSendSlate.getFee();
						
							// Check if serialized slate's offset isn't supported
							if("off" in serializedSlate === false || Common.isHexString(serializedSlate["off"]) === false || Common.hexStringLength(serializedSlate["off"]) !== Crypto.BLINDING_FACTOR_LENGTH || Secp256k1Zkp.isValidSecretKey(Common.fromHexString(serializedSlate["off"])) !== true) {
							
								// Throw error
								throw "Unsupported slate.";
							}
							
							// Set offset to serialized slate's offset
							this.offset = Common.fromHexString(serializedSlate["off"]);
						}
						
						// Set kernels to serialized slate's kernels
						this.kernels.push(new SlateKernel(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()));
					}
					
					// Break
					break;
				
				// Default
				default:
				
					// Throw error
					throw "Unsupported slate.";
			}
			
			// Set participant IDs
			var participantIds = [];
			
			// Set sender participant exists
			var senderParticipantExists = false;
			
			// Go through all participants
			for(var i = 0; i < this.getParticipants()["length"]; ++i) {
			
				// Get participant
				var participant = this.getParticipants()[i];
				
				// Check if participant's ID already exists
				if(participantIds.indexOf(participant.getId().toFixed()) !== Common.INDEX_NOT_FOUND) {
				
					// Throw error
					throw "Unsupported slate.";
				}
				
				// Append participant's ID to list
				participantIds.push(participant.getId().toFixed());
				
				// Check if participant is a sender
				if(participant.isSender() === true) {
				
					// Set sender participant exists
					senderParticipantExists = true;
				}
			}
			
			// Check if a sender participant doesn't exist
			if(senderParticipantExists === false) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if partial signatures failed to be verified
			if(this.verifyPartialSignatures() === false) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if receiver signature failed to be verified
			if(this.verifyReceiverSignature(isMainnet) === false) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if verifying weight failed
			if(this.verifyWeight() === false) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if verifying no recent duplicate kernels failed
			if(this.verifyNoRecentDuplicateKernels(isMainnet) === false) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if verifying sorted and unique failed
			if(this.verifySortedAndUnique() === false) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if verifying no cut through failed
			if(this.verifyNoCutThrough() === false) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if no kernels exist
			if(this.getKernels()["length"] === 0) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if kernel's features differ from the slate's
			if(this.getKernelFeatures() !== this.getKernels()[0].getFeatures()) {
			
				// Throw error
				throw "Unsupported slate.";
			}
			
			// Check if kernel is complete
			if(this.getKernels()[0].isComplete() === true) {
			
				// Check if verifying kernel sums failed
				if(this.verifyKernelSums() === false) {
				
					// Throw error
					throw "Unsupported slate.";
				}
			}
		}
		
		// Minimum compatible version
		minimumCompatibleVersion(preferredVersions) {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC wallet
				case Consensus.MWC_WALLET_TYPE:
		
					// Check if version Slatepack is preferred and kernel features are plain
					if(preferredVersions.indexOf(Slate.VERSION_SLATEPACK) !== Common.INDEX_NOT_FOUND && this.getKernelFeatures() === SlateKernel.PLAIN_FEATURES) {
					
						// Return version Slatepack
						return Slate.VERSION_SLATEPACK;
					}
				
					// Otherwise check if time to live cut off height or payment proof is used
					else if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT || this.hasPaymentProof() === true) {
					
						// Return version three
						return Slate.VERSION_THREE;
					}
					
					// Otherwise
					else {
					
						// Check if version two isn't preferred
						if(preferredVersions["length"] !== 0 && preferredVersions.indexOf("V" + Slate.VERSION_TWO.toFixed()) === Common.INDEX_NOT_FOUND) {
						
							// Return version three
							return Slate.VERSION_THREE;
						}
						
						// Otherwise
						else {
					
							// Return version two
							return Slate.VERSION_TWO;
						}
					}
					
					// Break
					break;
				
				// GRIN wallet
				case Consensus.GRIN_WALLET_TYPE:
				
					// Return version four
					return Slate.VERSION_FOUR;
				
				// EPIC wallet
				case Consensus.EPIC_WALLET_TYPE:
		
					// Check if time to live cut off height or payment proof is used
					if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT || this.hasPaymentProof() === true) {
					
						// Return version three
						return Slate.VERSION_THREE;
					}
					
					// Otherwise
					else {
					
						// Check if version two isn't preferred
						if(preferredVersions["length"] !== 0 && preferredVersions.indexOf("V" + Slate.VERSION_TWO.toFixed()) === Common.INDEX_NOT_FOUND) {
						
							// Return version three
							return Slate.VERSION_THREE;
						}
						
						// Otherwise
						else {
					
							// Return version two
							return Slate.VERSION_TWO;
						}
					}
					
					// Break
					break;
			}
		}
		
		// Verify partial signatures
		verifyPartialSignatures() {
		
			// Try
			try {
			
				// Get message to sign
				var messageToSign = SlateKernel.signatureMessage(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight());
			}
			
			// Catch errors
			catch(error) {
			
				// Return false
				return false;
			}
			
			// Get public nonce sum from combining participant's public nonces
			var publicNonceSum = Secp256k1Zkp.combinePublicKeys(this.getParticipants().map(function(participant) {
			
				// Return participant's public nonce
				return participant.getPublicNonce();
			}));
			
			// Check if getting public nonce sum failed
			if(publicNonceSum === Secp256k1Zkp.OPERATION_FAILED) {
			
				// Return false
				return false;
			}
			
			// Get public blind excess sum from combining participant's public blind excesses
			var publicBlindExcessSum = Secp256k1Zkp.combinePublicKeys(this.getParticipants().map(function(participant) {
			
				// Return participant's public blind excess
				return participant.getPublicBlindExcess();
			}));
			
			// Check if getting public blind excess sum failed
			if(publicBlindExcessSum === Secp256k1Zkp.OPERATION_FAILED) {
			
				// Return false
				return false;
			}
			
			// Go through all participants
			for(var i = 0; i < this.getParticipants()["length"]; ++i) {
			
				// Get participant
				var participant = this.getParticipants()[i];
				
				// Check if participant is complete
				if(participant.isComplete() === true) {
				
					// Check if partial signature doesn't verify the message
					if(Secp256k1Zkp.verifySingleSignerSignature(participant.getPartialSignature(), messageToSign, publicNonceSum, participant.getPublicBlindExcess(), publicBlindExcessSum, true) !== true) {
					
						// Return false
						return false;
					}
				}
			}
			
			// Return true
			return true;
		}
		
		// Verify receiver signature
		verifyReceiverSignature(isMainnet) {
		
			// Check if receiver signature exists
			if(this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) {
			
				// Try
				try {
				
					// Get excess
					var excess = this.getExcess();
				}
				
				// Catch errors
				catch(error) {
				
					// Return false
					return false;
				}
				
				// Try
				try {
				
					// Get message from amount, excess, and sender address
					var message = Slate.getPaymentProofMessage(this.getAmount(), excess, this.getSenderAddress());
					
					// Check wallet type
					switch(Consensus.getWalletType()) {
					
						// MWC wallet
						case Consensus.MWC_WALLET_TYPE:
					
							// Check receiver address's length
							switch(this.getReceiverAddress()["length"]) {
							
								// MQS address length
								case Mqs.ADDRESS_LENGTH:
								
									// Get message hash
									var messageHash = new Uint8Array(sha256.arrayBuffer(message));
								
									// Get receiver address's public key
									var receiverAddressPublicKey = Mqs.mqsAddressToPublicKey(this.getReceiverAddress(), isMainnet);
									
									// Check if receiver signature doesn't verify the message hash
									if(Secp256k1Zkp.verifyMessageHashSignature(this.getReceiverSignature(), messageHash, receiverAddressPublicKey) !== true) {
									
										// Return false
										return false;
									}
									
									// Break
									break;
								
								// Tor address length
								case Tor.ADDRESS_LENGTH:
									
									// Get receiver address's public key
									var receiverAddressPublicKey = Tor.torAddressToPublicKey(this.getReceiverAddress());
								
									// Check if receiver signature doesn't verify the message
									if(Ed25519.verify(message, this.getReceiverSignature(), receiverAddressPublicKey) !== true) {
									
										// Return false
										return false;
									}
									
									// Break
									break;
							}
							
							// Break
							break;
						
						// GRIN wallet
						case Consensus.GRIN_WALLET_TYPE:
						
							// Check receiver address's length
							switch(this.getReceiverAddress()["length"]) {
						
								// Slatepack address length
								case Slatepack.ADDRESS_LENGTH:
								
									// Get receiver address's public key
									var receiverAddressPublicKey = Slatepack.slatepackAddressToPublicKey(this.getReceiverAddress());
								
									// Check if receiver signature doesn't verify the message
									if(Ed25519.verify(message, this.getReceiverSignature(), receiverAddressPublicKey) !== true) {
									
										// Return false
										return false;
									}
									
									// Break
									break;
							}
							
							// Break
							break;
						
						// EPIC wallet
						case Consensus.EPIC_WALLET_TYPE:
					
							// Check receiver address's length
							switch(this.getReceiverAddress()["length"]) {
							
								// Tor address length
								case Tor.ADDRESS_LENGTH:
									
									// Get receiver address's public key
									var receiverAddressPublicKey = Tor.torAddressToPublicKey(this.getReceiverAddress());
								
									// Check if receiver signature doesn't verify the message
									if(Ed25519.verify(message, this.getReceiverSignature(), receiverAddressPublicKey) !== true) {
									
										// Return false
										return false;
									}
									
									// Break
									break;
							}
							
							// Break
							break;
					}
				}
				
				// Catch errors
				catch(error) {
				
					// Return false
					return false;
				}
			}
			
			// Return tru
			return true;
		}
		
		// Verify weight
		verifyWeight(expectedNumberOfOutputs = 0) {
		
			// Get coinbase weight
			var coinbaseWeight = Consensus.BLOCK_OUTPUT_WEIGHT + Consensus.BLOCK_KERNEL_WEIGHT;
			
			// Get maximum transaction weight while leaving room for a coinbase
			var maximumTransactionWeight = Math.max(Consensus.MAXIMUM_BLOCK_WEIGHT - coinbaseWeight, 0);
			
			// Check if weight is too heavy
			if(Slate.getWeight(this.getInputs()["length"], this.getOutputs()["length"] + expectedNumberOfOutputs, this.getKernels()["length"]).isGreaterThan(maximumTransactionWeight) === true)
			
				// Return false
				return false;
			
			// Return true
			return true;
		}
		
		// Verify no recent duplicate kernels
		verifyNoRecentDuplicateKernels(isMainnet) {
		
			// Check if no recent duplicate kernels is enabled
			if(Consensus.isNoRecentDuplicateKernelsEnabled(isMainnet) === true) {
			
				// Initialize no recent duplicate kernels excesses
				var noRecentDuplicateKernelsExcesses = [];
			
				// Go through all kernels
				for(var i = 0; i < this.getKernels()["length"]; ++i) {
				
					// Get kernel
					var kernel = this.getKernels()[i];
					
					// Check if kernel is no recent duplicate
					if(kernel.isNoRecentDuplicate() === true) {
					
						// Check if kernel's excess already exists in the list
						if(noRecentDuplicateKernelsExcesses.indexOf(Common.toHexString(kernel.getExcess())) !== Common.INDEX_NOT_FOUND) {
						
							// Return false
							return false;
						}
					
						// Append kernel's excess to list
						noRecentDuplicateKernelsExcesses.push(Common.toHexString(kernel.getExcess()));
					}
				}
			}
			
			// Return true
			return true;
		}
		
		// Verify sorted and unique
		verifySortedAndUnique() {
		
			// Check if inputs aren't sorted and unique
			if(Slate.isSortedAndUnique(this.getInputs().map(function(input) {
			
				// Return input's hash
				return input.getHash();
				
			})) === false) {
			
				// Return false
				return false;
			}
			
			// Check if outputs aren't sorted and unique
			if(Slate.isSortedAndUnique(this.getOutputs().map(function(output) {
			
				// Return output's hash
				return output.getHash();
				
			})) === false) {
			
				// Return false
				return false;
			}
			
			// Try
			try {
			
				// Check if kernels aren't sorted and unique
				if(Slate.isSortedAndUnique(this.getKernels().map(function(kernel) {
				
					// Return kernel's hash
					return kernel.getHash();
					
				})) === false) {
				
					// Return false
					return false;
				}
			}
			
			// Catch errors
			catch(error) {
			
				// Return false
				return false;
			}
			
			// Return true
			return true;
		}
		
		// Verify no cut through
		verifyNoCutThrough() {
		
			// Initialize serialized hashes
			var serializedHashes = [];
			
			// Go through all inputs
			for(var i = 0; i < this.getInputs()["length"]; ++i) {
			
				// Get input's serialized hash
				var serializedHash = this.getInputs()[i].getHash().serialize();
				
				// Check if serialized hash already exists in the list
				if(serializedHashes.indexOf(serializedHash) !== Common.INDEX_NOT_FOUND) {
				
					// Return false
					return false;
				}
				
				// Append serialized hash to list
				serializedHashes.push(serializedHash);
			}
			
			// Go through all outputs
			for(var i = 0; i < this.getOutputs()["length"]; ++i) {
			
				// Get input's serialized hash
				var serializedHash = this.getOutputs()[i].getHash().serialize();
				
				// Check if serialized hash already exists in the list
				if(serializedHashes.indexOf(serializedHash) !== Common.INDEX_NOT_FOUND) {
				
					// Return false
					return false;
				}
				
				// Append serialized hash to list
				serializedHashes.push(serializedHash);
			}
			
			// Return true
			return true;
		}
		
		// Verify fees
		verifyFees(baseFee) {
		
			// Get the required transaction fee
			var transactionFee = Slate.getRequiredFee(this.getInputs()["length"], this.getOutputs()["length"], this.getKernels()["length"], baseFee);
			
			// Check if transaction fee is greater than the overage
			if(transactionFee.isGreaterThan(this.getOverage()) === true) {
			
				// Return false
				return false;
			}
			
			// Check if transaction fee is greater than the received amount
			if(transactionFee.isGreaterThan(this.getAmount().plus(this.getFee())) === true) {
			
				// Return false
				return false;
			}
			
			// Check if transaction fee is less than the minimum fee
			if(transactionFee.isLessThan(Slate.MINIMUM_FEE) === true) {
			
				// Return false
				return false;
			}
		
			return true;
		}
		
		// Verify kernel sums
		verifyKernelSums() {
		
			// Set kernel excesses to all kernel's excesses
			var kernelExcesses = this.getKernels().map(function(kernel) {
			
				// Return kernel's excess
				return kernel.getExcess();
			});
			
			// Go through all kernel excesses
			for(var i = 0; i < kernelExcesses["length"]; ++i) {
			
				// Get kernel excess
				var kernelExcess = kernelExcesses[i];
				
				// Check if kernel excess is a zero commit
				if(Common.arraysAreEqual(kernelExcess, Slate.ZERO_COMMIT) === true) {
				
					// Remove kernel excess from list
					kernelExcesses.splice(i--, 1);
				}
			}
			
			// Check if getting kernels sum from kernel excesses failed
			var kernelsSum = Secp256k1Zkp.pedersenCommitSum(kernelExcesses, []);
			
			if(kernelsSum === Secp256k1Zkp.OPERATION_FAILED) {
			
				// Return false
				return false;
			}
			
			// Initialize kernel commits
			var kernelCommits = [
			
				// Kernels sum
				kernelsSum
			];
			
			// Try
			try {
			
				// Get offset excess
				var offsetExcess = this.getOffsetExcess();
			}
			
			// Catch errors
			catch(error) {
			
				// Return false
				return false;
			}
			
			// Append offset excess to kernel commits
			kernelCommits.push(offsetExcess);
			
			// Check if getting kernels sum with offset from kernel commits failed
			var kernelsSumWithOffset = Secp256k1Zkp.pedersenCommitSum(kernelCommits, []);
			
			if(kernelsSumWithOffset === Secp256k1Zkp.OPERATION_FAILED) {
			
				// Return false
				return false;
			}
			
			// Check if getting commits sum failed
			var commitsSum = this.getCommitsSum();
			
			if(commitsSum === false) {
			
				// Return false
				return false;
			}
			
			// Check if commits sum isn't equal to the kernels sum with offset
			if(Common.arraysAreEqual(commitsSum, kernelsSumWithOffset) === false) {
			
				// Return false
				return false;
			}
			
			// Return true
			return true;
		}
		
		// Get kernel features
		getKernelFeatures() {
		
			// Check if lock height exists
			if(this.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false) {
			
				// Return height locked kernel features
				return SlateKernel.HEIGHT_LOCKED_FEATURES;
			}
			
			// Check if relative height exists
			else if(this.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT) {
			
				// Return no recent duplicate kernel features
				return SlateKernel.NO_RECENT_DUPLICATE_FEATURES;
			}
			
			// Otherwise
			else {
			
				// Return plain kernel features
				return SlateKernel.PLAIN_FEATURES;
			}
		}
		
		// Get overage
		getOverage() {
		
			// Initialize overage
			var overage = new BigNumber(0);
		
			// Go through all kernels
			for(var i = 0; i < this.getKernels()["length"]; ++i) {
			
				// Get kernel
				var kernel = this.getKernels()[i];
				
				// Check kernel's features
				switch(kernel.getFeatures()) {
				
					// Plain, height locked, or no recent duplicate features
					case SlateKernel.PLAIN_FEATURES:
					case SlateKernel.HEIGHT_LOCKED_FEATURES:
					case SlateKernel.NO_RECENT_DUPLICATE_FEATURES:
					
						// Add kernel's fee to overage
						overage = overage.plus(kernel.getFee());
						
						// Break
						break;
				}
			}
			
			// Return overage
			return overage;
		}
		
		// Get commits sum
		getCommitsSum() {
		
			// Set input commits to all input's commits
			var inputCommits = this.getInputs().map(function(input) {
			
				// Return input's commit
				return input.getCommit();
			});
			
			// Set output commits to all output's commits
			var outputCommits = this.getOutputs().map(function(output) {
			
				// Return output's commit
				return output.getCommit();
			});
		
			// Get overage
			var overage = this.getOverage();
			
			// Check if overage isn't zero
			if(overage.isZero() === false) {
			
				// Try
				try {
			
					// Get over commit from the overage's absolute value
					var overCommit = Crypto.commitAmount(overage.absoluteValue());
				}
				
				// Catch errors
				catch(error) {
				
					// Return false
					return false;
				}
				
				// Check if overage is negative
				if(overage.isNegative() === true) {
				
					// Append over commit to input commits
					inputCommits.push(overCommit);
				}
				
				// Otherwise
				else {
				
					// Append over commit to output commits
					outputCommits.push(overCommit);
				}
			}
			
			// Go through all input commits
			for(var i = 0; i < inputCommits["length"]; ++i) {
			
				// Get input commit
				var inputCommit = inputCommits[i];
				
				// Check if input commit is a zero commit
				if(Common.arraysAreEqual(inputCommit, Slate.ZERO_COMMIT) === true) {
				
					// Remove input commit from list
					inputCommits.splice(i--, 1);
				}
			}
			
			// Go through all output commits
			for(var i = 0; i < outputCommits["length"]; ++i) {
			
				// Get output commit
				var outputCommit = outputCommits[i];
				
				// Check if output commit is a zero commit
				if(Common.arraysAreEqual(outputCommit, Slate.ZERO_COMMIT) === true) {
				
					// Remove output commit from list
					outputCommits.splice(i--, 1);
				}
			}
			
			// Check if getting commits sum from output commits and input commits failed
			var commitsSum = Secp256k1Zkp.pedersenCommitSum(outputCommits, inputCommits);
			
			if(commitsSum === Secp256k1Zkp.OPERATION_FAILED) {
			
				// Return false
				return false;
			}
			
			// Return commits sum
			return commitsSum;
		}
		
		// Update kernel
		updateKernel() {
		
			// Remove all kernels
			this.kernels = [];
			
			// Check kernel features
			switch(this.getKernelFeatures()) {
			
				// Plain features
				case SlateKernel.PLAIN_FEATURES:
				
					// Set kernel fee to fee
					var kernelFee = this.getFee();
					
					// Set kernel lock height to no lock height
					var kernelLockHeight = Slate.NO_LOCK_HEIGHT;
					
					// Set kernel relative height to no relative height
					var kernelRelativeHeight = Slate.NO_RELATIVE_HEIGHT;
				
					// Break
					break;
				
				// Height locked features
				case SlateKernel.HEIGHT_LOCKED_FEATURES:
				
					// Set kernel fee to fee
					var kernelFee = this.getFee();
					
					// Set kernel lock height to lock height
					var kernelLockHeight = this.getLockHeight();
					
					// Set kernel relative height to no relative height
					var kernelRelativeHeight = Slate.NO_RELATIVE_HEIGHT;
				
					// Break
					break;
				
				// No recent duplpicate features
				case SlateKernel.NO_RECENT_DUPLICATE_FEATURES:
				
					// Set kernel fee to fee
					var kernelFee = this.getFee();
					
					// Set kernel lock height to no lock height
					var kernelLockHeight = Slate.NO_LOCK_HEIGHT;
					
					// Set kernel relative height to relative height
					var kernelRelativeHeight = this.getRelativeHeight();
				
					// Break
					break;
			}
			
			// Create kernel with kernel features, kernel fee, and kernel lock height
			var kernel = new SlateKernel(this.getKernelFeatures(), kernelFee, kernelLockHeight, kernelRelativeHeight);
			
			// Append kernel to list
			this.kernels.push(kernel);
		}
		
		// Sort
		sort() {
		
			// Sort the inputs
			this.inputs.sort(function(inputOne, inputTwo) {
			
				// Return the result of comparing the input's hashes
				return inputOne.getHash().compare(inputTwo.getHash());
			});
			
			// Sort the outputs
			this.outputs.sort(function(outputOne, outputTwo) {
			
				// Return the result of comparing the output's hashes
				return outputOne.getHash().compare(outputTwo.getHash());
			});
			
			// Try
			try {
			
				// Sort the kernels
				this.kernels.sort(function(kernelOne, kernelTwo) {
				
					// Return the result of comparing the kernel's hashes
					return kernelOne.getHash().compare(kernelTwo.getHash());
				});
			}
			
			// Catch errors
			catch(error) {
			
				// Return false
				return false;
			}
			
			// Return true
			return true;
		}
		
		// Has payment proof
		hasPaymentProof() {
		
			// Return if receiver address and sender address exist
			return this.getReceiverAddress() !== Slate.NO_RECEIVER_ADDRESS && this.getSenderAddress() !== Slate.NO_SENDER_ADDRESS;
		}
		
		// Create partial signature
		createPartialSignature(secretKeyOrHardwareWallet, secretNonce, isNewParticipant, isMainnet, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
		
			// Set self
			var self = this;
			
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Get public blind excess
				var getPublicBlindExcess = function() {
				
					// Return promise
					return new Promise(function(resolve, reject) {
				
						// Check if a secret key is provided
						if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
						
							// Get secret key
							var secretKey = secretKeyOrHardwareWallet;
						
							// Check if getting public blind excess from secret key failed
							var publicBlindExcess = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
							
							if(publicBlindExcess === Secp256k1Zkp.OPERATION_FAILED) {
							
								// Reject error
								reject("Getting public blind excess failed.");
							}
							
							// Otherwise
							else {
							
								// Resolve public blind excess
								resolve(publicBlindExcess);
							}
						}
						
						// Otherwise
						else {
						
							// Get hardware wallet
							var hardwareWallet = secretKeyOrHardwareWallet;
							
							// Return getting the transaction public key with the hardware wallet
							return hardwareWallet.getTransactionPublicKey(hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(publicBlindExcess) {
							
								// Resolve public blind excess
								resolve(publicBlindExcess);
							
							// Catch errors
							}).catch(function(error) {
							
								// Reject error
								reject(error);
							});
						}
					});
				};
				
				// Return getting public blind excess
				return getPublicBlindExcess().then(function(publicBlindExcess) {
				
					// Get public nonce
					var getPublicNonce = function() {
					
						// Return promise
						return new Promise(function(resolve, reject) {
						
							// Check if a secret key is provided
							if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
							
								// Check if getting a public nonce from secret nonce failed
								var publicNonce = Secp256k1Zkp.publicKeyFromSecretKey(secretNonce);
								
								if(publicNonce === Secp256k1Zkp.OPERATION_FAILED) {
								
									// Reject error
									reject("Getting a public nonce failed.");
								}
								
								// Otherwise
								else {
								
									// Resolve public nonce
									resolve(publicNonce);
								}
							}
							
							// Otherwise
							else {
							
								// Get hardware wallet
								var hardwareWallet = secretKeyOrHardwareWallet;
								
								// Return getting the transaction public nonce with the hardware wallet
								return hardwareWallet.getTransactionPublicNonce(hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(publicNonce) {
								
									// Resolve public nonce
									resolve(publicNonce);
								
								// Catch errors
								}).catch(function(error) {
								
									// Reject error
									reject(error);
								});
							}
						});
					};
					
					// Return getting public nonce
					return getPublicNonce().then(function(publicNonce) {
				
						// Try
						try {
						
							// Get message to sign
							var messageToSign = SlateKernel.signatureMessage(self.getKernelFeatures(), self.getFee(), self.getLockHeight(), self.getRelativeHeight());
						}
						
						// Catch errors
						catch(error) {
						
							// Reject error
							reject("Getting message to sign failed.");
							
							// Return
							return;
						}
						
						// Get participants public nonces
						var publicNonces = self.getParticipants().map(function(participant) {
						
							// Return participant's public nonce
							return participant.getPublicNonce();
						});
						
						// Check if is new participant
						if(isNewParticipant === true) {
						
							// Append public nonce to list
							publicNonces.push(publicNonce);
						}
						
						// Get public nonce sum from combining participant's public nonces
						var publicNonceSum = Secp256k1Zkp.combinePublicKeys(publicNonces);
						
						// Check if getting public nonce sum failed
						if(publicNonceSum === Secp256k1Zkp.OPERATION_FAILED) {
						
							// Reject error
							reject("Getting public nonce sum failed.");
						}
						
						// Otherwise
						else {
						
							// Get participants public blind excesses
							var publicBlindExcesses = self.getParticipants().map(function(participant) {
							
								// Return participant's public blind excess
								return participant.getPublicBlindExcess();
							});
							
							// Check if is new participant
							if(isNewParticipant === true) {
							
								// Append public blind excess to list
								publicBlindExcesses.push(publicBlindExcess);
							}
							
							// Get public blind excess sum from combining participant's public blind excesses
							var publicBlindExcessSum = Secp256k1Zkp.combinePublicKeys(publicBlindExcesses);
							
							// Check if getting public blind excess sum failed
							if(publicBlindExcessSum === Secp256k1Zkp.OPERATION_FAILED) {
							
								// Reject error
								reject("Getting public blind excess sum failed.");
							}
							
							// Otherwise
							else {
							
								// Get partial signature
								var getPartialSignature = function() {
								
									// Return promise
									return new Promise(function(resolve, reject) {
									
										// Check if a secret key is provided
										if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
										
											// Get secret key
											var secretKey = secretKeyOrHardwareWallet;
											
											// Check if creating partial signature from the message to sign, secret key, secret nonce, public blind excess sum, and public nonce sum failed
											var partialSignature = Secp256k1Zkp.createSingleSignerSignature(messageToSign, secretKey, secretNonce, publicBlindExcessSum, publicNonceSum, publicNonceSum);
											
											if(partialSignature === Secp256k1Zkp.OPERATION_FAILED) {
											
												// Reject error
												reject("Creating partial signature failed.");
											}
											
											// Otherwise
											else {
											
												// Resolve partial signature
												resolve(partialSignature);
											}
										}
										
										// Otherwise
										else {
										
											// Get hardware wallet
											var hardwareWallet = secretKeyOrHardwareWallet;
											
											// Try
											try {
											
												// Get excess
												var excess = self.getExcess((isNewParticipant === true) ? publicBlindExcess : Slate.NO_PUBLIC_BLIND_EXCESS);
											}
											
											// Catch errors
											catch(error) {
											
												// Reject error
												reject(error);
												
												// Return
												return;
											}
											
											// Check if not a new participant
											if(isNewParticipant === false) {
												
												// Return getting transaction information with the public nonce sum, public blind excess sum, kernel information, and payment proof information with the hardware wallet
												return hardwareWallet.getTransactionInformation(publicNonceSum, publicBlindExcessSum, self.getKernelFeatures(), self.getLockHeight(), self.getRelativeHeight(), (self.getSenderAddress() !== Slate.NO_SENDER_ADDRESS) ? excess : HardwareWallet.NO_KERNEL_COMMIT, self.getSenderAddress(), self.getReceiverSignature(), hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(transactionInformation) {
												
													// Resolve partial signature
													resolve(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_SIGNATURE_INDEX]);
												
												// Catch errors
												}).catch(function(error) {
												
													// Reject error
													reject(error);
												});
											}
											
											// Otherwise
											else {
											
												// Return getting transaction information with the public nonce sum, public blind excess sum, and kernel information with the hardware wallet
												return hardwareWallet.getTransactionInformation(publicNonceSum, publicBlindExcessSum, self.getKernelFeatures(), self.getLockHeight(), self.getRelativeHeight(), (self.getSenderAddress() !== Slate.NO_SENDER_ADDRESS) ? excess : HardwareWallet.NO_KERNEL_COMMIT, self.getReceiverAddress(), Slate.NO_RECEIVER_SIGNATURE, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(transactionInformation) {
												
													// Check if transaction information contains a payment proof
													if(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_PAYMENT_PROOF_INDEX] !== HardwareWallet.NO_PAYMENT_PROOF) {
													
														// Set receiver signature
														self.setReceiverSignature(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_PAYMENT_PROOF_INDEX]);
														
														// Resolve partial signature
														resolve(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_SIGNATURE_INDEX]);
													}
													
													// Otherwise
													else {
												
														// Resolve partial signature
														resolve(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_SIGNATURE_INDEX]);
													}
												
												// Catch errors
												}).catch(function(error) {
												
													// Reject error
													reject(error);
												});
											}
										}
									});
								};
								
								// Return getting the partial signature
								return getPartialSignature().then(function(partialSignature) {
								
									// Resolve partial signature
									resolve(partialSignature);
								
								// Catch errors
								}).catch(function(error) {
								
									// Reject error
									reject(error);
								});
							}
						}
					
					// Catch errors
					}).catch(function(error) {
					
						// Reject error
						reject(error);
					});
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Verify after finalize
		verifyAfterFinalize(baseFee, isMainnet) {
		
			// Check if sorting failed
			if(this.sort() === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if verifying weight failed
			else if(this.verifyWeight() === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if verifying sorted and unique failed
			else if(this.verifySortedAndUnique() === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if verifying no cut through failed
			else if(this.verifyNoCutThrough() === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if verifying fees failed
			else if(this.verifyFees(baseFee) === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if kernel isn't complete
			else if(this.getKernels()[0].isComplete() === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if verifying kernel sums failed
			else if(this.verifyKernelSums() === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if has payment proof and the receiver signature doesn't exist
			else if(this.hasPaymentProof() === true && this.getReceiverSignature() === Slate.NO_RECEIVER_SIGNATURE) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if receiver signature failed to be verified
			else if(this.verifyReceiverSignature(isMainnet) === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise check if verifying no recent duplicate kernels failed
			else if(this.verifyNoRecentDuplicateKernels(isMainnet) === false) {
			
				// Return false
				return false;
			}
			
			// Otherwise
			else {
			
				// Return true
				return true;
			}
		}
		
		// Get weight
		static getWeight(numberOfInputs, numberOfOutputs, numberOfKernels) {
		
			// Get inputs weight
			var inputsWeight = (new BigNumber(numberOfInputs)).multipliedBy(Consensus.BLOCK_INPUT_WEIGHT);
			
			// Get outputs weight
			var outputsWeight = (new BigNumber(numberOfOutputs)).multipliedBy(Consensus.BLOCK_OUTPUT_WEIGHT);
			
			// Get kernels weight
			var kernelsWeight = (new BigNumber(Math.max(numberOfKernels, 1))).multipliedBy(Consensus.BLOCK_KERNEL_WEIGHT);
			
			// Return sum of weights
			return inputsWeight.plus(outputsWeight).plus(kernelsWeight);
		}
		
		// Detect version
		static detectVersion(serializedSlate, isMainnet) {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC wallet
				case Consensus.MWC_WALLET_TYPE:
		
					// Check if a binary serialized slate is provided
					if(serializedSlate instanceof Uint8Array === true) {
					
						// Return version Slatepack
						return Slate.VERSION_SLATEPACK;
					}
					
					// Otherwise check if serialized slate is an object
					else if(Object.isObject(serializedSlate) === true) {
					
						// Check if serialized slate contains an invalid coin type
						if("coin_type" in serializedSlate === true && serializedSlate["coin_type"] !== Slate.COIN_TYPE) {
						
							// Return unknown version
							return Slate.UNKNOWN_VERSION;
						}
						
						// Otherwise check if serialized slate contains an invalid network type
						else if("network_type" in serializedSlate === true && serializedSlate["network_type"] !== Slate.getNetworkType(isMainnet)) {
						
							// Return unknown version
							return Slate.UNKNOWN_VERSION;
						}
					
						// Otherwise check if serialized slate contains a version info structure
						else if("version_info" in serializedSlate === true && Object.isObject(serializedSlate["version_info"]) === true && "version" in serializedSlate["version_info"] === true && (Common.isNumberString(serializedSlate["version_info"]["version"]) === true || serializedSlate["version_info"]["version"] instanceof BigNumber === true) && (new BigNumber(serializedSlate["version_info"]["version"])).isInteger() === true && (new BigNumber(serializedSlate["version_info"]["version"])).isPositive() === true) {
						
							// Return version info structure's version
							return new BigNumber(serializedSlate["version_info"]["version"]);
						}
						
						// Otherwise check if serialized slate contains a version
						else if("version" in serializedSlate === true && (Common.isNumberString(serializedSlate["version"]) === true || serializedSlate["version"] instanceof BigNumber === true) && (new BigNumber(serializedSlate["version"])).isInteger() === true && (new BigNumber(serializedSlate["version"])).isPositive() === true) {
						
							// Return version one
							return Slate.VERSION_ONE;
						}
						
						// Otherwise
						else {
						
							// Return unknown version
							return Slate.UNKNOWN_VERSION;
						}
					}
					
					// Otherwise
					else {
					
						// Return unknown version
						return Slate.UNKNOWN_VERSION;
					}
					
					// Break
					break;
				
				// GRIN wallet
				case Consensus.GRIN_WALLET_TYPE:
				
					// Check if serialized slate is an object
					if(Object.isObject(serializedSlate) === true) {
				
						// Check if serialized slate contains a version
						if("ver" in serializedSlate === true && typeof serializedSlate["ver"] === "string" && Slate.VERSION_PATTERN.test(serializedSlate["ver"]) === true) {
						
							// Return serialized slate's version
							return new BigNumber(serializedSlate["ver"].split(Slate.VERSION_SEPARATOR)[0]);
						}
							
						// Otherwise
						else {
						
							// Return unknown version
							return Slate.UNKNOWN_VERSION;
						}
					}
					
					// Otherwise check if a binary serialized slate is provided
					else if(serializedSlate instanceof Uint8Array === true) {
					
						// Check if serialized slate contains a version
						if(serializedSlate["length"] >= Common.BYTES_IN_A_UINT16) {
						
							// Return serialized slate's version
							return new BigNumber(Common.HEX_PREFIX + Common.toHexString(serializedSlate.subarray(0, Common.BYTES_IN_A_UINT16)));
						}
						
						// Otherwise
						else {
						
							// Return unknown version
							return Slate.UNKNOWN_VERSION;
						}
					}
						
					// Otherwise
					else {
					
						// Return unknown version
						return Slate.UNKNOWN_VERSION;
					}
					
					// Break
					break;
				
				// EPIC wallet
				case Consensus.EPIC_WALLET_TYPE:
				
					// Check if serialized slate is an object
					if(Object.isObject(serializedSlate) === true) {
		
						// Check if serialized slate contains a version info structure
						if("version_info" in serializedSlate === true && Object.isObject(serializedSlate["version_info"]) === true && "version" in serializedSlate["version_info"] === true && (Common.isNumberString(serializedSlate["version_info"]["version"]) === true || serializedSlate["version_info"]["version"] instanceof BigNumber === true) && (new BigNumber(serializedSlate["version_info"]["version"])).isInteger() === true && (new BigNumber(serializedSlate["version_info"]["version"])).isPositive() === true) {
						
							// Return version info structure's version
							return new BigNumber(serializedSlate["version_info"]["version"]);
						}
						
						// Otherwise check if serialized slate contains a version
						else if("version" in serializedSlate === true && (Common.isNumberString(serializedSlate["version"]) === true || serializedSlate["version"] instanceof BigNumber === true) && (new BigNumber(serializedSlate["version"])).isInteger() === true && (new BigNumber(serializedSlate["version"])).isPositive() === true) {
						
							// Return version one
							return Slate.VERSION_ONE;
						}
						
						// Otherwise
						else {
						
							// Return unknown version
							return Slate.UNKNOWN_VERSION;
						}
					}
						
					// Otherwise
					else {
					
						// Return unknown version
						return Slate.UNKNOWN_VERSION;
					}
					
					// Break
					break;
			}
		}
		
		// Is sorted and unique
		static isSortedAndUnique(hashes) {
		
			// Go through all hashes
			for(var i = 1; i < hashes["length"]; ++i) {
			
				// Get last hash
				var lastHash = hashes[i - 1];
				
				// Get current hash
				var currentHash = hashes[i];
				
				// Get result of comparing the hashes
				var compareResult = currentHash.compare(lastHash);
				
				// Check if current hash is less than or equal to the last hash
				if(compareResult === Common.SORT_LESS_THAN || compareResult === Common.SORT_EQUAL)
				
					// Return false
					return false;
			}
		
			// Return true
			return true;
		}
		
		// Get network type
		static getNetworkType(isMainnet) {
		
			// Check if is mainnet
			if(isMainnet === true) {
			
				// Return network type
				return "mainnet";
			}
			
			// Otherwise
			else {
			
				// Return network type
				return "floonet";
			}
		}
		
		
		
		// Uncompact proof address
		static uncompactProofAddress(bitReader, isMainnet) {
		
			// Check if proof address is MQS proof address
			if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
			
				// Get proof address
				var proofAddress = Mqs.publicKeyToMqsAddress(Slate.uncompactPublicKey(bitReader), isMainnet);
			}
			
			// Otherwise
			else {
			
				// Get proof address
				var proofAddress = Tor.publicKeyToTorAddress(bitReader.getBytes(Crypto.ED25519_PUBLIC_KEY_LENGTH));
			}
			
			// Return proof address
			return proofAddress;
		}
		
		// Compact proof address
		static compactProofAddress(proofAddress, isMainnet, bitWriter) {
		
			// Check proof address's length
			switch(proofAddress["length"]) {
			
				// MQS address length
				case Mqs.ADDRESS_LENGTH:
				
					// Write that proof address is an MQS proof address
					bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
					
					// Write proof address
					Slate.compactPublicKey(Mqs.mqsAddressToPublicKey(proofAddress, isMainnet), bitWriter);
					
					// Break
					break;
			
				// Tor address length
				case Tor.ADDRESS_LENGTH:
				
					// Write that proof address is a Tor proof address
					bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
					
					// Write proof address
					bitWriter.setBytes(Tor.torAddressToPublicKey(proofAddress));
				
					// Break
					break;
			}
		}
		
		// Purpose to text
		static purposeToText(purpose) {
		
			// Check purpose
			switch(purpose) {
			
				// Send initial purpose
				case Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL:
				
					// Return purpose as text
					return "S1";
				
				// Send response purpose
				case Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE:
				
					// Return purpose as text
					return "S2";
			}
		}
		
		// Send request
		static sendRequest(request, transfer) {
		
			// Get current request index
			var currentRequestIndex = Slate.requestIndex++;
			
			// Check if current request index is at the max safe integer
			if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
			
				// Reset request index
				Slate.requestIndex = 0;
			
			// Add current request index to request
			request.unshift(currentRequestIndex);
			
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Response current request index event
				$(document).one(Slate.RESPONSE_EVENT + currentRequestIndex.toFixed(), function(event, response) {
				
					// Resolve response
					resolve(response);
				});
			
				// Send worker the request
				Slate.worker.postMessage(request, transfer);
			});
		}
		
		// Version one
		static get VERSION_ONE() {
		
			// Return version one
			return new BigNumber(1);
		}
		
		// Version two
		static get VERSION_TWO() {
		
			// Return version two
			return new BigNumber(2);
		}
		
		// Version three
		static get VERSION_THREE() {
		
			// Return version three
			return new BigNumber(3);
		}
		
		// Version four
		static get VERSION_FOUR() {
		
			// Return version four
			return new BigNumber(4);
		}
		
		// Version Slatepack
		static get VERSION_SLATEPACK() {
		
			// Return version Slatepack
			return "SP";
		}
		
		// Unknown version
		static get UNKNOWN_VERSION() {
		
			// Return unknown version
			return null;
		}
		
		// No lock height
		static get NO_LOCK_HEIGHT() {
		
			// Return no lock height
			return new BigNumber(0);
		}
		
		// No relative height
		static get NO_RELATIVE_HEIGHT() {
		
			// Return no relative height
			return null;
		}
		
		// Minimum number of participants
		static get MINIMUM_NUMBER_OF_PARTICIPANTS() {
		
			// Return minimum number of participants
			return 2;
		}
		
		// Zero offset
		static get ZERO_OFFSET() {
		
			// No zero offset
			return Crypto.ZERO_BLINDING_FACTOR;
		}
		
		// No participant
		static get NO_PARTICIPANT() {
		
			// Return no participant
			return null;
		}
		
		// No receiver address
		static get NO_RECEIVER_ADDRESS() {
		
			// Return no receiver address
			return null;
		}
		
		// No receiver signature
		static get NO_RECEIVER_SIGNATURE() {
		
			// Return no receiver signature
			return null;
		}
		
		// No sender address
		static get NO_SENDER_ADDRESS() {
		
			// Return no sender address
			return null;
		}
		
		// Zero commit
		static get ZERO_COMMIT() {
		
			// Return zero commit
			return (new Uint8Array(Crypto.COMMIT_LENGTH)).fill(0);
		}
		
		// Body weight output factor
		static get BODY_WEIGHT_OUTPUT_FACTOR() {
		
			// Return body weight output factor
			return 4;
		}
		
		// Minimum body weight
		static get MINIMUM_BODY_WEIGHT() {
		
			// Return minimum body weight
			return 1;
		}
		
		// Default number of participants
		static get DEFAULT_NUMBER_OF_PARTICIPANTS() {
		
			// Return default number of participants
			return new BigNumber(2);
		}
		
		// Coin type
		static get COIN_TYPE() {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC wallet
				case Consensus.MWC_WALLET_TYPE:
				
					// Return coin type
					return "mwc";
			}
		}
		
		// Compact boolean length
		static get COMPACT_BOOLEAN_LENGTH() {
		
			// Return compact boolean length
			return 1;
		}
		
		// Compact boolean false
		static get COMPACT_BOOLEAN_FALSE() {
		
			// Return compact boolean false
			return 0;
		}
		
		// Compact boolean true
		static get COMPACT_BOOLEAN_TRUE() {
		
			// Return compact boolean true
			return Slate.COMPACT_BOOLEAN_FALSE + 1;
		}
		
		// Compact slate purpose length
		static get COMPACT_SLATE_PURPOSE_LENGTH() {
		
			// Return compact slate purpose length
			return 3;
		}
		
		// Compact slate purpose send initial
		static get COMPACT_SLATE_PURPOSE_SEND_INITIAL() {
		
			// Return compact slate purpose send initial
			return 0;
		}
		
		// Compact slate purpose send response
		static get COMPACT_SLATE_PURPOSE_SEND_RESPONSE() {
		
			// Return compact slate purpose send response
			return Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL + 1;
		}
		
		// Compact proof signature length length
		static get COMPACT_PROOF_SIGNATURE_LENGTH_LENGTH() {
		
			// Return compact proof signature length length
			return 4;
		}
		
		// Compact number of hundreds length
		static get COMPACT_NUMBER_OF_HUNDREDS_LENGTH() {
		
			// Return compact number of hundreds length
			return 3;
		}
		
		// Compact number of digits length
		static get COMPACT_NUMBER_OF_DIGITS_LENGTH() {
		
			// Return compact number of digits length
			return 6;
		}
		
		// Compact hundreds scaling factor
		static get COMPACT_HUNDREDS_SCALING_FACTOR() {
		
			// Return compact hundreds scaling factor
			return 100;
		}
		
		// Compact public key length length
		static get COMPACT_PUBLIC_KEY_LENGTH_LENGTH() {
		
			// Return compact public key length length
			return 7;
		}
		
		// No public blind excess
		static get NO_PUBLIC_BLIND_EXCESS() {
		
			// Return no public blind excess
			return null;
		}
		
		// Version pattern
		static get VERSION_PATTERN() {
		
			// Return version pattern
			return /^(?:0|[1-9]\d*):(?:0|[1-9]\d*)$/u;
		}
		
		// Version separator
		static get VERSION_SEPARATOR() {
		
			// Return version separator
			return ":";
		}
		
		// Minimum amount
		static get MINIMUM_AMOUNT() {
		
			// Return minimum amount
			return 1;
		}
		
		// Message status offset
		static get MESSAGE_STATUS_OFFSET() {
		
			// Return message status offset
			return Slate.MESSAGE_TYPE_OFFSET + 1;
		}
		
		// Message response offset
		static get MESSAGE_RESPONSE_OFFSET() {
		
			// Return message response offset
			return Slate.MESSAGE_TYPE_OFFSET + 1;
		}
		
		// Response event
		static get RESPONSE_EVENT() {
		
			// Return response event
			return "SlateResponseEvent";
		}
		
		// Worker file location
		static get WORKER_FILE_LOCATION() {
		
			// Return worker file location
			return "." + getResource("./scripts/slate_worker.js");
		}
}


// Main function

// Set global object's slate
globalThis["Slate"] = Slate;
