import * as AbiConstructor from 'ox/AbiConstructor';
import * as AbiFunction from 'ox/AbiFunction';
import { parseAccount } from '../../accounts/utils/parseAccount.js';
import { ethAddress, zeroAddress } from '../../constants/address.js';
import { deploylessCallViaBytecodeBytecode } from '../../constants/contracts.js';
import { BaseError } from '../../errors/base.js';
import { encodeFunctionData, } from '../../utils/abi/encodeFunctionData.js';
import { hexToBigInt } from '../../utils/index.js';
import { createAccessList, } from './createAccessList.js';
import { simulateBlocks, } from './simulateBlocks.js';
const getBalanceCode = '0x6080604052348015600e575f80fd5b5061016d8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063f8b2cb4f1461002d575b5f80fd5b610047600480360381019061004291906100db565b61005d565b604051610054919061011e565b60405180910390f35b5f8173ffffffffffffffffffffffffffffffffffffffff16319050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100aa82610081565b9050919050565b6100ba816100a0565b81146100c4575f80fd5b50565b5f813590506100d5816100b1565b92915050565b5f602082840312156100f0576100ef61007d565b5b5f6100fd848285016100c7565b91505092915050565b5f819050919050565b61011881610106565b82525050565b5f6020820190506101315f83018461010f565b9291505056fea26469706673582212203b9fe929fe995c7cf9887f0bdba8a36dd78e8b73f149b17d2d9ad7cd09d2dc6264736f6c634300081a0033';
/**
 * Simulates execution of a batch of calls.
 *
 * @param client - Client to use
 * @param parameters - {@link SimulateCallsParameters}
 * @returns Results. {@link SimulateCallsReturnType}
 *
 * @example
 * ```ts
 * import { createPublicClient, http, parseEther } from 'viem'
 * import { mainnet } from 'viem/chains'
 * import { simulateCalls } from 'viem/actions'
 *
 * const client = createPublicClient({
 *   chain: mainnet,
 *   transport: http(),
 * })
 *
 * const result = await simulateCalls(client, {
 *   account: '0x5a0b54d5dc17e482fe8b0bdca5320161b95fb929',
 *   calls: [{
 *     {
 *       data: '0xdeadbeef',
 *       to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
 *     },
 *     {
 *       to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
 *       value: parseEther('1'),
 *     },
 *   ]
 * })
 * ```
 */
