UNPKG

viem

Version:

TypeScript Interface for Ethereum

2,196 lines • 75.4 kB
import { Hex } from 'ox';
import { EarnShares, TokenId } from 'ox/tempo';
import { parseAccount } from '../../accounts/utils/parseAccount.js';
import { estimateContractGas } from '../../actions/public/estimateContractGas.js';
import { getBlockNumber } from '../../actions/public/getBlockNumber.js';
import { getLogs } from '../../actions/public/getLogs.js';
import { multicall } from '../../actions/public/multicall.js';
import { readContract, } from '../../actions/public/readContract.js';
import { simulateContract, } from '../../actions/public/simulateContract.js';
import * as internal_Token from '../../actions/token/internal.js';
import { sendTransaction, } from '../../actions/wallet/sendTransaction.js';
import { sendTransactionSync } from '../../actions/wallet/sendTransactionSync.js';
import { writeContractSync } from '../../actions/wallet/writeContractSync.js';
import { AccountNotFoundError } from '../../errors/account.js';
import { encodeAbiParameters } from '../../utils/abi/encodeAbiParameters.js';
import { getAbiItem } from '../../utils/abi/getAbiItem.js';
import { parseEventLogs } from '../../utils/abi/parseEventLogs.js';
import { getAddress } from '../../utils/address/getAddress.js';
import { isAddressEqual } from '../../utils/address/isAddressEqual.js';
import { observe } from '../../utils/observe.js';
import { poll } from '../../utils/poll.js';
import { withResolvers } from '../../utils/promise/withResolvers.js';
import { stringify } from '../../utils/stringify.js';
import * as Abis from '../Abis.js';
import * as Addresses from '../Addresses.js';
import { GetVaultEngineChangedError, WaitForPrivateDepositTimeoutError, WaitForPrivateRedeemTimeoutError, } from '../errors.js';
import { defineCall, pickWriteParameters, resolveCallParameters, resolveTokenWithDecimals, } from '../internal/utils.js';
import * as policyActions from './policy.js';
import * as tokenActions from './token.js';
import * as zoneActions from './zone.js';
// biome-ignore lint/performance/noBarrelFile: namespace module
export * from './earn/deployment.js';
/** TIP-403 policy ID that allows every sender, recipient, and mint recipient. */
export const alwaysAllowPolicyId = 1n;
/**
 * Creates and attaches an admission-only TIP-403 policy to an Earn share
 * token. Existing holders remain able to send shares while recipients and mint
 * recipients must belong to the same whitelist.
 *
 * The action submits three or four sequential transactions and is not atomic.
 * Use {@link validateExitSafePolicy} to verify the final state.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const account = privateKeyToAccount('0x...')
 * const client = createClient({
 *   account,
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const { policy, receipts } =
 *   await Actions.earn.configureExitSafePolicy(client, {
 *     accessAdministrator: '0x...',
 *     initialMembers: ['0x...', '0x...'],
 *     shareToken: '0x...',
 *   })
 * ```
 *
 * @param client - Client authorized to change the Earn share token policy.
 * @param parameters - Share token, administrator, and initial members.
 * @returns The configured policy IDs and transaction receipts.
 */
export async function configureExitSafePolicy(client, parameters) {
    const account_ = parameters.account ?? client.account;
    if (!account_)
        throw new AccountNotFoundError();
    const account = parseAccount(account_);
    const initialMembers = [
        ...new Set(parameters.initialMembers.map((member) => getAddress(member))),
    ];
    if (initialMembers.length === 0)
        throw new Error('At least one initial policy member is required.');
    const eligibility = await policyActions.createSync(client, {
        account,
        addresses: initialMembers,
        chain: client.chain,
        type: 'whitelist',
    });
    if (eligibility.receipt.status === 'pending')
        return { receipt: eligibility.receipt };
    const compoundPolicy = await writeContractSync(client, {
        account,
        abi: Abis.tip403Registry,
        address: Addresses.tip403Registry,
        args: [alwaysAllowPolicyId, eligibility.policyId, eligibility.policyId],
        chain: client.chain,
        functionName: 'createCompoundPolicy',
        throwOnReceiptRevert: true,
    });
    if (compoundPolicy.status === 'pending')
        return { receipt: compoundPolicy };
    const [compoundEvent] = parseEventLogs({
        abi: Abis.tip403Registry,
        eventName: 'CompoundPolicyCreated',
        logs: compoundPolicy.logs,
        strict: true,
    });
    if (!compoundEvent)
        throw new Error('`CompoundPolicyCreated` event not found.');
    const tokenPolicy = await tokenActions.changeTransferPolicySync(client, {
        account,
        chain: client.chain,
        policyId: compoundEvent.args.policyId,
        token: parameters.shareToken,
    });
    if (tokenPolicy.receipt.status === 'pending')
        return { receipt: tokenPolicy.receipt };
    const policyAdmin = isAddressEqual(parameters.accessAdministrator, account.address)
        ? undefined
        : await policyActions.setAdminSync(client, {
            account,
            admin: parameters.accessAdministrator,
            chain: client.chain,
            policyId: eligibility.policyId,
        });
    if (policyAdmin &&
        policyAdmin.receipt.status === 'pending')
        return { receipt: policyAdmin.receipt };
    return {
        policy: {
            transferPolicyId: compoundEvent.args.policyId,
            senderPolicyId: alwaysAllowPolicyId,
            recipientPolicyId: eligibility.policyId,
            mintRecipientPolicyId: eligibility.policyId,
        },
        receipts: {
            eligibilityPolicy: eligibility.receipt,
            compoundPolicy,
            tokenPolicy: tokenPolicy.receipt,
            policyAdmin: policyAdmin?.receipt,
        },
    };
}
/**
 * Verifies that an Earn share token uses the expected exit-safe TIP-403
 * policy and that every required member can receive transfers and mints.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * await Actions.earn.validateExitSafePolicy(client, {
 *   accessAdministrator: '0x...',
 *   policy: {
 *     transferPolicyId: 3n,
 *     senderPolicyId: 1n,
 *     recipientPolicyId: 2n,
 *     mintRecipientPolicyId: 2n,
 *   },
 *   requiredMembers: ['0x...', '0x...'],
 *   shareToken: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Expected policy, administrator, and required members.
 * @returns Nothing when the policy is valid.
 */
export async function validateExitSafePolicy(client, parameters) {
    const { accessAdministrator, policy, requiredMembers, shareToken, ...rest } = parameters;
    const [tokenPolicyId, compound, simplePolicy, memberResults] = await Promise.all([
        readContract(client, {
            ...rest,
            abi: Abis.tip20,
            address: shareToken,
            functionName: 'transferPolicyId',
        }),
        readContract(client, {
            ...rest,
            abi: Abis.tip403Registry,
            address: Addresses.tip403Registry,
            args: [policy.transferPolicyId],
            functionName: 'compoundPolicyData',
        }),
        readContract(client, {
            ...rest,
            abi: Abis.tip403Registry,
            address: Addresses.tip403Registry,
            args: [policy.recipientPolicyId],
            functionName: 'policyData',
        }),
        Promise.all(requiredMembers.map(async (member) => {
            const [recipient, mintRecipient] = await Promise.all([
                readContract(client, {
                    ...rest,
                    abi: Abis.tip403Registry,
                    address: Addresses.tip403Registry,
                    args: [policy.transferPolicyId, member],
                    functionName: 'isAuthorizedRecipient',
                }),
                readContract(client, {
                    ...rest,
                    abi: Abis.tip403Registry,
                    address: Addresses.tip403Registry,
                    args: [policy.transferPolicyId, member],
                    functionName: 'isAuthorizedMintRecipient',
                }),
            ]);
            return { member, mintRecipient, recipient };
        })),
    ]);
    if (tokenPolicyId !== policy.transferPolicyId)
        throw new Error('Earn share token transfer policy mismatch.');
    if (compound[0] !== policy.senderPolicyId ||
        compound[1] !== policy.recipientPolicyId ||
        compound[2] !== policy.mintRecipientPolicyId)
        throw new Error('TIP-403 compound policy components mismatch.');
    if (policy.senderPolicyId !== alwaysAllowPolicyId)
        throw new Error('TIP-403 sender policy is not always allow.');
    if (policy.recipientPolicyId !== policy.mintRecipientPolicyId)
        throw new Error('TIP-403 recipient and mint-recipient policies must match.');
    if (simplePolicy[0] !== 0)
        throw new Error('TIP-403 eligibility policy is not a whitelist.');
    if (!isAddressEqual(simplePolicy[1], accessAdministrator))
        throw new Error('TIP-403 access administrator mismatch.');
    const unauthorized = memberResults.find((result) => !result.recipient || !result.mintRecipient);
    if (unauthorized)
        throw new Error(`Required TIP-403 member is unauthorized: ${unauthorized.member}`);
}
/**
 * Deposits assets into a vault and mints Earn shares to `recipient`. The
 * transaction includes the required asset approval.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   account: privateKeyToAccount('0x...'),
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const hash = await Actions.earn.deposit(client, {
 *   assetAmount: 100_000_000n,
 *   shareAmount: 99_900_000n,
 *   slippageBps: 50,
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The transaction hash.
 */
