UNPKG

@kiroboio/fct-core

Version:

Kirobo.io FCT Core library

286 lines 11.5 kB
import { utils } from "ethers"; // fctCall overhead (1st call) - 40k // fctCall overhead (other calls) - 11k const pluginCache = new Map(); // TODO: This needs to be heavily optimized. const WHOLE_IN_BPS = 10000n; const fees = { beforeCallingBatchMultiSigCall: 5000n, FCTControllerOverhead: 43000n, gasBeforeEncodedLoop: 3000n, gasForEncodingCall: 8000n, additionalGasForEncodingCall: 100n, FCTControllerRegisterCall: 43000n, signatureRecovery: 6000n, miscGasBeforeMcallLoop: 1700n, paymentApproval: 9000n, paymentsOutBase: 24500n, paymentsOutPerPayment: 1300n, totalCallsChecker: 16000n, estimateExtraCommmonGasCost: 4000n, mcallOverheadFirstCall: 40000n, mcallOverheadOtherCalls: 11000n, defaultGasLimit: 30000n, }; // Arbitrum fees are 13x higher than Ethereum fees. Multiply all fees by 13. const arbitrumFees = Object.fromEntries(Object.entries(fees).map(([key, value]) => [key, BigInt(value) * 13n])); export function getFee(key, chainId) { if (chainId === "42161" || chainId === "421613") { return arbitrumFees[key]; } return fees[key]; } const getEncodingMcallCost = (callCount, chainId) => { return (BigInt(callCount) * getFee("gasForEncodingCall", chainId) + (BigInt(callCount) * BigInt(callCount - 1) * getFee("additionalGasForEncodingCall", chainId)) / 2n); }; const getSignatureRecoveryCost = (signatureCount, chainId) => { return BigInt(signatureCount) * getFee("signatureRecovery", chainId); }; const getPaymentsOutCost = (callCount, chainId) => { return getFee("paymentsOutBase", chainId) + BigInt(callCount) * getFee("paymentsOutPerPayment", chainId); }; const getExtraCommonGas = (payersCount, msgDataLength) => { return 23100n + 4600n * BigInt(payersCount) + (77600n * BigInt(msgDataLength)) / 10000n; }; const getPayers = (calls, pathIndexes) => { return pathIndexes.reduce((acc, pathIndex) => { const call = calls[Number(pathIndex)].get(); const payerIndex = call.options.payerIndex; if (payerIndex === 0) return acc; if (payerIndex === +pathIndex + 1) { const payer = call.from; if (payer && payer !== acc[acc.length - 1] && typeof payer === "string") { acc.push(payer); } return acc; } // Else we dont know the from const payer = calls[payerIndex - 1].get().from; if (payer && payer !== acc[acc.length - 1] && typeof payer === "string") { acc.push(payer); } return acc; }, []); }; const getAllSigners = (calls) => { return calls.reduce((acc, call) => { const from = call.get().from; if (typeof from === "string" && !acc.includes(from)) { acc.push(from); } return acc; }, []); }; export function getPayersForRoute({ FCT, chainId, calls, pathIndexes, calldata, fctID, }) { const payers = getPayers(calls, pathIndexes); const allSigners = getAllSigners(calls); const batchMultiSigCallOverhead = getFee("FCTControllerOverhead", chainId) + getFee("gasBeforeEncodedLoop", chainId) + getEncodingMcallCost(calls.length, chainId) + getFee("FCTControllerRegisterCall", chainId) + getSignatureRecoveryCost(allSigners.length + 1, chainId) + // +1 because verification signature getFee("miscGasBeforeMcallLoop", chainId); const overhead = getFee("beforeCallingBatchMultiSigCall", chainId) + batchMultiSigCallOverhead + getPaymentsOutCost(calls.length, chainId) + getFee("totalCallsChecker", chainId) + getFee("estimateExtraCommmonGasCost", chainId); const commonGas = getExtraCommonGas(payers.length, calldata.length) + overhead; const commonGasPerCall = commonGas / BigInt(pathIndexes.length); const gasForFCTCall = pathIndexes.reduce((acc, path) => { const call = calls[Number(path)]; const _call = call.get(); const options = _call.options; const payerIndex = options.payerIndex; // If payer is the activator, dont add it to the needed fuel if (payerIndex === 0) return acc; const payer = calls[payerIndex - 1].get().from; if (typeof payer !== "string") return acc; let gas; if (options.gasLimit === "0") { gas = getFee("defaultGasLimit", chainId); } else { gas = BigInt(options.gasLimit); } const amount = gas + commonGasPerCall; if (acc[payer]) { acc[payer] += amount; } else { acc[payer] = amount; } return acc; }, {}); const gasForPaymentApprovals = payers.reduce((acc, address) => { const fee = getFee("paymentApproval", chainId); if (acc[address]) { acc[address] += fee; } else { acc[address] = fee; } return acc; }, {}); return payers.map((payer) => { const gas = gasForFCTCall[payer] + gasForPaymentApprovals[payer]; return { payer, gas: gas || 0n, }; }); } export function getEffectiveGasPrice({ maxGasPrice, gasPrice, baseFeeBPS, bonusFeeBPS, }) { return ((BigInt(gasPrice) * (WHOLE_IN_BPS + baseFeeBPS) + (BigInt(maxGasPrice) - BigInt(gasPrice)) * bonusFeeBPS) / WHOLE_IN_BPS).toString(); } export function getCostInKiro({ ethPriceInKIRO, ethCost, }) { return (((ethCost || 0n) * BigInt(ethPriceInKIRO)) / 10n ** 18n).toString(); } export function getGasPrices({ maxGasPrice, gasPrice, baseFeeBPS, bonusFeeBPS, }) { // NOTE: Calculation for base // uint256 l1GasUsed = (dataLength * 84500) / WHOLE_IN_BPS; // uint256 effectiveGasPrice = (OPGasOracle.baseFeeScalar() * 16 * OPGasOracle.l1BaseFee()) + // (OPGasOracle.blobBaseFeeScalar() * OPGasOracle.blobBaseFee()); // uint256 fee = l1GasUsed * effectiveGasPrice; const txGasPrice = gasPrice ? BigInt(gasPrice) : maxGasPrice; const effectiveGasPrice = BigInt(getEffectiveGasPrice({ gasPrice: txGasPrice, maxGasPrice, baseFeeBPS, bonusFeeBPS, })); return { txGasPrice, effectiveGasPrice, }; } export function getPayerMap({ FCT, fctID, paths, calldata, calls, gasPrice, maxGasPrice, baseFeeBPS, bonusFeeBPS, payableGasLimit, penalty, opStackGasFees, }) { const chainId = FCT.chainId; const { txGasPrice, effectiveGasPrice } = getGasPrices({ maxGasPrice, gasPrice, baseFeeBPS, bonusFeeBPS, }); let additionalGas = 0n; // If the chain is OP Stack chain if (chainId === "10" || chainId === "8453") { if (!opStackGasFees) { console.warn("For OP Stack chains (Base, Optimism) opStackGasFees should be provided to estimate gas more precisely."); } else { // From Arch.sol: // uint256 l1GasUsed = (dataLength * 84500) / WHOLE_IN_BPS; // uint256 effectiveGasPrice = (OPGasOracle.baseFeeScalar() * 16 * OPGasOracle.l1BaseFee()) + // (OPGasOracle.blobBaseFeeScalar() * OPGasOracle.blobBaseFee()); // uint256 fee = l1GasUsed * effectiveGasPrice; // additionalGas = fee / (16 * 10 ** 6 * tx.gasprice); const dataLength = BigInt(calldata.length / 2 - 1); const l1GasUsed = (dataLength * 84500n) / 10000n; const baseFeeScalar = BigInt(opStackGasFees.baseFeeScalar); const l1BaseFee = BigInt(opStackGasFees.l1BaseFee); const blobBaseFeeScalar = BigInt(opStackGasFees.blobBaseFeeScalar); const blobBaseFee = BigInt(opStackGasFees.blobBaseFee); const opStackEffectiveGasPrice = baseFeeScalar * 16n * l1BaseFee + blobBaseFeeScalar * blobBaseFee; const fee = l1GasUsed * opStackEffectiveGasPrice; additionalGas = fee / (16n * 10n ** 6n * txGasPrice); } } return paths.map((path) => { const payers = getPayersForRoute({ chainId, calldata, calls, pathIndexes: path, FCT, fctID, }); return payers.reduce((acc, payer) => { let gas = payer.gas + additionalGas; if (payableGasLimit) { gas = gas > payableGasLimit ? payableGasLimit : gas; } const base = gas * txGasPrice; const fee = gas * (effectiveGasPrice - txGasPrice); const ethCost = base + fee; return { ...acc, [payer.payer]: { ...payer, gas: gas, pureEthCost: ethCost, ethCost: (ethCost * BigInt(penalty || 10_000)) / 10000n, }, }; }, {}); }); } export function preparePaymentPerPayerResult({ payerMap, senders, ethPriceInKIRO, }) { return senders.map((payer) => { const { largest, smallest } = payerMap.reduce((currentValues, pathData) => { if (!pathData[payer]) { return currentValues; } const currentLargestValue = currentValues.largest?.ethCost; const currentSmallestValue = currentValues.smallest?.ethCost; const value = pathData[payer]?.pureEthCost || 0n; if (value > currentLargestValue) { currentValues.largest = pathData[payer]; } if (currentSmallestValue == 0n || value < currentSmallestValue) { currentValues.smallest = pathData[payer]; } return currentValues; }, { largest: { payer, gas: 0n, ethCost: 0n, pureEthCost: 0n, }, smallest: { payer, gas: 0n, ethCost: 0n, pureEthCost: 0n, }, }); const largestKiroCost = getCostInKiro({ ethPriceInKIRO, ethCost: largest?.pureEthCost }); const smallestKiroCost = getCostInKiro({ ethPriceInKIRO, ethCost: smallest?.pureEthCost }); return { payer, largestPayment: { gas: largest.gas.toString(), tokenAmountInWei: largestKiroCost, nativeAmountInWei: largest.ethCost.toString(), tokenAmount: utils.formatEther(largestKiroCost), nativeAmount: utils.formatEther(largest.ethCost.toString()), }, smallestPayment: { gas: smallest.gas.toString(), tokenAmountInWei: smallestKiroCost, nativeAmountInWei: smallest.ethCost.toString(), tokenAmount: utils.formatEther(smallestKiroCost), nativeAmount: utils.formatEther(smallest.ethCost.toString()), }, }; }); } // function getPlugin({ FCT, fctID, index }: { FCT: BatchMultiSigCall; fctID: string; index: number }) { // const plugin = pluginCache.get(fctID + index); // if (plugin || plugin === null) return plugin; // try { // const plugin = FCT.getPlugin(index); // pluginCache.set(fctID + index, plugin); // return plugin; // } catch (e) { // pluginCache.set(fctID + index, null); // return null; // } // } //# sourceMappingURL=getPaymentPerPayer.js.map