var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Transaction_addressVersion;
import { TX_VERSION, MAGIC_NUMBER } from "../../consts";
import logger from "../../logging";
import { sha256, num2hexstring, reverseHex, StringStream, num2VarInt, generateRandomArray, ab2hexstring, HexString, BigInteger, serializeArrayOf, } from "../../u";
import { Account, sign, getAddressFromScriptHash } from "../../wallet";
import { TransactionAttribute, Witness, Signer, } from "../components";
import { deserializeVersion, deserializeScript, deserializeFee, deserializeAttributes, deserializeWitnesses, deserializeNonce, deserializeValidUntilBlock, deserializeSigners, } from "./main";
const log = logger("tx");
export class Transaction {
    constructor(tx = {}) {
        /**
         * Stores the address version if the data is made available.
         */
        _Transaction_addressVersion.set(this, null);
        const { version, nonce, systemFee, networkFee, validUntilBlock, attributes, signers = [], witnesses, script, } = tx;
        this.version = version !== null && version !== void 0 ? version : TX_VERSION;
        this.nonce = nonce !== null && nonce !== void 0 ? nonce : parseInt(ab2hexstring(generateRandomArray(4)), 16);
        this.validUntilBlock = validUntilBlock !== null && validUntilBlock !== void 0 ? validUntilBlock : 0;
        this.attributes = Array.isArray(attributes)
            ? attributes.map((a) => new TransactionAttribute(a))
            : [];
        this.signers = [];
        this.witnesses = [];
        signers.forEach((s) => this.addSigner(s));
        this.witnesses = Array.isArray(witnesses)
            ? witnesses.map((a) => new Witness(a))
            : [];
        this.systemFee =
            systemFee instanceof BigInteger
                ? systemFee
                : BigInteger.fromNumber(systemFee !== null && systemFee !== void 0 ? systemFee : 0);
        this.networkFee =
            networkFee instanceof BigInteger
                ? networkFee
                : BigInteger.fromNumber(networkFee !== null && networkFee !== void 0 ? networkFee : 0);
        this.script = HexString.fromHex(script !== null && script !== void 0 ? script : "");
    }
    /**
     * transation invoker in script hash in big endian
     */
    get sender() {
        return this.signers && this.signers.length > 0
            ? this.signers[0].account
            : HexString.fromHex("");
    }
    static fromJson(input) {
        const output = new Transaction({
            version: input.version,
            nonce: input.nonce,
            systemFee: BigInteger.fromDecimal(input.sysfee, 0),
            networkFee: BigInteger.fromDecimal(input.netfee, 0),
            validUntilBlock: input.validuntilblock,
            attributes: input.attributes.map((a) => TransactionAttribute.fromJson(a)),
            signers: input.signers.map((c) => Signer.fromJson(c)),
            script: HexString.fromBase64(input.script),
            witnesses: input.witnesses.map((w) => Witness.fromJson(w)),
        });
        // We attempt to extract the address version here.
        if (input.sender) {
            __classPrivateFieldSet(output, _Transaction_addressVersion, new Account(input.sender).addressVersion, "f");
        }
        return output;
    }
    get [(_Transaction_addressVersion = new WeakMap(), Symbol.toStringTag)]() {
        return "Transaction";
    }
    /**
     * Transaction hash.
     */
    hash() {
        return reverseHex(sha256(this.serialize(false)));
    }
    get size() {
        return (this.headerSize +
            num2VarInt(this.attributes.length).length / 2 +
            this.attributes.reduce((sum, a) => sum + a.size, 0) +
            num2VarInt(this.signers.length).length / 2 +
            this.signers.reduce((sum, c) => sum + c.size, 0) +
            num2VarInt(this.script.byteLength).length / 2 +
            this.script.byteLength +
            num2VarInt(this.witnesses.length).length / 2 +
            this.witnesses.reduce((sum, w) => sum + w.size, 0));
    }
    /**
     * Returns the fees as a integer string.
     * Divide this by 100000000 to get the decimal value.
     */
    get fees() {
        return this.systemFee.add(this.networkFee).toString();
    }
    /**
     * Transaction header size
     */
    get headerSize() {
        return (
        /* version */ 1 +
            /* nonce */ 4 +
            /* systemFee */ 8 +
            /* networkFee */ 8 +
            /* validUntilBlock */ 4);
    }
    /**
     * Deserializes a hexstring into a Transaction object.
     * @param hexstring - hexstring of the transaction.
     */
    static deserialize(hex) {
        const ss = new StringStream(hex);
        let txObj = deserializeVersion(ss);
        txObj = deserializeNonce(ss, txObj);
        txObj = deserializeFee(ss, txObj);
        txObj = deserializeValidUntilBlock(ss, txObj);
        txObj = deserializeSigners(ss, txObj);
        txObj = deserializeAttributes(ss, txObj);
        txObj = deserializeScript(ss, txObj);
        if (!ss.isEmpty()) {
            txObj = deserializeWitnesses(ss, txObj);
        }
        return new Transaction(txObj);
    }
    addSigner(newSigner) {
        const acctHashes = this.signers.map((signer) => signer.account);
        const newHash = HexString.fromHex(newSigner.account);
        if (acctHashes.find((hash) => hash.equals(newHash))) {
            throw new Error(`Cannot add duplicate cosigner: ${newSigner.account}`);
        }
        this.signers.push(new Signer(newSigner));
        this.orderWitnesses();
        return this;
    }
    addAttribute(attr) {
        this.attributes.push(new TransactionAttribute(attr));
        return this;
    }
    /**
     * Adds Witness to the Transaction and automatically sorts the witnesses.
     * If the witness already exists, attempts to override the invocationScript
     * with the newly provided one.
     * @param obj - Witness object to add as witness
     */
    addWitness(obj) {
        const newWitness = new Witness(obj);
        const existingWitness = this.witnesses.find((w) => w.verificationScript.equals(newWitness.verificationScript));
        if (existingWitness && newWitness.invocationScript.byteLength !== 0) {
            // Existing witness, we simply replace the signature.
            existingWitness.invocationScript = newWitness.invocationScript;
            return this;
        }
        this.witnesses.push(newWitness);
        this.orderWitnesses();
        return this;
    }
    orderWitnesses() {
        this.signers.forEach((signer, ind) => {
            const signerScriptHash = signer.account.toBigEndian();
            const witnessIndex = this.witnesses.findIndex((w) => w.scriptHash === signerScriptHash);
            if (witnessIndex !== -1) {
                const extractedWitness = this.witnesses.splice(witnessIndex, 1)[0];
                this.witnesses.splice(ind, 0, extractedWitness);
            }
        });
        return this;
    }
    /**
     * Serialize the transaction and return it as a hexstring.
     * @param signed - whether to serialize the signatures. Signing requires it to be serialized without the signatures.
     */
    serialize(signed = true) {
        if (this.version !== 0) {
            throw new Error(`Version must be 0`);
        }
        let out = "";
        out += num2hexstring(this.version);
        out += num2hexstring(this.nonce, 4, true);
        out += this.systemFee.toReverseHex().padEnd(16, "0");
        out += this.networkFee.toReverseHex().padEnd(16, "0");
        out += num2hexstring(this.validUntilBlock, 4, true);
        out += serializeArrayOf(this.signers);
        out += serializeArrayOf(this.attributes);
        out += num2VarInt(this.script.byteLength);
        out += this.script.toString();
        if (signed) {
            out += serializeArrayOf(this.witnesses);
        }
        return out;
    }
    /**
     * Signs a transaction.
     * @param signingKey - Account, privateKey or WIF
     * @param networkMagic - Magic number of network found in protocol.json.
     * @param k - optional nonce for signature generation. Setting this causes the signature to be deterministic.
     */
    sign(signingKey, networkMagic = MAGIC_NUMBER.MainNet, k) {
        if (typeof signingKey === "string") {
            signingKey = new Account(signingKey);
        }
        const signature = sign(num2hexstring(networkMagic, 4, true) + reverseHex(this.hash()), signingKey.privateKey, k);
        log.info(`Signed Transaction with Account: ${signingKey.label}`);
        this.addWitness(Witness.fromSignature(signature, signingKey.publicKey));
        return this;
    }
    /**
     * Hexstring of transaction for signing.
     * @param networkMagic - magic number of network found in protocol.json.
     */
    getMessageForSigning(networkMagic) {
        return num2hexstring(networkMagic, 4, true) + reverseHex(this.hash());
    }
    equals(other) {
        if (other instanceof Transaction) {
            return this.hash() === other.hash();
        }
        return this.hash() === new Transaction(other).hash();
    }
    export() {
        return {
            version: this.version,
            nonce: this.nonce,
            systemFee: this.systemFee.toString(),
            networkFee: this.networkFee.toString(),
            validUntilBlock: this.validUntilBlock,
            attributes: this.attributes.map((a) => a.export()),
            signers: this.signers.map((s) => s.export()),
            witnesses: this.witnesses.map((a) => a.export()),
            script: this.script.toBigEndian(),
        };
    }
    toJson() {
        return {
            size: this.size,
            version: this.version,
            nonce: this.nonce,
            sender: this.sender.byteLength === 0 || __classPrivateFieldGet(this, _Transaction_addressVersion, "f") === null
                ? ""
                : getAddressFromScriptHash(this.sender.toBigEndian(), __classPrivateFieldGet(this, _Transaction_addressVersion, "f")),
            sysfee: this.systemFee.toDecimal(0),
            netfee: this.networkFee.toDecimal(0),
            validuntilblock: this.validUntilBlock,
            attributes: this.attributes.map((a) => a.toJson()),
            signers: this.signers.map((c) => c.toJson()),
            script: this.script.toBase64(),
            witnesses: this.witnesses.map((w) => w.toJson()),
        };
    }
    getScriptHashesForVerifying() {
        return this.signers.map((s) => s.account.toBigEndian());
    }
}
/**
 * Maximum duration in blocks that a transaction can stay valid in the mempool.
 * This is 24 hours based on 15s blocktime.
 */
Transaction.MAX_TRANSACTION_LIFESPAN = 5760;
export default Transaction;
//# sourceMappingURL=Transaction.js.map