export async function deposit(client, parameters) {
    return deposit.inner(sendTransaction, client, parameters);
}
(function (deposit) {
    /** @internal Shared dispatch; reads the asset for the approval. */
    async function inner(action, client, parameters) {
        const [args, assetToken] = await Promise.all([
            toDepositArgs(client, parameters),
            readContract(client, {
                abi: Abis.earnVault,
                address: parameters.vault,
                functionName: 'asset',
            }),
        ]);
        return (await action(client, {
            ...parameters,
            calls: deposit.calls({ ...args, assetToken }),
        }));
    }
    deposit.inner = inner;
    /**
     * Defines a deposit call without an approval. Provide token decimals for
     * formatted inputs and an explicit output bound because this builder performs no reads.
     *
     * @param parameters - Client (optional), followed by the call arguments.
     * @returns The call.
     */
    function call(...parameters) {
        const [, args] = resolveCallParameters(parameters);
        const { recipient, vault } = args;
        const shareAmountMin = (() => {
            if (args.shareAmountMin !== undefined)
                return args.shareAmountMin;
            return EarnShares.minimumOutput(args.shareAmount, args.slippageBps);
        })();
        return defineCall({
            address: vault,
            abi: Abis.earnVault,
            functionName: 'deposit',
            args: [
                internal_Token.toBaseUnits(args.assetAmount, undefined),
                recipient,
                shareAmountMin,
            ],
        });
    }
    deposit.call = call;
    /**
     * Defines the asset approval and deposit calls for atomic execution. Pass
     * `assetToken` and token decimals explicitly because this builder performs no reads.
     *
     * @param args - Arguments.
     * @returns The calls.
     */
    function calls(args) {
        const { assetToken, vault } = args;
        const assetAmount = internal_Token.toBaseUnits(args.assetAmount, undefined);
        return [
            defineCall({
                address: TokenId.toAddress(assetToken),
                abi: Abis.tip20,
                functionName: 'approve',
                args: [vault, assetAmount],
            }),
            deposit.call({ ...args, assetAmount }),
        ];
    }
    deposit.calls = calls;
    /**
     * Extracts a `Deposited` event from the vault's logs.
     *
     * @param logs - Logs.
     * @param parameters - Parameters.
     * @returns The `Deposited` event.
     */
    function extractEvent(logs, parameters) {
        const { vault } = parameters;
        // Earn contracts are user-deployed: several adapters can emit the same
        // signature in one receipt, so filter by emitting address before decode.
        const [log] = parseEventLogs({
            abi: Abis.earnVault,
            eventName: 'Deposited',
            logs: logs.filter((log) => isAddressEqual(log.address, vault)),
        });
        if (!log)
            throw new Error('`Deposited` event not found.');
        return log;
    }
    deposit.extractEvent = extractEvent;
    /**
     * Estimates gas for a deposit, assuming the vault has enough asset allowance.
     *
     * @param client - Client.
     * @param parameters - Parameters.
     * @returns The gas estimate.
     */
    async function estimateGas(client, parameters) {
        return estimateContractGas(client, {
            ...pickWriteParameters(parameters),
            ...deposit.call(await toDepositArgs(client, parameters)),
        });
    }
    deposit.estimateGas = estimateGas;
    /**
     * Simulates a deposit, assuming the vault has enough asset allowance.
     *
     * @param client - Client.
     * @param parameters - Parameters.
     * @returns The simulation result and write request.
     */
    async function simulate(client, parameters) {
        return simulateContract(client, {
            ...pickWriteParameters(parameters),
            ...deposit.call(await toDepositArgs(client, parameters)),
        });
    }
    deposit.simulate = simulate;
})(deposit || (deposit = {}));
/**
 * Deposits assets and returns the confirmed receipt and event data.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions, EarnShares } from 'viem/tempo'
 *
 * const client = createClient({
 *   account: privateKeyToAccount('0x...'),
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const { shareAmount } = await Actions.earn.depositSync(client, {
 *   assetAmount: 100_000_000n,
 *   shareAmountMin: EarnShares.minimumOutput(99_900_000n, 50),
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The transaction receipt and event data.
 */
export async function depositSync(client, parameters) {
    const { throwOnReceiptRevert = true, vault } = parameters;
    const receipt = await deposit.inner(sendTransactionSync, client, {
        ...parameters,
        throwOnReceiptRevert,
    });
    if (receipt.status === 'pending')
        return { receipt };
    const { args } = deposit.extractEvent(receipt.logs, { vault });
    return {
        assetAmount: args.assets,
        caller: args.caller,
        receipt,
        recipient: args.receiver,
        shareAmount: args.earnShares,
    };
}
/**
 * Deposits venue shares into a vault and mints Earn shares to `recipient`.
 * The transaction includes the required venue share approval.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   account: privateKeyToAccount('0x...'),
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const hash = await Actions.earn.depositShares(client, {
 *   earnShareAmount: 499_000_000n,
 *   slippageBps: 30,
 *   vault: '0x...',
 *   venueShareAmount: 500_000_000n,
 *   venueShareToken: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The transaction hash.
 */
