UNPKG

viem

Version:

TypeScript Interface for Ethereum

1,037 lines • 37.1 kB
import { Hex } from 'ox'; import { parseAccount } from '../../../accounts/utils/parseAccount.js'; import { getCode } from '../../../actions/public/getCode.js'; import { getLogs } from '../../../actions/public/getLogs.js'; import { readContract } from '../../../actions/public/readContract.js'; import { simulateContract } from '../../../actions/public/simulateContract.js'; import { writeContract, } from '../../../actions/wallet/writeContract.js'; import { writeContractSync } from '../../../actions/wallet/writeContractSync.js'; import { zeroAddress } from '../../../constants/address.js'; import { maxUint64, maxUint256 } from '../../../constants/number.js'; import { AccountNotFoundError } from '../../../errors/account.js'; import { BaseError } from '../../../errors/base.js'; import { ChainNotFoundError } from '../../../errors/chain.js'; import { TransactionReceiptRevertedError } from '../../../errors/transaction.js'; import { getAbiItem } from '../../../utils/abi/getAbiItem.js'; import { parseEventLogs } from '../../../utils/abi/parseEventLogs.js'; import { isAddressEqual } from '../../../utils/address/isAddressEqual.js'; import { getChainContractAddress } from '../../../utils/chain/getChainContractAddress.js'; import * as Abis from '../../Abis.js'; import * as Addresses from '../../Addresses.js'; import { defineCall, pickWriteParameters, pickWriteSyncParameters, } from '../../internal/utils.js'; const engineBindingAbi = [ { type: 'function', name: 'initializeEarnVault', inputs: [ { name: 'earnVault_', type: 'address' }, { name: 'finalOwner_', type: 'address' }, ], outputs: [], stateMutability: 'nonpayable', }, { type: 'function', name: 'initializeEarnVault', inputs: [{ name: 'earnVault_', type: 'address' }], outputs: [], stateMutability: 'nonpayable', }, { type: 'event', name: 'EarnVaultInitialized', inputs: [{ name: 'earnVault', type: 'address', indexed: true }], anonymous: false, }, ]; /** * Deploys a deterministic ERC-4626 Earn engine. * * @experimental * * @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.createErc4626Engine(client, { * deploymentId: '0x...', * factory: '0x...', * venue: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction hash. */ export async function createErc4626Engine(client, parameters) { return createErc4626Engine.inner(writeContract, client, parameters); } (function (createErc4626Engine) { /** @internal */ async function inner(action, client, parameters) { const { account = client.account, chain = client.chain, deploymentId, factory, name, owner: owner_ = account, symbol, venue, ...rest } = parameters; if (!account) throw new AccountNotFoundError(); if (!owner_) throw new Error('`owner` is required.'); const owner = parseAccount(owner_).address; return (await action(client, { ...rest, account, chain, ...createErc4626Engine.call({ deploymentId, factory, name, owner, symbol, venue, }), })); } createErc4626Engine.inner = inner; /** * Defines a call to `ERC4626EngineFactory.deploy`. * * Can be passed as a parameter to: * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls * * @example * ```ts * import { createClient, http, walletActions } from 'viem' * import { tempoModerato } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ chain: tempoModerato, transport: http() }) * .extend(walletActions) * await client.sendTransaction({ * calls: [Actions.earn.createErc4626Engine.call({ * deploymentId: '0x...', * factory: '0x...', * owner: '0x...', * venue: '0x...', * })], * }) * ``` * * @param args - Arguments. * @returns The call. */ function call(args) { validateDeploymentId(args.deploymentId); return defineCall({ address: args.factory, abi: Abis.erc4626EngineFactory, functionName: 'deploy', args: [ args.deploymentId, args.venue, args.owner, args.name ?? '', args.symbol ?? '', ], }); } createErc4626Engine.call = call; /** * Predicts the deterministic engine address. * * @param client - Client. * @param args - Engine deployment arguments. * @returns The predicted engine address. */ async function predict(client, args) { validateDeploymentId(args.deploymentId); return readContract(client, { address: args.factory, abi: Abis.erc4626EngineFactory, functionName: 'predictEngine', args: [ args.deploymentId, args.venue, args.owner, args.name ?? '', args.symbol ?? '', ], }); } createErc4626Engine.predict = predict; /** * Extracts the `ERC4626EngineDeployed` event from factory logs. * * @param logs - The logs. * @param parameters - Factory address used to filter the logs. * @returns The deployment event. */ function extractEvent(logs, parameters) { const [log] = parseEventLogs({ abi: Abis.erc4626EngineFactory, eventName: 'ERC4626EngineDeployed', logs: logs.filter((log) => isAddressEqual(log.address, parameters.factory)), strict: true, }); if (!log) throw new Error('`ERC4626EngineDeployed` event not found.'); return log; } createErc4626Engine.extractEvent = extractEvent; })(createErc4626Engine || (createErc4626Engine = {})); /** * Deploys an ERC-4626 engine and waits for confirmation. * * @experimental * * @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 result = await Actions.earn.createErc4626EngineSync(client, { * deploymentId: '0x...', * factory: '0x...', * venue: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The receipt and deployed engine metadata. */ export async function createErc4626EngineSync(client, parameters) { const { factory, throwOnReceiptRevert = true } = parameters; const receipt = await createErc4626Engine.inner(writeContractSync, client, { ...parameters, throwOnReceiptRevert, }); if (receipt.status === 'pending') return { receipt }; const { args } = createErc4626Engine.extractEvent(receipt.logs, { factory }); return { ...args, receipt }; } /** * Creates an EarnShare, EarnVault, and EarnFees stack around an unbound engine. * * @experimental * * @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.createStack(client, { * deploymentId: '0x...', * engine: '0x...', * factory: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction hash. */ export async function createStack(client, parameters) { return createStack.inner(writeContract, client, parameters); } (function (createStack) { /** @internal */ async function inner(action, client, parameters) { const { account = client.account, chain = client.chain, controls, deploymentId, distributor, engine, factory, fees, owner: owner_ = account, transferPolicyId, ...rest } = parameters; if (!account) throw new AccountNotFoundError(); if (!owner_) throw new Error('`owner` is required.'); return (await action(client, { ...rest, account, chain, ...createStack.call({ controls, deploymentId, distributor, engine, factory, fees, owner: parseAccount(owner_).address, transferPolicyId, }), })); } createStack.inner = inner; /** * Defines a call to `EarnFactory.deploy`. * * Can be passed as a parameter to: * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls * * @example * ```ts * import { createClient, http, walletActions } from 'viem' * import { tempoModerato } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ chain: tempoModerato, transport: http() }) * .extend(walletActions) * await client.sendTransaction({ * calls: [Actions.earn.createStack.call({ * deploymentId: '0x...', * engine: '0x...', * factory: '0x...', * owner: '0x...', * })], * }) * ``` * * @param args - Arguments. * @returns The call. */ function call(args) { validateDeploymentId(args.deploymentId); return defineCall({ address: args.factory, abi: Abis.earnFactory, functionName: 'deploy', args: [toDeployParameters(args)], }); } createStack.call = call; /** * Predicts the deterministic EarnShare and EarnFees addresses. * * @param client - Client. * @param args - Stack deployment arguments. * @returns The predicted EarnShare and EarnFees addresses. */ async function predict(client, args) { validateDeploymentId(args.deploymentId); const parameters = toDeployParameters(args); const [earnShare, earnFees] = await Promise.all([ readContract(client, { address: args.factory, abi: Abis.earnFactory, functionName: 'predictEarnShare', args: [parameters], }), readContract(client, { address: args.factory, abi: Abis.earnFactory, functionName: 'predictEarnFees', args: [parameters], }), ]); return { earnFees, earnShare }; } createStack.predict = predict; /** * Extracts the `EarnStackDeployed` event from factory logs. * * @param logs - The logs. * @param parameters - Factory address used to filter the logs. * @returns The deployment event. */ function extractEvent(logs, parameters) { const [log] = parseEventLogs({ abi: Abis.earnFactory, eventName: 'EarnStackDeployed', logs: logs.filter((log) => isAddressEqual(log.address, parameters.factory)), strict: true, }); if (!log) throw new Error('`EarnStackDeployed` event not found.'); return log; } createStack.extractEvent = extractEvent; })(createStack || (createStack = {})); /** * Creates an Earn core stack and waits for confirmation. * * @experimental * * @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 result = await Actions.earn.createStackSync(client, { * deploymentId: '0x...', * engine: '0x...', * factory: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The receipt and deployed stack addresses. */ export async function createStackSync(client, parameters) { const { factory, throwOnReceiptRevert = true } = parameters; const receipt = await createStack.inner(writeContractSync, client, { ...parameters, throwOnReceiptRevert, }); if (receipt.status === 'pending') return { receipt }; const { args } = createStack.extractEvent(receipt.logs, { factory }); return { ...args, receipt }; } /** * Permanently binds an engine to its EarnVault. * * @experimental * * @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.bindEngine(client, { * engine: '0x...', * finalOwner: '0x...', * vault: '0x...', * }) * ``` * * @param client - Client controlled by the final engine owner. * @param parameters - Parameters. * @returns The transaction hash. */ export async function bindEngine(client, parameters) { return bindEngine.inner(writeContract, client, parameters); } (function (bindEngine) { /** @internal */ async function inner(action, client, parameters) { const { engine, finalOwner, vault, ...rest } = parameters; return (await action(client, { ...rest, ...bindEngine.call({ engine, finalOwner, vault }), })); } bindEngine.inner = inner; /** * Defines a call to `initializeEarnVault` on an Earn engine. * * Can be passed as a parameter to: * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls * * @example * ```ts * import { createClient, http, walletActions } from 'viem' * import { tempoModerato } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ chain: tempoModerato, transport: http() }) * .extend(walletActions) * await client.sendTransaction({ * calls: [Actions.earn.bindEngine.call({ * engine: '0x...', * finalOwner: '0x...', * vault: '0x...', * })], * }) * ``` * * @param args - Arguments. * @returns The call. */ function call(args) { const { engine, finalOwner, vault } = args; if (finalOwner) return defineCall({ address: engine, abi: engineBindingAbi, functionName: 'initializeEarnVault', args: [vault, finalOwner], }); return defineCall({ address: engine, abi: engineBindingAbi, functionName: 'initializeEarnVault', args: [vault], }); } bindEngine.call = call; /** * Extracts the `EarnVaultInitialized` event from engine logs. * * @param logs - The logs. * @param parameters - Engine address used to filter the logs. * @returns The initialization event. */ function extractEvent(logs, parameters) { const [log] = parseEventLogs({ abi: engineBindingAbi, eventName: 'EarnVaultInitialized', logs: logs.filter((log) => isAddressEqual(log.address, parameters.engine)), strict: true, }); if (!log) throw new Error('`EarnVaultInitialized` event not found.'); return log; } bindEngine.extractEvent = extractEvent; })(bindEngine || (bindEngine = {})); /** * Binds an engine and waits for confirmation. * * @experimental * * @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 result = await Actions.earn.bindEngineSync(client, { * engine: '0x...', * finalOwner: '0x...', * vault: '0x...', * }) * ``` * * @param client - Client controlled by the final engine owner. * @param parameters - Parameters. * @returns The receipt and bound addresses. */ export async function bindEngineSync(client, parameters) { const { engine, throwOnReceiptRevert = true } = parameters; const receipt = await bindEngine.inner(writeContractSync, client, { ...parameters, throwOnReceiptRevert, }); if (receipt.status === 'pending') return { receipt }; const { args } = bindEngine.extractEvent(receipt.logs, { engine }); return { engine, vault: args.earnVault, receipt }; } /** * Error thrown after a partially completed Earn stack deployment. * * @experimental */ export class DeployErc4626StackError extends BaseError { constructor(cause, parameters) { super(`ERC-4626 Earn deployment failed during ${parameters.stage}.`, { cause, name: 'DeployErc4626StackError', }); Object.defineProperty(this, "receipts", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "stage", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "state", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.receipts = parameters.receipts; this.stage = parameters.stage; this.state = parameters.state; } } /** * Deploys and binds a complete ERC-4626 Earn stack through sequential, * resumable transactions. * * @experimental * * @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 result = await Actions.earn.deployErc4626StackSync(client, { * deploymentId: '0x...', * venue: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The deployed addresses and receipts created by this run. */ export async function deployErc4626StackSync(client, parameters) { const deployer = parameters.account ?? client.account; if (!deployer) throw new AccountNotFoundError(); const owner_ = parameters.owner ?? deployer; const owner = parseAccount(owner_).address; const deployerAddress = parseAccount(deployer).address; const bindingAccount = (() => { if (parameters.bindingAccount) return parameters.bindingAccount; if (typeof owner_ !== 'string') return owner_; if (isAddressEqual(owner, deployerAddress)) return deployer; return undefined; })(); if (bindingAccount && !isAddressEqual(parseAccount(bindingAccount).address, owner)) throw new Error('`bindingAccount` must match `owner`.'); const factories = (() => { if (parameters.factories) return parameters.factories; if (!client.chain) throw new ChainNotFoundError(); return { earn: getChainContractAddress({ chain: client.chain, contract: 'earnFactory', }), erc4626Engine: getChainContractAddress({ chain: client.chain, contract: 'erc4626EngineFactory', }), }; })(); validateDeploymentId(parameters.deploymentId); if (parameters.resume && parameters.resume.deploymentId.toLowerCase() !== parameters.deploymentId.toLowerCase()) throw new Error('The resumed deployment ID does not match `deploymentId`.'); await validateContracts(client, { factories, venue: parameters.venue, }); const { gas: _, keyAuthorization: __, nonce: ___, owner: ____, ...sharedWriteParameters } = pickWriteParameters(parameters); const writeParameters = { ...sharedWriteParameters, ...pickWriteSyncParameters(parameters), account: deployer, throwOnReceiptRevert: true, }; const engineArgs = { deploymentId: parameters.deploymentId, factory: factories.erc4626Engine, name: parameters.name, owner, symbol: parameters.symbol, venue: parameters.venue, }; const predictedEngine = await createErc4626Engine.predict(client, engineArgs); const state = { deploymentId: parameters.deploymentId, earnShare: parameters.resume?.earnShare, engine: predictedEngine, fees: parameters.resume?.fees, vault: parameters.resume?.vault, }; if (parameters.resume?.engine && !isAddressEqual(parameters.resume.engine, predictedEngine)) throw new Error('The resumed engine does not match the factory prediction.'); const engineExists = await hasCode(client, predictedEngine); if (!bindingAccount) { const boundVault = engineExists ? await readContract(client, { address: predictedEngine, abi: Abis.erc4626Engine, functionName: 'earnVault', }) : zeroAddress; if (isAddressEqual(boundVault, zeroAddress)) throw new Error('`bindingAccount` is required when the final owner differs from the deployment account.'); } const receipts = {}; try { if (!engineExists) { await simulateContract(client, { ...sharedWriteParameters, account: deployer, ...createErc4626Engine.call(engineArgs), }); const receipt = await createErc4626Engine.inner(writeContractSync, client, { ...writeParameters, ...engineArgs, }); receipts.engine = receipt; if (receipt.status === 'pending') return { receipt }; createErc4626Engine.extractEvent(receipt.logs, { factory: factories.erc4626Engine, }); } await verifyEngine(client, engineArgs, predictedEngine); } catch (error) { throw deploymentError(error, 'engine', state, receipts); } const stackArgs = { controls: parameters.controls, deploymentId: parameters.deploymentId, distributor: parameters.distributor, engine: predictedEngine, factory: factories.earn, fees: parameters.fees, owner, transferPolicyId: parameters.transferPolicyId, }; try { const predicted = await createStack.predict(client, stackArgs); if (parameters.resume?.earnShare && !isAddressEqual(parameters.resume.earnShare, predicted.earnShare)) throw new Error('The resumed EarnShare does not match the factory prediction.'); if (parameters.resume?.fees && !isAddressEqual(parameters.resume.fees, predicted.earnFees)) throw new Error('The resumed EarnFees does not match the factory prediction.'); state.earnShare = predicted.earnShare; state.fees = predicted.earnFees; if (!(await hasCode(client, predicted.earnShare))) { if (await hasCode(client, predicted.earnFees)) throw new Error('Predicted EarnFees exists without the predicted EarnShare.'); await simulateContract(client, { ...sharedWriteParameters, account: deployer, ...createStack.call(stackArgs), }); const receipt = await createStack.inner(writeContractSync, client, { ...writeParameters, ...stackArgs, }); receipts.stack = receipt; if (receipt.status === 'pending') return { receipt }; const { args } = createStack.extractEvent(receipt.logs, { factory: factories.earn, }); state.vault = args.earnVault; if (parameters.resume?.vault && !isAddressEqual(parameters.resume.vault, args.earnVault)) throw new Error('The resumed EarnVault does not match the factory deployment.'); } else { if (!(await hasCode(client, predicted.earnFees))) throw new Error('Predicted EarnShare exists without the predicted EarnFees.'); state.vault = parameters.resume?.vault; if (!state.vault) { const event = await findStackDeployment(client, { earnShare: predicted.earnShare, factory: factories.earn, fromBlock: parameters.fromBlock, }); state.vault = event.args.earnVault; if (!isAddressEqual(event.args.earnFees, predicted.earnFees)) throw new Error('Recovered EarnFees does not match the factory prediction.'); } } if (!state.vault) throw new Error('EarnVault address was not recovered.'); await verifyStack(client, { engine: predictedEngine, fees: predicted.earnFees, owner, share: predicted.earnShare, vault: state.vault, }); } catch (error) { throw deploymentError(error, 'stack', state, receipts); } try { const vault = state.vault; const boundVault = await readContract(client, { address: predictedEngine, abi: Abis.erc4626Engine, functionName: 'earnVault', }); if (isAddressEqual(boundVault, zeroAddress)) { if (!bindingAccount) throw new Error('`bindingAccount` is required when the final owner differs from the deployment account.'); const bindingParameters = { ...writeParameters, account: bindingAccount, engine: predictedEngine, finalOwner: owner, vault, }; await simulateContract(client, { ...sharedWriteParameters, account: bindingAccount, ...bindEngine.call({ engine: predictedEngine, finalOwner: owner, vault, }), }); const receipt = await bindEngine.inner(writeContractSync, client, bindingParameters); receipts.binding = receipt; if (receipt.status === 'pending') return { receipt }; bindEngine.extractEvent(receipt.logs, { engine: predictedEngine }); } else if (!isAddressEqual(boundVault, vault)) { throw new Error(`Engine is already bound to ${boundVault}.`); } const verified = await readContract(client, { address: predictedEngine, abi: Abis.erc4626Engine, functionName: 'earnVault', }); if (!isAddressEqual(verified, vault)) throw new Error('Engine binding verification failed.'); } catch (error) { throw deploymentError(error, 'binding', state, receipts); } return { deploymentId: parameters.deploymentId, earnShare: state.earnShare, engine: predictedEngine, fees: state.fees, receipts, vault: state.vault, }; } function validateDeploymentId(deploymentId) { if (Hex.size(deploymentId) !== 32 || BigInt(deploymentId) === 0n) throw new Error('`deploymentId` must be a nonzero 32-byte hex value.'); } function toDeployParameters(args) { const fixedFees = args.fees?.fixedFees ?? []; if (fixedFees.length > 4) throw new Error('Earn supports at most four fixed fee recipients.'); const seen = new Set(); let totalRateBps = 0; for (const fee of fixedFees) { validateFeeRate(fee.rateBps, '`fixedFees[].rateBps`', false); if (isAddressEqual(fee.account, zeroAddress)) throw new Error('Fixed fee recipients cannot be the zero address.'); const account = fee.account.toLowerCase(); if (seen.has(account)) throw new Error('Fixed fee recipients must be unique.'); seen.add(account); totalRateBps += fee.rateBps; } if (totalRateBps > 10_000) throw new Error('The total fixed fee rate cannot exceed 10,000 bps.'); const excess = args.fees?.excess; if (excess) { validateFeeRate(excess.annualTargetRateBps, '`annualTargetRateBps`', true); validateFeeRate(excess.rateBps, '`excess.rateBps`', false); if (isAddressEqual(excess.account, zeroAddress)) throw new Error('The excess fee recipient cannot be the zero address.'); } const zeroFee = { account: zeroAddress, rateBps: 0 }; const distributor = args.distributor?.distributor ?? zeroAddress; const updateDelay = args.distributor?.updateDelay ?? 0; if (args.distributor) { if (isAddressEqual(distributor, zeroAddress)) throw new Error('An enabled distributor cannot be the zero address.'); if (updateDelay <= 0) throw new Error('An enabled distributor requires a positive update delay.'); if (fixedFees.length === 0) throw new Error('An enabled distributor requires at least one fixed fee.'); } if (!Number.isSafeInteger(updateDelay) || updateDelay > 0xffffffffff) throw new Error('`updateDelay` must fit into uint40 seconds.'); const transferPolicyId = args.transferPolicyId ?? 0n; if (transferPolicyId < 0n || transferPolicyId > maxUint64) throw new Error('`transferPolicyId` must fit into uint64.'); const maxManagedAssets = args.controls?.maxManagedAssets ?? 0n; if (maxManagedAssets < 0n || maxManagedAssets > maxUint256) throw new Error('`maxManagedAssets` must fit into uint256.'); return { controls: { asyncJanitor: args.controls?.asyncJanitor ?? zeroAddress, emergencyGuardian: args.controls?.emergencyGuardian ?? zeroAddress, maxManagedAssets, migrationMode: (args.controls?.migrationMode ?? 'userOnly') === 'userOnly' ? 0 : 1, }, deploymentId: args.deploymentId, distributorConfig: { distributor, updateDelay }, engine: args.engine, fees: { excess: excess ? { account: excess.account, annualTargetRateBps: excess.annualTargetRateBps, enabled: true, excessFeeRateBps: excess.rateBps, } : { account: zeroAddress, annualTargetRateBps: 0, enabled: false, excessFeeRateBps: 0, }, fixedFeeCount: fixedFees.length, fixedFees: [ fixedFees[0] ?? zeroFee, fixedFees[1] ?? zeroFee, fixedFees[2] ?? zeroFee, fixedFees[3] ?? zeroFee, ], }, owner: args.owner, transferPolicyId, }; } function validateFeeRate(rate, name, allowZero) { if (!Number.isInteger(rate) || rate < (allowZero ? 0 : 1) || rate > 10_000) throw new Error(`${name} must be an integer between ${allowZero ? 0 : 1} and 10,000.`); } async function hasCode(client, address) { const code = await getCode(client, { address }); return code !== undefined && code !== '0x'; } async function validateContracts(client, parameters) { const [engineFactory, earnFactory, venue] = await Promise.all([ hasCode(client, parameters.factories.erc4626Engine), hasCode(client, parameters.factories.earn), hasCode(client, parameters.venue), ]); if (!engineFactory) throw new Error('ERC4626EngineFactory has no code.'); if (!earnFactory) throw new Error('EarnFactory has no code.'); if (!venue) throw new Error('ERC-4626 venue has no code.'); const tip20Factory = await readContract(client, { address: parameters.factories.earn, abi: Abis.earnFactory, functionName: 'tip20Factory', }); if (!isAddressEqual(tip20Factory, Addresses.tip20Factory)) throw new Error('EarnFactory uses an unexpected TIP-20 factory.'); } async function verifyEngine(client, args, engine) { const [venue, owner] = await Promise.all([ readContract(client, { address: engine, abi: Abis.erc4626Engine, functionName: 'vault', }), readContract(client, { address: engine, abi: Abis.erc4626Engine, functionName: 'owner', }), ]); if (!isAddressEqual(venue, args.venue)) throw new Error('Engine venue verification failed.'); if (!isAddressEqual(owner, args.owner)) throw new Error('Engine owner verification failed.'); } async function verifyStack(client, parameters) { const [engine, fees, operator, share] = await Promise.all([ readContract(client, { address: parameters.vault, abi: Abis.earnVault, functionName: 'engine', }), readContract(client, { address: parameters.vault, abi: Abis.earnVault, functionName: 'earnFees', }), readContract(client, { address: parameters.vault, abi: Abis.earnVault, functionName: 'operator', }), readContract(client, { address: parameters.vault, abi: Abis.earnVault, functionName: 'earnShare', }), ]); if (!isAddressEqual(engine, parameters.engine)) throw new Error('EarnVault engine verification failed.'); if (!isAddressEqual(fees, parameters.fees)) throw new Error('EarnVault fees verification failed.'); if (!isAddressEqual(operator, parameters.owner)) throw new Error('EarnVault operator verification failed.'); if (!isAddressEqual(share, parameters.share)) throw new Error('EarnVault share verification failed.'); } async function findStackDeployment(client, parameters) { const event = getAbiItem({ abi: Abis.earnFactory, name: 'EarnStackDeployed', }); const logs = await getLogs(client, { address: parameters.factory, args: { earnShare: parameters.earnShare }, event, fromBlock: parameters.fromBlock ?? 0n, strict: true, toBlock: 'latest', }); const log = logs.at(-1); if (!log) throw new Error('Prior `EarnStackDeployed` event not found.'); return log; } function deploymentError(error, stage, state, receipts) { if (error instanceof DeployErc4626StackError) return error; if (error instanceof TransactionReceiptRevertedError) receipts[stage] = error.receipt; return new DeployErc4626StackError(error instanceof Error ? error : new Error(String(error)), { receipts: { ...receipts }, stage, state: { ...state } }); } //# sourceMappingURL=deployment.js.map