export async function simulateCalls(client, parameters) {
    const { blockNumber, blockTag, calls, stateOverrides, traceAssetChanges, traceTransfers, validation, } = parameters;
    const account = parameters.account
        ? parseAccount(parameters.account)
        : undefined;
    if (traceAssetChanges && !account)
        throw new BaseError('`account` is required when `traceAssetChanges` is true');
    // Derive bytecode to extract ETH balance via a contract call.
    const getBalanceData = account
        ? AbiConstructor.encode(AbiConstructor.from('constructor(bytes, bytes)'), {
            bytecode: deploylessCallViaBytecodeBytecode,
            args: [
                getBalanceCode,
                AbiFunction.encodeData(AbiFunction.from('function getBalance(address)'), [account.address]),
            ],
        })
        : undefined;
    // Fetch ERC20/721 addresses that were "touched" from the calls.
    const assetAddresses = traceAssetChanges
        ? await Promise.all(parameters.calls.map(async (call) => {
            if (!call.data && !call.abi)
                return;
            const { accessList } = await createAccessList(client, {
                account: account.address,
                ...call,
                data: call.abi ? encodeFunctionData(call) : call.data,
            });
            return accessList.map(({ address, storageKeys }) => storageKeys.length > 0 ? address : null);
        })).then((x) => x.flat().filter(Boolean))
        : [];
    const blocks = await simulateBlocks(client, {
        blockNumber,
        blockTag: blockTag,
        blocks: [
            ...(traceAssetChanges
                ? [
                    // ETH pre balances
                    {
                        calls: [{ data: getBalanceData }],
                        stateOverrides,
                    },
                    // Asset pre balances
                    {
                        calls: assetAddresses.map((address, i) => ({
                            abi: [
                                AbiFunction.from('function balanceOf(address) returns (uint256)'),
                            ],
                            functionName: 'balanceOf',
                            args: [account.address],
                            to: address,
                            from: zeroAddress,
                            nonce: i,
                        })),
                        stateOverrides: [
                            {
                                address: zeroAddress,
                                nonce: 0,
                            },
                        ],
                    },
                ]
                : []),
            {
                calls: [...calls, { to: zeroAddress }].map((call) => ({
                    ...call,
                    from: account?.address,
                })),
                stateOverrides,
            },
            ...(traceAssetChanges
                ? [
                    // ETH post balances
                    {
                        calls: [{ data: getBalanceData }],
                    },
                    // Asset post balances
                    {
                        calls: assetAddresses.map((address, i) => ({
                            abi: [
                                AbiFunction.from('function balanceOf(address) returns (uint256)'),
                            ],
                            functionName: 'balanceOf',
                            args: [account.address],
                            to: address,
                            from: zeroAddress,
                            nonce: i,
                        })),
                        stateOverrides: [
                            {
                                address: zeroAddress,
                                nonce: 0,
                            },
                        ],
                    },
                    // Decimals
                    {
                        calls: assetAddresses.map((address, i) => ({
                            to: address,
                            abi: [
                                AbiFunction.from('function decimals() returns (uint256)'),
                            ],
                            functionName: 'decimals',
                            from: zeroAddress,
                            nonce: i,
                        })),
                        stateOverrides: [
                            {
                                address: zeroAddress,
                                nonce: 0,
                            },
                        ],
                    },
                    // Token URI
                    {
                        calls: assetAddresses.map((address, i) => ({
                            to: address,
                            abi: [
                                AbiFunction.from('function tokenURI(uint256) returns (string)'),
                            ],
                            functionName: 'tokenURI',
                            args: [0n],
                            from: zeroAddress,
                            nonce: i,
                        })),
                        stateOverrides: [
                            {
                                address: zeroAddress,
                                nonce: 0,
                            },
                        ],
                    },
                    // Symbols
                    {
                        calls: assetAddresses.map((address, i) => ({
                            to: address,
                            abi: [AbiFunction.from('function symbol() returns (string)')],
                            functionName: 'symbol',
                            from: zeroAddress,
                            nonce: i,
                        })),
                        stateOverrides: [
                            {
                                address: zeroAddress,
                                nonce: 0,
                            },
                        ],
                    },
                ]
                : []),
        ],
        traceTransfers,
        validation,
    });
    const block_results = traceAssetChanges ? blocks[2] : blocks[0];
    const [block_ethPre, block_assetsPre, , block_ethPost, block_assetsPost, block_decimals, block_tokenURI, block_symbols,] = traceAssetChanges ? blocks : [];
    // Extract call results from the simulation.
    const { calls: block_calls, ...block } = block_results;
    const results = block_calls.slice(0, -1) ?? [];
    // Extract pre-execution ETH and asset balances.
    const ethPre = block_ethPre?.calls ?? [];
    const assetsPre = block_assetsPre?.calls ?? [];
    const balancesPre = [...ethPre, ...assetsPre].map((call) => call.status === 'success' ? hexToBigInt(call.data) : null);
    // Extract post-execution ETH and asset balances.
    const ethPost = block_ethPost?.calls ?? [];
    const assetsPost = block_assetsPost?.calls ?? [];
    const balancesPost = [...ethPost, ...assetsPost].map((call) => call.status === 'success' ? hexToBigInt(call.data) : null);
    // Extract asset symbols & decimals.
    const decimals = (block_decimals?.calls ?? []).map((x) => x.status === 'success' ? x.result : null);
    const symbols = (block_symbols?.calls ?? []).map((x) => x.status === 'success' ? x.result : null);
    const tokenURI = (block_tokenURI?.calls ?? []).map((x) => x.status === 'success' ? x.result : null);
    const changes = [];
    for (const [i, balancePost] of balancesPost.entries()) {
        const balancePre = balancesPre[i];
        if (typeof balancePost !== 'bigint')
            continue;
        if (typeof balancePre !== 'bigint')
            continue;
        const decimals_ = decimals[i - 1];
        const symbol_ = symbols[i - 1];
        const tokenURI_ = tokenURI[i - 1];
        const token = (() => {
            if (i === 0)
                return {
                    address: ethAddress,
                    decimals: 18,
                    symbol: 'ETH',
                };
            return {
                address: assetAddresses[i - 1],
                decimals: tokenURI_ || decimals_ ? Number(decimals_ ?? 1) : undefined,
                symbol: symbol_ ?? undefined,
            };
        })();
        if (changes.some((change) => change.token.address === token.address))
            continue;
        changes.push({
            token,
            value: {
                pre: balancePre,
                post: balancePost,
                diff: balancePost - balancePre,
            },
        });
    }
    return {
        assetChanges: changes,
        block,
        results,
    };
}
//# sourceMappingURL=simulateCalls.js.map