export async function depositShares(client, parameters) {
    return depositShares.inner(sendTransaction, client, parameters);
}
(function (depositShares) {
    /** @internal Shared dispatch; reads the engine for the approval. */
    async function inner(action, client, parameters) {
        const engine = await readContract(client, {
            abi: Abis.earnVault,
            address: parameters.vault,
            functionName: 'engine',
        });
        return (await action(client, {
            ...parameters,
            calls: depositShares.calls({
                ...toDepositSharesArgs(client, parameters),
                engine,
                venueShareToken: parameters.venueShareToken,
            }),
        }));
    }
    depositShares.inner = inner;
    /**
     * Defines a venue share deposit call without an approval. Provide an
     * explicit output bound because this builder performs no reads.
     *
     * @param parameters - Client (optional), followed by the call arguments.
     * @returns The call.
     */
    function call(...parameters) {
        const [, args] = resolveCallParameters(parameters);
        const { recipient, vault, venueShareAmount } = args;
        const earnShareAmountMin = (() => {
            if (args.earnShareAmountMin !== undefined)
                return args.earnShareAmountMin;
            return EarnShares.minimumOutput(args.earnShareAmount, args.slippageBps);
        })();
        return defineCall({
            address: vault,
            abi: Abis.earnVault,
            functionName: 'depositVenueShares',
            args: [venueShareAmount, recipient, earnShareAmountMin],
        });
    }
    depositShares.call = call;
    /**
     * Defines the venue share approval and deposit calls for atomic execution.
     * Pass the vault's current `engine` and `venueShareToken` explicitly.
     *
     * @param args - Arguments.
     * @returns The calls.
     */
    function calls(args) {
        const { engine, venueShareAmount, venueShareToken } = args;
        return [
            defineCall({
                address: venueShareToken,
                abi: Abis.tip20,
                functionName: 'approve',
                args: [engine, venueShareAmount],
            }),
            depositShares.call(args),
        ];
    }
    depositShares.calls = calls;
    /**
     * Extracts a `VenueSharesDeposited` event from the vault's logs.
     *
     * @param logs - Logs.
     * @param parameters - Parameters.
     * @returns The `VenueSharesDeposited` event.
     */
    function extractEvent(logs, parameters) {
        const { vault } = parameters;
        // Earn contracts are user-deployed: several adapters can emit the same
        // signature in one receipt, so filter by emitting address before decode.
        const [log] = parseEventLogs({
            abi: Abis.earnVault,
            eventName: 'VenueSharesDeposited',
            logs: logs.filter((log) => isAddressEqual(log.address, vault)),
        });
        if (!log)
            throw new Error('`VenueSharesDeposited` event not found.');
        return log;
    }
    depositShares.extractEvent = extractEvent;
    /**
     * Estimates gas for a venue share deposit, assuming enough allowance.
     *
     * @param client - Client.
     * @param parameters - Parameters.
     * @returns The gas estimate.
     */
    async function estimateGas(client, parameters) {
        return estimateContractGas(client, {
            ...pickWriteParameters(parameters),
            ...depositShares.call(toDepositSharesArgs(client, parameters)),
        });
    }
    depositShares.estimateGas = estimateGas;
    /**
     * Simulates a venue share deposit, assuming enough allowance.
     *
     * @param client - Client.
     * @param parameters - Parameters.
     * @returns The simulation result and write request.
     */
    async function simulate(client, parameters) {
        return simulateContract(client, {
            ...pickWriteParameters(parameters),
            ...depositShares.call(toDepositSharesArgs(client, parameters)),
        });
    }
    depositShares.simulate = simulate;
})(depositShares || (depositShares = {}));
/**
 * Deposits venue shares and returns the confirmed receipt and event data.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions, EarnShares } from 'viem/tempo'
 *
 * const client = createClient({
 *   account: privateKeyToAccount('0x...'),
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const { earnShareAmount } = await Actions.earn.depositSharesSync(client, {
 *   earnShareAmount: 499_000_000n,
 *   slippageBps: 30,
 *   vault: '0x...',
 *   venueShareAmount: 500_000_000n,
 *   venueShareToken: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The transaction receipt and event data.
 */
export async function depositSharesSync(client, parameters) {
    const { throwOnReceiptRevert = true, vault } = parameters;
    const receipt = await depositShares.inner(sendTransactionSync, client, {
        ...parameters,
        throwOnReceiptRevert,
    });
    if (receipt.status === 'pending')
        return { receipt };
    const { args } = depositShares.extractEvent(receipt.logs, { vault });
    return {
        caller: args.caller,
        earnShareAmount: args.earnShares,
        receipt,
        receivedVenueShareAmount: args.receivedEngineShares,
        recipient: args.receiver,
        venueShareAmount: args.requestedVenueShares,
    };
}
/**
 * Withdraws assets from a Zone and deposits them into a vault on the parent
 * chain. Use {@link privateDeposit.prepare} to build the encrypted callback.
 *
 * @example
 * ```ts
 * const prepared = await Actions.earn.privateDeposit.prepare(parentClient, {
 *   assetAmount: 100_000_000n,
 *   assetToken: '0x...',
 *   gateway: '0x...',
 *   recipient: '0x...',
 *   shareAmountMin: 99_500_000n,
 *   tempoRefundRecipient: '0x...',
 *   vault: '0x...',
 *   vaultAssetAmountMin: 99_000_000n,
 *   zoneId: 7,
 * })
 * const hash = await Actions.earn.privateDeposit(zoneClient, prepared)
 * ```
 *
 * @param client - Zone client.
 * @param parameters - Prepared deposit and transaction parameters.
 * @returns The transaction hash.
 */
export async function privateDeposit(client, parameters) {
    await assertPreparedZoneRequestChain(client, parameters);
    return zoneActions.requestWithdrawal(client, parameters);
}
(function (privateDeposit) {
    /**
     * Builds an encrypted Zone withdrawal that deposits into the selected vault
     * and returns the resulting shares to the Zone.
     *
     * @param client - Parent-chain client.
     * @param parameters - Deposit intent and recovery parameters.
     * @returns The prepared withdrawal and correlation data.
     */
    async function prepare(client, parameters) {
        const chainId = client.chain?.id;
        if (!chainId)
            throw new Error('`chain` is required.');
        const { actionId = Hex.random(32), assetAmount, callbackGas = zoneGatewayCallbackGas, tempoRefundRecipient, fallbackRecipient = tempoRefundRecipient, gateway, portalAddress: portalAddress_, recipient, returnMemo, vault, withdrawalMemo, zoneId, } = parameters;
        const portalAddress = portalAddress_ ?? Addresses.zonePortal(zoneId);
        const readParameters = pickReadParameters(parameters);
        const [fromBlock, config] = await Promise.all([
            getBlockNumber(client, { cacheTime: 0 }),
            getZoneGatewayConfig(client, {
                ...readParameters,
                flow: 0,
                gateway,
                vault,
                zoneId,
                zonePortal: portalAddress,
            }),
        ]);
        const assetToken = parameters.assetToken ?? config.privateAsset;
        if (!isAddressEqual(assetToken, config.privateAsset))
            throw new Error('`assetToken` must match the Zone gateway private asset.');
        const { encrypted, keyIndex } = await zoneActions.encryptedDeposit.prepareRecipient(client, {
            ...readParameters,
            memo: returnMemo,
            portalAddress: config.zonePortal,
            recipient,
            sender: gateway,
            zoneId: config.zoneId,
        });
        const shareAmountMin = resolveMinimumShareAmount(parameters);
        const data = encodeAbiParameters(Abis.earnRouterCallbackData, [
            {
                actionId,
                flow: 0,
                minEarnShares: shareAmountMin,
                minOutputAmount: 0n,
                minVaultAssets: parameters.vaultAssetAmountMin ?? assetAmount,
                zoneReturn: {
                    encrypted,
                    keyIndex,
                    refundRecipient: tempoRefundRecipient,
                },
            },
        ]);
        return {
            actionId,
            amount: assetAmount,
            callbackGas,
            chainId,
            data,
            fallbackRecipient,
            fromBlock,
            memo: withdrawalMemo,
            to: gateway,
            token: assetToken,
            zoneId: config.zoneId,
        };
    }
    privateDeposit.prepare = prepare;
    /**
     * Defines the approval and Zone withdrawal calls for a prepared deposit.
     *
     * @param args - Prepared deposit arguments.
     * @returns The Zone withdrawal calls.
     */
    function calls(args) {
        return zoneActions.requestWithdrawal.calls(args);
    }
    privateDeposit.calls = calls;
})(privateDeposit || (privateDeposit = {}));
/**
 * Requests a private Zone deposit and waits for the Zone transaction receipt.
 * The receipt confirms withdrawal acceptance, not the parent-chain deposit.
 *
 * @param client - Zone client.
 * @param parameters - Prepared deposit and transaction parameters.
 * @returns The Zone transaction receipt and parent-chain withdrawal sender tag.
 */
export async function privateDepositSync(client, parameters) {
    await assertPreparedZoneRequestChain(client, parameters);
    return zoneActions.requestWithdrawalSync(client, parameters);
}
/**
 * Waits for a Zone gateway deposit to complete on the parent chain.
 *
 * @example
 * ```ts
 * const result = await Actions.earn.waitForPrivateDeposit(parentClient, {
 *   actionId: prepared.actionId,
 *   fromBlock: prepared.fromBlock,
 *   gateway: '0x...',
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Parent-chain client.
 * @param parameters - Prepared action correlation and polling parameters.
 * @returns The completed gateway deposit.
 */
