@biconomy/abstractjs
Version:
SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.
154 lines • 6.78 kB
JavaScript
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