UNPKG

@biconomy/abstractjs

Version:

SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.

154 lines 6.78 kB
import { toAcrossPlugin } from "../utils/toAcrossPlugin.js"; import { queryBridge } from "./queryBridge.js"; /** * Makes sure that the user has enough funds on the selected chain before filling the * supertransaction. Bridges funds from other chains if needed. * * @param params - {@link MultichainBridgingParams} Configuration for the bridge operation * @param params.depositor - The address initiating the bridge (sender) * @param params.recipient - The address receiving the bridged tokens (destination) * @param params.toChainId - The numeric chain ID of the destination chain * @param params.unifiedBalance - Current token balances across chains * @param params.amount - The amount to bridge * @param params.bridgingPlugins - Optional array of bridging plugins (defaults to Across) * @param params.feeData - Optional fee configuration * @param params.mode - The mode of the bridge operation, defaults to "DEBIT". In optimistic mode, the bridging instructions are returned without preexisting balance checks. * * @returns Promise resolving to {@link BridgingInstructions} containing all necessary operations * * @throws Error if insufficient balance is available for bridging * @throws Error if chain configuration is missing for any deployment * * @example * const bridgeInstructions = await buildBridgeInstructions({ * depositor: myAddress, * recipient: recipientAddress, * amount: BigInt("1000000"), // 1 USDC * toChainId: optimism.id, * unifiedBalance: myTokenBalance, * bridgingPlugins: [acrossPlugin], * feeData: { * txFeeChainId: 1, * txFeeAmount: BigInt("100000") * } * }); */ export const buildBridgeInstructions = async (params) => { const { depositor, recipient, amount: targetAmount, toChainId, unifiedBalance, bridgingPlugins = [toAcrossPlugin()], feeData, mode = "DEBIT", metadata, lowerBoundTimestamp, upperBoundTimestamp, executionSimulationRetryDelay, simulationOverrides } = params; const tokenMapping = { on: (chainId) => unifiedBalance.mcToken.deployments.get(chainId) || "0x", deployments: Array.from(unifiedBalance.mcToken.deployments.entries(), ([chainId, address]) => ({ chainId, address })) }; // Get current balance on destination chain const destinationBalance = unifiedBalance.breakdown.find((b) => b.chainId === toChainId)?.balance || 0n; // If we have enough on destination, no bridging needed if (destinationBalance >= targetAmount) { return { instructions: [], meta: { bridgingInstructions: [], totalAvailableOnDestination: destinationBalance } }; } // Calculate how much we need to bridge const amountToBridge = targetAmount - destinationBalance; // Get available balances from source chains const sourceBalances = unifiedBalance.breakdown .filter((balance) => balance.chainId !== toChainId) .map((balance_) => { // If we are in optimistic mode, we need to retrieve instructions for the bridging action regardless of the balance const balancePerChain = mode === "DEBIT" ? balance_ : { ...balance_, balance: targetAmount }; // If this is the fee payment chain, adjust available balance const isFeeChain = feeData && feeData.txFeeChainId === balancePerChain.chainId; const availableBalance = isFeeChain && "txFeeAmount" in feeData ? balancePerChain.balance > feeData.txFeeAmount ? balancePerChain.balance - feeData.txFeeAmount : 0n : balancePerChain.balance; return { chainId: balancePerChain.chainId, balance: availableBalance }; }) .filter((balance) => balance.balance > 0n); // Query all possible routes const bridgeQueries = sourceBalances.flatMap((source) => { return bridgingPlugins.map((plugin) => queryBridge({ depositor, recipient, fromChainId: source.chainId, toChainId, plugin, amount: source.balance, tokenMapping })); }); const bridgeResults = (await Promise.all(bridgeQueries)) .filter((result) => result !== null) // Sort by received amount relative to sent amount .sort((a, b) => Number((b.receivedAtDestination * 10000n) / b.amount) - Number((a.receivedAtDestination * 10000n) / a.amount)); // Build instructions by taking from best routes until we have enough const { bridgingInstructions, instructions, totalBridged, remainingNeeded } = bridgeResults.reduce((acc, result) => { if (acc.remainingNeeded <= 0n) return acc; const amountToTake = result.amount >= acc.remainingNeeded ? acc.remainingNeeded : result.amount; const receivedFromRoute = (result.receivedAtDestination * amountToTake) / result.amount; // If no metadata from plugin ? Custom metadata will be added const customMetadata = [ { type: "CUSTOM", chainId: result.userOp.chainId, description: "Custom Bridging on-chain action" } ]; const instruction = { ...result.userOp, metadata: metadata || result.userOp.metadata || customMetadata, lowerBoundTimestamp, upperBoundTimestamp, executionSimulationRetryDelay, simulationOverrides }; return { bridgingInstructions: [ ...acc.bridgingInstructions, { userOp: instruction, receivedAtDestination: receivedFromRoute, bridgingDurationExpectedMs: result.bridgingDurationExpectedMs } ], instructions: [...acc.instructions, instruction], totalBridged: acc.totalBridged + receivedFromRoute, remainingNeeded: acc.remainingNeeded - amountToTake }; }, { bridgingInstructions: [], instructions: [], totalBridged: 0n, remainingNeeded: amountToBridge }); // Check if we got enough if (remainingNeeded > 0n) { throw new Error(`Insufficient balance for bridging: Required: ${targetAmount.toString()} Available to bridge: ${totalBridged.toString()} Shortfall: ${remainingNeeded.toString()}`); } return { instructions, meta: { bridgingInstructions, totalAvailableOnDestination: destinationBalance + totalBridged } }; }; export default buildBridgeInstructions; //# sourceMappingURL=buildBridgeInstructions.js.map