export async function waitForPrivateDeposit(client, parameters) {
    const { actionId, fromBlock, gateway, pollingInterval = client.pollingInterval, timeout = 60_000, vault, } = parameters;
    const event = getAbiItem({
        abi: Abis.earnRouter,
        name: 'EarnDeposit',
    });
    const observerId = stringify([
        'waitForPrivateDeposit',
        client.uid,
        gateway,
        vault,
        actionId,
        fromBlock,
    ]);
    const { promise, reject, resolve } = withResolvers();
    let timer;
    let unobserve;
    const cleanup = () => {
        clearTimeout(timer);
        unobserve();
    };
    const resolve_ = (result) => {
        cleanup();
        resolve(result);
    };
    const reject_ = (error) => {
        cleanup();
        reject(error);
    };
    unobserve = observe(observerId, { reject: reject_, resolve: resolve_ }, (emit) => {
        const unpoll = poll(async () => {
            try {
                const [log] = await getLogs(client, {
                    address: gateway,
                    args: { actionId, earnVault: vault },
                    event,
                    fromBlock,
                    strict: true,
                    toBlock: 'latest',
                });
                if (!log)
                    return;
                unpoll();
                emit.resolve({
                    actionId: log.args.actionId,
                    inputAmount: log.args.inputAmount,
                    inputToken: log.args.inputToken,
                    shares: log.args.earnShares,
                    tempoBlockNumber: log.blockNumber,
                    vaultAssets: log.args.vaultAssets,
                    zoneDepositHash: log.args.zoneDepositHash,
                });
            }
            catch (error) {
                unpoll();
                emit.reject(error);
            }
        }, { emitOnBegin: true, interval: pollingInterval });
        return unpoll;
    });
    timer = timeout
        ? setTimeout(() => {
            reject_(new WaitForPrivateDepositTimeoutError({ actionId, gateway }));
        }, timeout)
        : undefined;
    return await promise;
}
/**
 * Gets the vault's active fee configuration, pending fees, and fee baselines.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const feeState = await Actions.earn.getFeeState(client, {
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The active fee configuration, pending fees, and baselines.
 */
export async function getFeeState(client, parameters) {
    const { recipient, vault, ...rest } = parameters;
    const fees = await readContract(client, {
        ...rest,
        abi: Abis.earnVault,
        address: vault,
        functionName: 'earnFees',
    });
    const contracts = [
        defineCall({
            address: fees,
            abi: Abis.earnFees,
            functionName: 'currentFeeConfigId',
        }),
        defineCall({
            address: fees,
            abi: Abis.earnFees,
            functionName: 'feesActive',
        }),
        defineCall({
            address: fees,
            abi: Abis.earnFees,
            functionName: 'highWaterMark',
        }),
        defineCall({
            address: fees,
            abi: Abis.earnFees,
            functionName: 'previewAccruedFees',
        }),
        defineCall({
            address: fees,
            abi: Abis.earnFees,
            functionName: 'targetBase',
        }),
    ];
    // Stored configs are immutable per id, so a follow-up `feeConfig` read stays
    // consistent with the batched id.
    const feeConfig = async (configId) => toFeeConfig(await readContract(client, {
        ...rest,
        abi: Abis.earnFees,
        address: fees,
        functionName: 'feeConfig',
        args: [configId],
    }));
    if (recipient !== undefined) {
        const [configId, feesActive, highWaterMark, preview, targetBase, shares] = await multicall(client, {
            ...rest,
            allowFailure: false,
            contracts: [
                ...contracts,
                defineCall({
                    address: fees,
                    abi: Abis.earnFees,
                    functionName: 'claimableEarnShares',
                    args: [recipient],
                }),
            ],
            deployless: true,
        });
        return {
            claimableShares: shares,
            config: await feeConfig(configId),
            configId,
            feesActive,
            highWaterMark,
            preview: toFeePreview(preview),
            targetBase,
        };
    }
    const [configId, feesActive, highWaterMark, preview, targetBase] = await multicall(client, {
        ...rest,
        allowFailure: false,
        contracts,
        deployless: true,
    });
    return {
        config: await feeConfig(configId),
        configId,
        feesActive,
        highWaterMark,
        preview: toFeePreview(preview),
        targetBase,
    };
}
/**
 * Gets an account's asset and Earn share balances, allowances, and current
 * share value. The value includes fees.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const position = await Actions.earn.getPosition(client, {
 *   account: '0x...',
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The asset and Earn share balances, allowances, and value.
 */
export async function getPosition(client, parameters) {
    const { account: account_ = client.account, vault, ...rest } = parameters;
    if (!account_)
        throw new AccountNotFoundError();
    const account = parseAccount(account_).address;
    const [assetToken, shareToken] = await multicall(client, {
        ...rest,
        allowFailure: false,
        contracts: [
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'asset',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'earnShare',
            }),
        ],
        deployless: true,
    });
    const [assetAllowance, assetBalance, shareAllowance, shareBalance] = await multicall(client, {
        ...rest,
        allowFailure: false,
        contracts: [
            defineCall({
                address: assetToken,
                abi: Abis.tip20,
                functionName: 'allowance',
                args: [account, vault],
            }),
            defineCall({
                address: assetToken,
                abi: Abis.tip20,
                functionName: 'balanceOf',
                args: [account],
            }),
            defineCall({
                address: shareToken,
                abi: Abis.tip20,
                functionName: 'allowance',
                args: [account, vault],
            }),
            defineCall({
                address: shareToken,
                abi: Abis.tip20,
                functionName: 'balanceOf',
                args: [account],
            }),
        ],
        deployless: true,
    });
    const value = await readContract(client, {
        ...rest,
        abi: Abis.earnVault,
        address: vault,
        args: [shareBalance],
        functionName: 'previewRedeem',
    });
    return {
        assetAllowance,
        assetBalance,
        assetToken,
        shareAllowance,
        shareBalance,
        shareToken,
        value,
    };
}
/**
 * Gets the asset output for an exact Earn share input, including fees.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const assetAmount = await Actions.earn.getRedeemQuote(client, {
 *   shareAmount: 100_000_000n,
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The asset output, including fees.
 */
export async function getRedeemQuote(client, parameters) {
    const { shareAmount, vault, ...rest } = parameters;
    return readContract(client, {
        ...rest,
        ...getRedeemQuote.call({ shareAmount, vault }),
    });
}
(function (getRedeemQuote) {
    /**
     * Defines a call to the vault's `previewRedeem` function.
     *
     * Can be passed as a parameter to:
     * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
     * - [`multicall`](https://viem.sh/docs/contract/multicall): batch the call with other contract reads
     * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
     *
     * @example
     * ```ts
     * import { Actions } from 'viem/tempo'
     *
     * const call = Actions.earn.getRedeemQuote.call({
     *   shareAmount: 100_000_000n,
     *   vault: '0x...',
     * })
     * ```
     *
     * @param args - Arguments.
     * @returns The call.
     */
    function call(args) {
        const { shareAmount, vault } = args;
        return defineCall({
            address: vault,
            abi: Abis.earnVault,
            args: [shareAmount],
            functionName: 'previewRedeem',
        });
    }
    getRedeemQuote.call = call;
})(getRedeemQuote || (getRedeemQuote = {}));
/**
 * Gets the vault's addresses, configuration, accounting state, and supported
 * actions. Throws {@link GetVaultEngineChangedError} if its engine changes mid-read.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const vault = await Actions.earn.getVault(client, {
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The vault state and metadata.
 */
