Source: nem/TransactionFactory.js

const { Address } = require('./Network');
const nc = require('./models');
const { Hash256, PublicKey } = require('../CryptoTypes');
const { RuleBasedTransactionFactory } = require('../RuleBasedTransactionFactory');
const { uint8ToHex } = require('../utils/converter');

/**
 * Factory for creating NEM transactions.
 */
class TransactionFactory {
	/**
	 * Creates a factory for the specified network.
	 * @param {Network} network NEM network.
	 * @param {Map} typeRuleOverrides Type rule overrides.
	 */
	constructor(network, typeRuleOverrides) {
		this.factory = TransactionFactory.buildRules(typeRuleOverrides);
		this.network = network;
	}

	/**
	 * Creates a transaction from a transaction descriptor.
	 * @param {object} transactionDescriptor Transaction descriptor.
	 * @returns {object} Newly created transaction.
	 */
	create(transactionDescriptor) {
		const transaction = this.factory.createFromFactory(nc.TransactionFactory.createByName, {
			...transactionDescriptor,
			network: this.network.identifier
		});

		// hack: explicitly translate transfer message
		if (nc.TransactionType.TRANSFER === transaction.type && 'string' === typeof (transaction.message.message))
			transaction.message.message = new TextEncoder().encode(transaction.message.message);

		return transaction;
	}

	/**
	 * Converts a transaction to a non-verifiable transaction.
	 * @param {object} transaction Transaction object.
	 * @returns {object} Non-verifiable transaction object.
	 */
	static toNonVerifiableTransaction(transaction) {
		let nonVerifiableClassName = transaction.constructor.name;
		if (0 !== nonVerifiableClassName.indexOf('NonVerifiable'))
			nonVerifiableClassName = `NonVerifiable${nonVerifiableClassName}`;

		const NonVerifiableClass = nc[nonVerifiableClassName];
		const nonVerifiableTransaction = new NonVerifiableClass();
		Object.getOwnPropertyNames(transaction).forEach(key => {
			nonVerifiableTransaction[key] = transaction[key];
		});

		return nonVerifiableTransaction;
	}

	/**
	 * Attaches a signature to a transaction.
	 * @param {object} transaction Transaction object.
	 * @param {Signature} signature Signature to attach.
	 * @returns {string} JSON transaction payload.
	 */
	static attachSignature(transaction, signature) {
		transaction.signature = new nc.Signature(signature.bytes);

		const transactionHex = uint8ToHex(this.toNonVerifiableTransaction(transaction).serialize());
		const signatureHex = signature.toString();
		const jsonPayload = `{"data":"${transactionHex}", "signature":"${signatureHex}"}`;
		return jsonPayload;
	}

	static _nemTypeConverter(value) {
		if (value instanceof Address) {
			// yes, unfortunately, nem's Address is 40 bytes string, but we need to pass it as actual bytes not to confuse ByteArray
			return new nc.Address(new TextEncoder().encode(value.toString()));
		}

		return undefined;
	}

	static buildRules(typeRuleOverrides) {
		const factory = new RuleBasedTransactionFactory(nc, this._nemTypeConverter, typeRuleOverrides);
		factory.autodetect();

		[
			'LinkAction', 'MessageType', 'MosaicSupplyChangeAction', 'MosaicTransferFeeType',
			'MultisigAccountModificationType', 'NetworkType', 'TransactionType'
		].forEach(name => { factory.addEnumParser(name); });

		[
			'Message', 'NamespaceId', 'MosaicId', 'Mosaic', 'SizePrefixedMosaic', 'MosaicLevy',
			'MosaicProperty', 'SizePrefixedMosaicProperty', 'MosaicDefinition',
			'MultisigAccountModification', 'SizePrefixedMultisigAccountModification'
		].forEach(name => { factory.addStructParser(name); });

		const sdkTypeMapping = {
			Address,
			Hash256,
			PublicKey
		};
		Object.keys(sdkTypeMapping).forEach(name => { factory.addPodParser(name, sdkTypeMapping[name]); });

		[
			'struct:SizePrefixedMosaic', 'struct:SizePrefixedMosaicProperty', 'struct:SizePrefixedMultisigAccountModification'
		].forEach(name => { factory.addArrayParser(name); });

		return factory;
	}
}

module.exports = { TransactionFactory };