"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Gnosis = exports.GnosisApi = void 0;
const stream_1 = require("stream");
const crypto_wallet_core_1 = require("@bitpay-labs/crypto-wallet-core");
const __1 = require("../../");
const config_1 = require("../../../../services/config");
const multisig_1 = require("../abi/multisig");
const multisigTransform_1 = require("../api/multisigTransform");
const populateEffectsTransform_1 = require("../api/populateEffectsTransform");
const populateReceiptTransform_1 = require("../api/populateReceiptTransform");
const transform_1 = require("../api/transform");
const block_1 = require("../models/block");
const transaction_1 = require("../models/transaction");
function getCSP(chain, network) {
    return __1.ChainStateProvider.get({ chain, network });
}
class GnosisApi {
    constructor() {
        this.gnosisFactories = {
            'ETH': {
                testnet: '0x2C992817e0152A65937527B774c7A99a84603045',
                mainnet: '0x6e95C8E8557AbC08b46F3c347bA06F8dC012763f'
            },
            'MATIC': {
                mainnet: '0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2'
            }
        };
        this.MULTISIG_TX_PROPOSAL_EXPIRE_TIME = 48 * 3600 * 1000;
    }
    async multisigFor(chain, network, address) {
        const { web3 } = await getCSP(chain, network).getWeb3(network);
        const contract = new web3.eth.Contract(multisig_1.MultisigAbi, address);
        return contract;
    }
    async getMultisigContractInstantiationInfo(chain, network, sender, txId) {
        const networkConfig = config_1.Config.chainConfig({ chain: 'ETH', network });
        const { gnosisFactory = this.gnosisFactories[chain][network] } = networkConfig;
        const query = { chain, network, txid: txId };
        const found = await transaction_1.EVMTransactionStorage.collection.findOne(query);
        const blockHeight = found && found.blockHeight ? found.blockHeight : null;
        if (!blockHeight || blockHeight < 0)
            return Promise.resolve([]);
        const contract = await this.multisigFor(chain, network, gnosisFactory);
        const contractInfo = await contract.getPastEvents('ContractInstantiation', {
            fromBlock: crypto_wallet_core_1.Utils.toHex(blockHeight),
            toBlock: crypto_wallet_core_1.Utils.toHex(blockHeight)
        });
        return this.convertMultisigContractInstantiationInfo(contractInfo.filter(info => typeof info.returnValues.sender === 'string' && info.returnValues.sender.toLowerCase() === sender.toLowerCase()));
    }
    convertMultisigContractInstantiationInfo(contractInstantiationInfo) {
        return contractInstantiationInfo.map(this.convertContractInstantiationInfo);
    }
    convertContractInstantiationInfo(transfer) {
        const { blockHash, blockNumber, transactionHash, returnValues, transactionIndex } = transfer;
        return {
            blockHash,
            blockNumber,
            transactionHash,
            transactionIndex,
            hash: transactionHash,
            sender: returnValues['sender'],
            instantiation: returnValues['instantiation']
        };
    }
    async getMultisigTxpsInfo(chain, network, multisigContractAddress) {
        const contract = await this.multisigFor(chain, network, multisigContractAddress);
        const time = Math.floor(Date.now()) - this.MULTISIG_TX_PROPOSAL_EXPIRE_TIME;
        const [block] = await block_1.EVMBlockStorage.collection
            .find({
            chain,
            network,
            timeNormalized: { $gte: new Date(time) }
        })
            .limit(1)
            .toArray();
        const blockHeight = block.height;
        const [confirmationInfo, revocationInfo, executionInfo, executionFailure] = await Promise.all([
            contract.getPastEvents('Confirmation', {
                fromBlock: blockHeight,
                toBlock: 'latest'
            }),
            contract.getPastEvents('Revocation', {
                fromBlock: blockHeight,
                toBlock: 'latest'
            }),
            contract.getPastEvents('Execution', {
                fromBlock: blockHeight,
                toBlock: 'latest'
            }),
            contract.getPastEvents('ExecutionFailure', {
                fromBlock: blockHeight,
                toBlock: 'latest'
            })
        ]);
        const executionTransactionIdArray = executionInfo.map(i => i.returnValues.transactionId);
        const contractTransactionsInfo = [...confirmationInfo, ...revocationInfo, ...executionFailure];
        const multisigTxpsInfo = contractTransactionsInfo.filter(i => !executionTransactionIdArray.includes(i.returnValues.transactionId));
        return this.convertMultisigTxpsInfo(multisigTxpsInfo);
    }
    convertMultisigTxpsInfo(multisigTxpsInfo) {
        return multisigTxpsInfo.map(this.convertTxpsInfo);
    }
    convertTxpsInfo(transfer) {
        const { blockHash, blockNumber, transactionHash, returnValues, transactionIndex, event } = transfer;
        return {
            blockHash,
            blockNumber,
            transactionHash,
            transactionIndex,
            hash: transactionHash,
            sender: returnValues['sender'],
            transactionId: returnValues['transactionId'],
            event
        };
    }
    async getMultisigInfo(chain, network, multisigContractAddress) {
        const contract = await this.multisigFor(chain, network, multisigContractAddress);
        const owners = await contract.methods.getOwners().call();
        const required = await contract.methods.required().call();
        return {
            owners,
            required
        };
    }
    async streamGnosisWalletTransactions(params) {
        const { chain, network, multisigContractAddress, res, args } = params;
        const transactionQuery = getCSP(chain, network).getWalletTransactionQuery(params);
        delete transactionQuery.wallets;
        delete transactionQuery['wallets.0'];
        let query;
        if (args.tokenAddress) {
            query = {
                $or: [
                    {
                        ...transactionQuery,
                        to: args.tokenAddress,
                        'abiType.params.0.value': multisigContractAddress.toLowerCase()
                    },
                    {
                        ...transactionQuery,
                        'internal.action.to': args.tokenAddress.toLowerCase(),
                        'internal.action.from': multisigContractAddress.toLowerCase()
                    },
                    {
                        ...transactionQuery,
                        'effects.contractAddress': args.tokenAddress,
                        'effects.from': multisigContractAddress
                    }
                ]
            };
        }
        else {
            query = {
                $or: [
                    { ...transactionQuery, to: multisigContractAddress },
                    { ...transactionQuery, 'internal.action.to': multisigContractAddress.toLowerCase() },
                    { ...transactionQuery, 'effects.to': multisigContractAddress }
                ]
            };
        }
        let transactionStream = new stream_1.Readable({ objectMode: true });
        const ethTransactionTransform = new transform_1.EVMListTransactionsStream([multisigContractAddress, args.tokenAddress]);
        const EVM = getCSP(chain, network);
        const populateReceipt = new populateReceiptTransform_1.PopulateReceiptTransform(EVM);
        const populateEffects = new populateEffectsTransform_1.PopulateEffectsTransform(EVM);
        // Store cursor reference for cleanup
        const cursor = transaction_1.EVMTransactionStorage.collection
            .find(query)
            .sort({ blockTimeNormalized: 1 })
            .addCursorFlag('noCursorTimeout', true);
        // Add cleanup handlers when client disconnects
        let cursorClosed = false;
        const cleanupCursor = () => {
            if (!cursorClosed) {
                cursorClosed = true;
                try {
                    cursor.close();
                }
                catch {
                    // Cursor might already be closed, ignore
                }
            }
        };
        const { req } = params;
        req.on('close', cleanupCursor);
        res.on('close', cleanupCursor);
        transactionStream = cursor.pipe(populateEffects); // For old db entries
        if (multisigContractAddress) {
            const multisigTransform = new multisigTransform_1.MultisigRelatedFilterTransform(multisigContractAddress, args.tokenAddress);
            transactionStream = transactionStream.pipe(multisigTransform);
        }
        transactionStream
            .pipe(populateReceipt)
            .pipe(ethTransactionTransform)
            .pipe(res);
    }
}
exports.GnosisApi = GnosisApi;
exports.Gnosis = new GnosisApi();
//# sourceMappingURL=gnosis.js.map