export async function getVault(client, parameters) {
    const { vault, ...rest } = parameters;
    const [engine, fees] = await Promise.all([
        readContract(client, {
            ...rest,
            abi: Abis.earnVault,
            address: vault,
            functionName: 'engine',
        }),
        readContract(client, {
            ...rest,
            abi: Abis.earnVault,
            address: vault,
            functionName: 'earnFees',
        }),
    ]);
    const [assetToken, engine_, shareToken, operator, emergencyGuardian, asyncJanitor, engineMigrationMode, depositsPaused, engineShares, shareSupply, isSynced, pendingRedeemCount, feesActive, totalAssets, name, symbol, asyncRedeem, exactWithdraw, inKindDeposit, syncRedeem,] = await multicall(client, {
        ...rest,
        allowFailure: false,
        contracts: getVault.calls({ engine, fees, vault }),
        deployless: true,
    });
    if (!isAddressEqual(engine, engine_))
        throw new GetVaultEngineChangedError({ vault });
    return {
        assetToken,
        asyncJanitor,
        capabilities: { asyncRedeem, exactWithdraw, inKindDeposit, syncRedeem },
        depositsPaused,
        emergencyGuardian,
        engine: { address: engine_, name, symbol, totalAssets },
        // `EngineMigrationMode`: 0 = UserOnly, 1 = OperatorEnabled.
        engineMigrationMode: engineMigrationMode === 0 ? 'userOnly' : 'operatorEnabled',
        engineShares,
        feesActive,
        isSynced,
        operator,
        pendingRedeemCount,
        shareSupply,
        shareToken,
    };
}
(function (getVault) {
    /**
     * Defines the reads used by {@link getVault}. Pass the current engine and
     * fee contract addresses.
     *
     * @param args - Arguments.
     * @returns The calls.
     */
    function calls(args) {
        const { engine, fees, vault } = args;
        return [
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'asset',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'engine',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'earnShare',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'operator',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'emergencyGuardian',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'asyncJanitor',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'engineMigrationMode',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'depositsPaused',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'engineShares',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'totalEarnShares',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'isAccountingAligned',
            }),
            defineCall({
                address: vault,
                abi: Abis.earnVault,
                functionName: 'openRedeemRequestCount',
            }),
            defineCall({
                address: fees,
                abi: Abis.earnFees,
                functionName: 'feesActive',
            }),
            defineCall({
                address: engine,
                abi: Abis.earnEngine,
                functionName: 'totalAssets',
            }),
            defineCall({
                address: engine,
                abi: Abis.earnEngine,
                functionName: 'name',
            }),
            defineCall({
                address: engine,
                abi: Abis.earnEngine,
                functionName: 'symbol',
            }),
            defineCall({
                address: engine,
                abi: Abis.earnEngine,
                functionName: 'supportsInterface',
                args: [interfaceIds.asyncRedeem],
            }),
            defineCall({
                address: engine,
                abi: Abis.earnEngine,
                functionName: 'supportsInterface',
                args: [interfaceIds.exactWithdraw],
            }),
            defineCall({
                address: engine,
                abi: Abis.earnEngine,
                functionName: 'supportsInterface',
                args: [interfaceIds.inKindDeposit],
            }),
            defineCall({
                address: engine,
                abi: Abis.earnEngine,
                functionName: 'supportsInterface',
                args: [interfaceIds.syncRedeem],
            }),
        ];
    }
    getVault.calls = calls;
})(getVault || (getVault = {}));
/**
 * Gets the Earn shares required for an exact asset output, including fees
 * and ceiling rounding.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const shareAmount = await Actions.earn.getWithdrawQuote(client, {
 *   assetAmount: 250_000_000n,
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The required Earn share input, ceiling-rounded.
 */
export async function getWithdrawQuote(client, parameters) {
    const { assetAmount, vault, ...rest } = parameters;
    return readContract(client, {
        ...rest,
        ...getWithdrawQuote.call({ assetAmount, vault }),
    });
}
(function (getWithdrawQuote) {
    /**
     * Defines a call to the vault's `previewWithdraw` function.
     *
     * Can be passed as a parameter to:
     * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
     * - [`multicall`](https://viem.sh/docs/contract/multicall): batch the call with other contract reads
     * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
     *
     * @example
     * ```ts
     * import { Actions } from 'viem/tempo'
     *
     * const call = Actions.earn.getWithdrawQuote.call({
     *   assetAmount: 250_000_000n,
     *   vault: '0x...',
     * })
     * ```
     *
     * @param args - Arguments.
     * @returns The call.
     */
    function call(args) {
        const { assetAmount, vault } = args;
        return defineCall({
            address: vault,
            abi: Abis.earnVault,
            args: [assetAmount],
            functionName: 'previewWithdraw',
        });
    }
    getWithdrawQuote.call = call;
})(getWithdrawQuote || (getWithdrawQuote = {}));
/**
 * Redeems Earn shares for assets sent to `recipient`. The transaction
 * includes the required Earn share approval.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   account: privateKeyToAccount('0x...'),
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const hash = await Actions.earn.redeem(client, {
 *   shareAmount: 100_000_000n,
 *   slippageBps: 50,
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The transaction hash.
 */
export async function redeem(client, parameters) {
    return redeem.inner(sendTransaction, client, parameters);
}
(function (redeem) {
    /** @internal Shared dispatch; reads the Earn share token for the approval. */
    async function inner(action, client, parameters) {
        const [args, shareToken] = await Promise.all([
            toRedeemArgs(client, parameters),
            readContract(client, {
                abi: Abis.earnVault,
                address: parameters.vault,
                functionName: 'earnShare',
            }),
        ]);
        return (await action(client, {
            ...parameters,
            calls: redeem.calls({ ...args, shareToken }),
        }));
    }
    redeem.inner = inner;
    /**
     * Defines a redeem call without an approval. Provide Earn share decimals
     * for formatted inputs and an explicit output bound because this builder performs no reads.
     *
     * @param parameters - Client (optional), followed by the call arguments.
     * @returns The call.
     */
    function call(...parameters) {
        const [, args] = resolveCallParameters(parameters);
        const { recipient, vault } = args;
        const assetAmountMin = (() => {
            if (args.assetAmountMin !== undefined)
                return args.assetAmountMin;
            return EarnShares.minimumOutput(args.assetAmount, args.slippageBps);
        })();
        return defineCall({
            address: vault,
            abi: Abis.earnVault,
            functionName: 'redeem',
            args: [
                internal_Token.toBaseUnits(args.shareAmount, undefined),
                recipient,
                assetAmountMin,
            ],
        });
    }
    redeem.call = call;
    /**
     * Defines the Earn share approval and redeem calls for atomic execution.
     * Pass `shareToken` explicitly because this builder performs no reads.
     *
     * @param args - Arguments.
     * @returns The calls.
     */
    function calls(args) {
        const { shareToken, vault } = args;
        const shareAmount = internal_Token.toBaseUnits(args.shareAmount, undefined);
        return [
            defineCall({
                address: shareToken,
                abi: Abis.tip20,
                functionName: 'approve',
                args: [vault, shareAmount],
            }),
            redeem.call({ ...args, shareAmount }),
        ];
    }
    redeem.calls = calls;
    /**
     * Extracts a `Redeemed` event from the vault's logs.
     *
     * @param logs - Logs.
     * @param parameters - Parameters.
     * @returns The `Redeemed` event.
     */
    function extractEvent(logs, parameters) {
        const { vault } = parameters;
        // Earn contracts are user-deployed: several adapters can emit the same
        // signature in one receipt, so filter by emitting address before decode.
        const [log] = parseEventLogs({
            abi: Abis.earnVault,
            eventName: 'Redeemed',
            logs: logs.filter((log) => isAddressEqual(log.address, vault)),
        });
        if (!log)
            throw new Error('`Redeemed` event not found.');
        return log;
    }
    redeem.extractEvent = extractEvent;
    /**
     * Estimates gas for a redemption, assuming enough Earn share allowance.
     *
     * @param client - Client.
     * @param parameters - Parameters.
     * @returns The gas estimate.
     */
    async function estimateGas(client, parameters) {
        return estimateContractGas(client, {
            ...pickWriteParameters(parameters),
            ...redeem.call(await toRedeemArgs(client, parameters)),
        });
    }
    redeem.estimateGas = estimateGas;
    /**
     * Simulates a redemption, assuming enough Earn share allowance.
     *
     * @param client - Client.
     * @param parameters - Parameters.
     * @returns The simulation result and write request.
     */
    async function simulate(client, parameters) {
        return simulateContract(client, {
            ...pickWriteParameters(parameters),
            ...redeem.call(await toRedeemArgs(client, parameters)),
        });
    }
    redeem.simulate = simulate;
})(redeem || (redeem = {}));
/**
 * Redeems Earn shares and returns the confirmed receipt and event data.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   account: privateKeyToAccount('0x...'),
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const { assetAmount } = await Actions.earn.redeemSync(client, {
 *   assetAmountMin: 99_500_000n,
 *   shareAmount: 100_000_000n,
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The transaction receipt and event data.
 */
