import { tx, sc, u } from "@cityofzion/neon-core";
export class TransactionBuilder {
    constructor() {
        this.vmScripts = [];
        this.networkFee = u.BigInteger.fromNumber(0);
        this.systemFee = u.BigInteger.fromNumber(0);
        this.validUntilBlock = 0;
        this.attributes = [];
        this.signers = [];
        this.witnesses = [];
    }
    static newBuilder() {
        return new TransactionBuilder();
    }
    /**
     * Adds the logic for claiming gas. Alternatively, you may just trigger the gas claim by performing an actual transaction involving NEO.
     * @param account - Account to claim gas on.
     */
    addGasClaim(account) {
        const address = account.address;
        return this.addContractCall(sc.NeoContract.INSTANCE.transfer(address, address, 0)).addBasicSignatureField(account);
    }
    /**
     *  Adds the logic to send tokens around.
     * @param account - originating account
     * @param destination - account where the tokens will be sent
     * @param tokenScriptHash - scripthash of the token contract
     * @param amt - Amount of tokens in integer format.
     */
    addNep17Transfer(account, destination, tokenScriptHash, amt) {
        const address = account.address;
        const contract = new sc.Nep17Contract(tokenScriptHash);
        return this.addContractCall(contract.transfer(address, destination, amt)).addBasicSignatureField(account);
    }
    /**
     * Adds the logic to vote for a candidate.
     * @param account - Account containing the NEO.
     * @param candidatePublicKey - The candidate's publickey in hex big endian.
     */
    addVote(account, candidatePublicKey) {
        const address = account.address;
        return this.addContractCall(sc.NeoContract.INSTANCE.vote(address, candidatePublicKey)).addBasicSignatureField(account);
    }
    /**
     * Adds a signature field representing the request for a signature from this account.
     * Under the hood, this adds a Signer and empty Witness to the transaction. The Signer defaults to the basic scope.
     * For more advanced usage, plase manually add your own Signers and Witnesses.
     * @param account - account that has to sign the transaction.
     */
    addBasicSignatureField(account) {
        return this.addSigners({
            account: account.scriptHash,
            scopes: tx.WitnessScope.CalledByEntry,
        }).addEmptyWitness(account);
    }
    /**
     * Sets an account to pay fees for this transaction.
     * The first Signer defaults to the payer.
     * @param account - Account to pay fees from.
     */
    setFeeAccount(account) {
        const ind = this.signers.findIndex((s) => s.account.equals(account.scriptHash));
        // Signer exists. We shift it to first in array to become the sender.
        if (ind > 0) {
            const s = this.signers.splice(ind, 1)[0];
            this.signers.unshift(s);
            return this;
        }
        else if (ind === -1) {
            this.signers.unshift(new tx.Signer({
                account: account.scriptHash,
                scopes: tx.WitnessScope.None,
            }));
            return this.addEmptyWitness(account);
        }
        // Account is already the sender.
        return this;
    }
    /**
     * Add signers. Will deduplicate signers and merge scopes.
     * This does not add any Witnesses.
     */
    addSigners(...signers) {
        for (const newSigner of signers) {
            const ind = this.signers.findIndex((s) => s.account.equals(newSigner.account));
            if (ind !== -1) {
                this.signers[ind].merge(newSigner);
            }
            else {
                this.signers.push(new tx.Signer(newSigner));
            }
        }
        return this;
    }
    /**
     * You can add multiple intents to the transaction
     */
    addContractCall(...contractCalls) {
        this.vmScripts = this.vmScripts.concat(contractCalls);
        return this;
    }
    addScript(hexString) {
        this.vmScripts.push(hexString);
        return this;
    }
    /**
     * Add an attribute.
     * @param usage - The usage type. Do refer to txAttrUsage enum values for all available options.
     * @param data - The data as hexstring.
     */
    addAttributes(...attrs) {
        this.attributes = this.attributes.concat(attrs.map((a) => new tx.TransactionAttribute(a)));
        return this;
    }
    /**
     * Adds an unsigned witness to the transaction.
     * Will deduplicate witnesses based on verificationScript.
     * Required to calculate the network fee correctly.
     */
    addEmptyWitness(account) {
        const verificationScript = u.HexString.fromBase64(account.contract.script);
        if (!this.witnesses.some((w) => w.verificationScript.equals(verificationScript))) {
            this.witnesses.push(new tx.Witness({
                verificationScript,
                invocationScript: u.HexString.fromHex(""),
            }));
        }
        return this;
    }
    addEmptyWitnesses(...accounts) {
        accounts.forEach((a) => this.addEmptyWitness(a));
        return this;
    }
    setSystemFee(fee) {
        this.systemFee = fee;
        return this;
    }
    setNetworkFee(fee) {
        this.networkFee = fee;
        return this;
    }
    build() {
        return new tx.Transaction({
            networkFee: this.networkFee,
            systemFee: this.systemFee,
            signers: this.signers,
            attributes: this.attributes,
            validUntilBlock: this.validUntilBlock,
            script: this.vmScripts
                .reduce((sb, cc) => typeof cc === "string"
                ? sb.appendScript(cc)
                : sb.emitContractCall(cc), new sc.ScriptBuilder())
                .build(),
            witnesses: this.witnesses,
        });
    }
}
//# sourceMappingURL=TransactionBuilder.js.map