export async function redeemSync(client, parameters) {
    const { throwOnReceiptRevert = true, vault } = parameters;
    const receipt = await redeem.inner(sendTransactionSync, client, {
        ...parameters,
        throwOnReceiptRevert,
    });
    if (receipt.status === 'pending')
        return { receipt };
    const { args } = redeem.extractEvent(receipt.logs, { vault });
    return {
        assetAmount: args.assets,
        caller: args.caller,
        receipt,
        recipient: args.receiver,
        shareAmount: args.earnShares,
    };
}
/**
 * Withdraws Earn shares from a Zone and redeems them on the parent chain. Use
 * {@link privateRedeem.prepare} to build the encrypted callback.
 *
 * @example
 * ```ts
 * const prepared = await Actions.earn.privateRedeem.prepare(parentClient, {
 *   gateway: '0x...',
 *   recipient: '0x...',
 *   shareAmount: 100_000_000n,
 *   slippageBps: 50,
 *   tempoRefundRecipient: '0x...',
 *   vault: '0x...',
 *   zoneId: 7,
 * })
 * const hash = await Actions.earn.privateRedeem(zoneClient, prepared)
 * ```
 *
 * @param client - Zone client.
 * @param parameters - Prepared redemption and transaction parameters.
 * @returns The transaction hash.
 */
export async function privateRedeem(client, parameters) {
    await assertPreparedZoneRequestChain(client, parameters);
    return zoneActions.requestWithdrawal(client, parameters);
}
(function (privateRedeem) {
    /**
     * Builds an encrypted Zone withdrawal that redeems Earn shares and returns
     * the resulting assets to the Zone.
     *
     * @param client - Parent-chain client.
     * @param parameters - Redemption intent and recovery parameters.
     * @returns The prepared withdrawal and correlation data.
     */
    async function prepare(client, parameters) {
        const chainId = client.chain?.id;
        if (!chainId)
            throw new Error('`chain` is required.');
        const { actionId = Hex.random(32), callbackGas = zoneGatewayCallbackGas, tempoRefundRecipient, fallbackRecipient = tempoRefundRecipient, gateway, portalAddress: portalAddress_, recipient, returnMemo, shareAmount, vault, withdrawalMemo, zoneId, } = parameters;
        const portalAddress = portalAddress_ ?? Addresses.zonePortal(zoneId);
        const readParameters = pickReadParameters(parameters);
        const [fromBlock, config] = await Promise.all([
            getBlockNumber(client, { cacheTime: 0 }),
            getZoneGatewayConfig(client, {
                ...readParameters,
                flow: 1,
                gateway,
                vault,
                zoneId,
                zonePortal: portalAddress,
            }),
        ]);
        const assetToken = parameters.assetToken ?? config.privateAsset;
        if (!isAddressEqual(assetToken, config.privateAsset))
            throw new Error('`assetToken` must match the Zone gateway private asset.');
        const [{ encrypted, keyIndex }, assetAmountMin] = await Promise.all([
            zoneActions.encryptedDeposit.prepareRecipient(client, {
                ...readParameters,
                memo: returnMemo,
                portalAddress: config.zonePortal,
                recipient,
                sender: gateway,
                zoneId: config.zoneId,
            }),
            (async () => {
                if (parameters.assetAmountMin !== undefined)
                    return EarnShares.minimumOutput(parameters.assetAmountMin, 0);
                if (parameters.assetAmount !== undefined)
                    return EarnShares.minimumOutput(parameters.assetAmount, parameters.slippageBps);
                const assetAmount = await getRedeemQuote(client, {
                    ...readParameters,
                    shareAmount,
                    vault: config.vault,
                });
                return EarnShares.minimumOutput(assetAmount, parameters.slippageBps);
            })(),
        ]);
        const data = encodeAbiParameters(Abis.earnRouterCallbackData, [
            {
                actionId,
                flow: 1,
                minEarnShares: 0n,
                minOutputAmount: assetAmountMin,
                minVaultAssets: assetAmountMin,
                zoneReturn: {
                    encrypted,
                    keyIndex,
                    refundRecipient: tempoRefundRecipient,
                },
            },
        ]);
        return {
            actionId,
            amount: shareAmount,
            callbackGas,
            chainId,
            data,
            fallbackRecipient,
            fromBlock,
            memo: withdrawalMemo,
            to: gateway,
            token: config.shareToken,
            zoneId: config.zoneId,
        };
    }
    privateRedeem.prepare = prepare;
    /**
     * Defines the approval and Zone withdrawal calls for a prepared redemption.
     *
     * @param args - Prepared redemption arguments.
     * @returns The Zone withdrawal calls.
     */
    function calls(args) {
        return zoneActions.requestWithdrawal.calls(args);
    }
    privateRedeem.calls = calls;
})(privateRedeem || (privateRedeem = {}));
/**
 * Requests a private Zone redemption and waits for the Zone transaction
 * receipt. The receipt confirms withdrawal acceptance, not redemption.
 *
 * @param client - Zone client.
 * @param parameters - Prepared redemption and transaction parameters.
 * @returns The Zone transaction receipt and parent-chain withdrawal sender tag.
 */
export async function privateRedeemSync(client, parameters) {
    await assertPreparedZoneRequestChain(client, parameters);
    return zoneActions.requestWithdrawalSync(client, parameters);
}
/**
 * Waits for a Zone gateway redemption to complete on the parent chain.
 *
 * @example
 * ```ts
 * const result = await Actions.earn.waitForPrivateRedeem(parentClient, {
 *   actionId: prepared.actionId,
 *   fromBlock: prepared.fromBlock,
 *   gateway: '0x...',
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Parent-chain client.
 * @param parameters - Prepared action correlation and polling parameters.
 * @returns The completed gateway redemption.
 */
export async function waitForPrivateRedeem(client, parameters) {
    const { actionId, fromBlock, gateway, pollingInterval = client.pollingInterval, timeout = 60_000, vault, } = parameters;
    const event = getAbiItem({
        abi: Abis.earnRouter,
        name: 'EarnRedeem',
    });
    const observerId = stringify([
        'waitForPrivateRedeem',
        client.uid,
        gateway,
        vault,
        actionId,
        fromBlock,
    ]);
    const { promise, reject, resolve } = withResolvers();
    let timer;
    let unobserve;
    const cleanup = () => {
        clearTimeout(timer);
        unobserve();
    };
    const resolve_ = (result) => {
        cleanup();
        resolve(result);
    };
    const reject_ = (error) => {
        cleanup();
        reject(error);
    };
    unobserve = observe(observerId, { reject: reject_, resolve: resolve_ }, (emit) => {
        const unpoll = poll(async () => {
            try {
                const [log] = await getLogs(client, {
                    address: gateway,
                    args: { actionId, earnVault: vault },
                    event,
                    fromBlock,
                    strict: true,
                    toBlock: 'latest',
                });
                if (!log)
                    return;
                unpoll();
                emit.resolve({
                    actionId: log.args.actionId,
                    outputAmount: log.args.outputAmount,
                    outputToken: log.args.outputToken,
                    shares: log.args.earnShares,
                    tempoBlockNumber: log.blockNumber,
                    vaultAssets: log.args.vaultAssets,
                    zoneDepositHash: log.args.zoneDepositHash,
                });
            }
            catch (error) {
                unpoll();
                emit.reject(error);
            }
        }, { emitOnBegin: true, interval: pollingInterval });
        return unpoll;
    });
    timer = timeout
        ? setTimeout(() => {
            reject_(new WaitForPrivateRedeemTimeoutError({ actionId, gateway }));
        }, timeout)
        : undefined;
    return await promise;
}
/**
 * Withdraws an exact asset amount to `recipient`, up to the specified Earn
 * share limit. The transaction includes the required Earn share approval;
 * use {@link redeem} for a full exit.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   account: privateKeyToAccount('0x...'),
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const hash = await Actions.earn.withdrawExact(client, {
 *   assetAmount: 40_000_000n,
 *   slippageBps: 50,
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The transaction hash.
 */
export async function withdrawExact(client, parameters) {
    return withdrawExact.inner(sendTransaction, client, parameters);
}
(function (withdrawExact) {
    /** @internal Shared dispatch; reads the Earn share token for the approval. */
    async function inner(action, client, parameters) {
        const [args, shareToken] = await Promise.all([
            toWithdrawExactArgs(client, parameters),
            readContract(client, {
                abi: Abis.earnVault,
                address: parameters.vault,
                functionName: 'earnShare',
            }),
        ]);
        return (await action(client, {
            ...parameters,
            calls: withdrawExact.calls({ ...args, shareToken }),
        }));
    }
    withdrawExact.inner = inner;
    /**
     * Defines an exact withdrawal call without an approval. Provide asset
     * decimals and an explicit input limit because this builder performs no reads.
     *
     * @param parameters - Client (optional), followed by the call arguments.
     * @returns The call.
     */
    function call(...parameters) {
        const [, args] = resolveCallParameters(parameters);
        const { recipient, vault } = args;
        const shareAmountMax = (() => {
            if (args.shareAmountMax !== undefined)
                return args.shareAmountMax;
            return maximumInput(args.shareAmount, args.slippageBps);
        })();
        return defineCall({
            address: vault,
            abi: Abis.earnVault,
            functionName: 'withdrawExact',
            args: [
                internal_Token.toBaseUnits(args.assetAmount, undefined),
                recipient,
                shareAmountMax,
            ],
        });
    }
    withdrawExact.call = call;
    /**
     * Defines the Earn share approval and withdrawal calls for atomic
     * execution. Pass `shareToken` explicitly because this builder performs no reads.
     *
     * @param args - Arguments.
     * @returns The calls.
     */
    function calls(args) {
        const { shareToken, vault } = args;
        const assetAmount = internal_Token.toBaseUnits(args.assetAmount, undefined);
        const call = withdrawExact.call({ ...args, assetAmount });
        const [, , shareAmountMax] = call.args;
        return [
            defineCall({
                address: shareToken,
                abi: Abis.tip20,
                functionName: 'approve',
                args: [vault, shareAmountMax],
            }),
            call,
        ];
    }
    withdrawExact.calls = calls;
    /**
     * Extracts a `WithdrewExact` event from the vault's logs.
     *
     * @param logs - Logs.
     * @param parameters - Parameters.
     * @returns The `WithdrewExact` event.
     */
    function extractEvent(logs, parameters) {
        const { vault } = parameters;
        // Earn contracts are user-deployed: several adapters can emit the same
        // signature in one receipt, so filter by emitting address before decode.
        const [log] = parseEventLogs({
            abi: Abis.earnVault,
            eventName: 'WithdrewExact',
            logs: logs.filter((log) => isAddressEqual(log.address, vault)),
        });
        if (!log)
            throw new Error('`WithdrewExact` event not found.');
        return log;
    }
    withdrawExact.extractEvent = extractEvent;
    /**
     * Estimates gas for an exact withdrawal, assuming enough Earn share allowance.
     *
     * @param client - Client.
     * @param parameters - Parameters.
     * @returns The gas estimate.
     */
    async function estimateGas(client, parameters) {
        return estimateContractGas(client, {
            ...pickWriteParameters(parameters),
            ...withdrawExact.call(await toWithdrawExactArgs(client, parameters)),
        });
    }
    withdrawExact.estimateGas = estimateGas;
    /**
     * Simulates an exact withdrawal, assuming enough Earn share allowance.
     *
     * @param client - Client.
     * @param parameters - Parameters.
     * @returns The simulation result and write request.
     */
    async function simulate(client, parameters) {
        return simulateContract(client, {
            ...pickWriteParameters(parameters),
            ...withdrawExact.call(await toWithdrawExactArgs(client, parameters)),
        });
    }
    withdrawExact.simulate = simulate;
})(withdrawExact || (withdrawExact = {}));
/**
 * Withdraws an exact asset amount and returns the confirmed receipt and event data.
 *
 * @example
 * ```ts
 * import { createClient, http } from 'viem'
 * import { privateKeyToAccount } from 'viem/accounts'
 * import { tempoModerato } from 'viem/chains'
 * import { Actions } from 'viem/tempo'
 *
 * const client = createClient({
 *   account: privateKeyToAccount('0x...'),
 *   chain: tempoModerato,
 *   transport: http(),
 * })
 *
 * const { shareAmount } = await Actions.earn.withdrawExactSync(client, {
 *   assetAmount: 40_000_000n,
 *   shareAmountMax: 40_200_000n,
 *   vault: '0x...',
 * })
 * ```
 *
 * @param client - Client.
 * @param parameters - Parameters.
 * @returns The transaction receipt and event data.
 */
export async function withdrawExactSync(client, parameters) {
    const { throwOnReceiptRevert = true, vault } = parameters;
    const receipt = await withdrawExact.inner(sendTransactionSync, client, {
        ...parameters,
        throwOnReceiptRevert,
    });
    if (receipt.status === 'pending')
        return { receipt };
    const { args } = withdrawExact.extractEvent(receipt.logs, { vault });
    return {
        assetAmount: args.assets,
        caller: args.caller,
        receipt,
        recipient: args.receiver,
        shareAmount: args.earnSharesBurned,
    };
}
const zoneGatewayCallbackGas = 10000000n;
function resolveMinimumShareAmount(parameters) {
    if (parameters.shareAmountMin !== undefined)
        return EarnShares.minimumOutput(parameters.shareAmountMin, 0);
    return EarnShares.minimumOutput(parameters.shareAmount, parameters.slippageBps);
}
async function assertPreparedZoneRequestChain(client, parameters) {
    const chain = client.chain;
    if (!chain)
        throw new Error('`chain` is required.');
    if (chain.sourceId !== parameters.chainId)
        throw new Error('Prepared Zone request parent chain ID does not match client chain.');
    const { zoneId } = await zoneActions.getZoneInfo(client);
    if (zoneId !== parameters.zoneId)
        throw new Error('Prepared Zone request Zone ID does not match client chain.');
}
function pickReadParameters(parameters) {
    const { blockOverrides, stateOverride } = parameters;
    if (parameters.blockNumber !== undefined)
        return {
            blockNumber: parameters.blockNumber,
            blockOverrides,
            stateOverride,
        };
    return { blockOverrides, blockTag: parameters.blockTag, stateOverride };
}
async function getZoneGatewayConfig(client, parameters) {
    const { flow, gateway, vault, zoneId, zonePortal, ...rest } = parameters;
    const [vaultAsset, shareToken, allowedZoneId, routerVault, privateAsset, routerVaultAsset, routerShareToken, supportsFlow,] = await multicall(client, {
        ...rest,
        allowFailure: false,
        contracts: [
            {
                abi: Abis.earnVault,
                address: vault,
                functionName: 'asset',
            },
            {
                abi: Abis.earnVault,
                address: vault,
                functionName: 'earnShare',
            },
            {
                abi: Abis.earnRouter,
                address: gateway,
                functionName: 'allowedZoneId',
            },
            {
                abi: Abis.earnRouter,
                address: gateway,
                functionName: 'earnVault',
            },
            {
                abi: Abis.earnRouter,
                address: gateway,
                functionName: 'privateAsset',
            },
            {
                abi: Abis.earnRouter,
                address: gateway,
                functionName: 'vaultAsset',
            },
            {
                abi: Abis.earnRouter,
                address: gateway,
                functionName: 'earnShare',
            },
            {
                abi: Abis.earnRouter,
                address: gateway,
                args: [flow],
                functionName: 'supportsFlow',
            },
        ],
        deployless: true,
    });
    if (!supportsFlow)
        throw new Error('Zone gateway flow is not supported.');
    if (allowedZoneId !== zoneId)
        throw new Error('Zone gateway is configured for a different Zone.');
    if (!isAddressEqual(routerVault, vault))
        throw new Error('Zone gateway is configured for a different Earn vault.');
    if (!isAddressEqual(routerVaultAsset, vaultAsset))
        throw new Error('Zone gateway vault asset does not match the Earn vault.');
    if (!isAddressEqual(routerShareToken, shareToken))
        throw new Error('Zone gateway share token does not match the Earn vault.');
    return {
        privateAsset,
        shareToken,
        vault,
        vaultAsset,
        zoneId,
        zonePortal,
    };
}
// ERC-165 ids of the optional engine capability interfaces (XOR of each
// interface's function selectors).
const interfaceIds = {
    /** `IEarnEngineAsyncRedeem`. */
    asyncRedeem: '0xa1a6a1d7',
    /** `IEarnEngineExactWithdraw`. */
    exactWithdraw: '0x0adfb0b9',
    /** `IEarnEngineInKindDeposit`. */
    inKindDeposit: '0xce4790a9',
    /** `IEarnEngineRedeem`. */
    syncRedeem: '0x94a2d467',
};
/** Trims the decoded `FeeConfig` to its active fixed-fee count. */
function toFeeConfig(config) {
    return {
        excess: config.excess,
        fixedFees: config.fixedFees.slice(0, config.fixedFeeCount),
    };
}
/** Maps decoded fee fields to the action result. */
function toFeePreview(preview) {
    const { allocationCount, allocations, postFeeValuePerEarnShare, preFeeValuePerEarnShare, targetValuePerEarnShare, totalFeeEarnShares, ...rest } = preview;
    return {
        ...rest,
        allocations: allocations
            .slice(0, allocationCount)
            .map(({ feeEarnShares, ...allocation }) => ({
            ...allocation,
            feeShares: feeEarnShares,
        })),
        postFeeValuePerShare: postFeeValuePerEarnShare,
        preFeeValuePerShare: preFeeValuePerEarnShare,
        targetValuePerShare: targetValuePerEarnShare,
        totalFeeShares: totalFeeEarnShares,
    };
}
/** Resolves `deposit` parameters into the adapter call args. @internal */
async function toDepositArgs(client, parameters) {
    const { vault } = parameters;
    const assetAmount = await toBaseUnitsLive(client, {
        amount: parameters.assetAmount,
        token: 'asset',
        vault,
    });
    const args = {
        assetAmount,
        recipient: resolveRecipient(client, parameters),
        vault,
    };
    if (parameters.shareAmountMin !== undefined)
        return { ...args, shareAmountMin: parameters.shareAmountMin };
    return {
        ...args,
        shareAmount: parameters.shareAmount,
        slippageBps: parameters.slippageBps,
    };
}
/** Resolves `depositShares` parameters into the adapter call args. @internal */
function toDepositSharesArgs(client, parameters) {
    const { vault, venueShareAmount } = parameters;
    const args = {
        recipient: resolveRecipient(client, parameters),
        vault,
        venueShareAmount,
    };
    if (parameters.earnShareAmountMin !== undefined)
        return { ...args, earnShareAmountMin: parameters.earnShareAmountMin };
    return {
        ...args,
        earnShareAmount: parameters.earnShareAmount,
        slippageBps: parameters.slippageBps,
    };
}
/** Resolves `redeem` parameters into the adapter call args. @internal */
async function toRedeemArgs(client, parameters) {
    const { vault } = parameters;
    const shareAmount = await toBaseUnitsLive(client, {
        amount: parameters.shareAmount,
        token: 'shareToken',
        vault,
    });
    const args = {
        recipient: resolveRecipient(client, parameters),
        shareAmount,
        vault,
    };
    if (parameters.assetAmountMin !== undefined)
        return { ...args, assetAmountMin: parameters.assetAmountMin };
    const assetAmount = await (async () => {
        if (parameters.assetAmount !== undefined)
            return parameters.assetAmount;
        return getRedeemQuote(client, { shareAmount, vault });
    })();
    return {
        ...args,
        assetAmount,
        slippageBps: parameters.slippageBps,
    };
}
/** Resolves `withdrawExact` parameters into the adapter call args. @internal */
async function toWithdrawExactArgs(client, parameters) {
    const { vault } = parameters;
    const assetAmount = await toBaseUnitsLive(client, {
        amount: parameters.assetAmount,
        token: 'asset',
        vault,
    });
    const args = {
        assetAmount,
        recipient: resolveRecipient(client, parameters),
        vault,
    };
    if (parameters.shareAmountMax !== undefined)
        return { ...args, shareAmountMax: parameters.shareAmountMax };
    const shareAmount = await (async () => {
        if (parameters.shareAmount !== undefined)
            return parameters.shareAmount;
        return getWithdrawQuote(client, { assetAmount, vault });
    })();
    return {
        ...args,
        shareAmount,
        slippageBps: parameters.slippageBps,
    };
}
/** Raises a quoted input by basis points with ceiling rounding. @internal */
function maximumInput(shareAmount, slippageBps) {
    if (shareAmount <= 0n)
        throw new EarnShares.InvalidExpectedOutputError({
            expectedAmount: shareAmount,
        });
    if (!Number.isInteger(slippageBps) ||
        slippageBps < 0 ||
        slippageBps >= EarnShares.basisPointScale)
        throw new EarnShares.InvalidSlippageError({ slippageBps });
    const scale = BigInt(EarnShares.basisPointScale);
    const numerator = shareAmount * (scale + BigInt(slippageBps));
    // Adding the denominator minus one converts floor division to ceiling.
    return (numerator + scale - 1n) / scale;
}
/**
 * Converts an amount to base units, resolving missing decimals with live
 * reads of the vault's asset or share token. Earn share tokens are not
 * genesis-declared, so nothing is cached. @internal
 */
async function toBaseUnitsLive(client, options) {
    const { amount, token, vault } = options;
    if (typeof amount === 'bigint')
        return amount;
    if (amount.decimals !== undefined)
        return internal_Token.toBaseUnits(amount, amount.decimals);
    const address = await readContract(client, {
        abi: Abis.earnVault,
        address: vault,
        functionName: token === 'asset' ? 'asset' : 'earnShare',
    });
    const { decimals } = await resolveTokenWithDecimals(client, {
        token: address,
    });
    return internal_Token.toBaseUnits(amount, decimals);
}
/** Defaults a write's `recipient` to the sending account's address. @internal */
function resolveRecipient(client, parameters) {
    if (parameters.recipient)
        return parameters.recipient;
    const account = parameters.account ?? client.account;
    if (!account)
        throw new AccountNotFoundError();
    return parseAccount(account).address;
}
//# sourceMappingURL=earn.js.map