@0xfacet/sdk
Version:
A toolkit for Facet blockchain integration.
175 lines (174 loc) • 7.35 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.bridgeAndCall = bridgeAndCall;
const solady_1 = require("solady");
const viem_1 = require("viem");
const accounts_1 = require("viem/accounts");
const actions_1 = require("viem/actions");
const chains_1 = require("viem/chains");
const constants_1 = require("../constants");
const utils_1 = require("../utils");
const chains_2 = require("./chains");
/**
* Bridges ETH from L1 to L2 and executes a contract call on Facet (L2).
*
* This function handles the complexities of bridging ETH from L1 to L2 and executing a contract call
* on the Facet network. It includes transaction simulation, gas estimation, and proper encoding of
* the bridged transaction data.
*
* @template chain - The chain type for the client
* @template account - The account type for the client
* @template abi - The ABI type for the contract
* @template functionName - The name of the function to call
* @template args - The arguments for the function call
* @template chainOverride - Optional chain override type
*
* @param client - The viem client instance used to interact with the blockchain
* @param parameters - The contract parameters, following viem's WriteContractParameters format
* @param ethValue - The amount of ETH to bridge (in wei)
* @param config - Optional configuration object for contract addresses
*
* @returns A promise that resolves to the Facet transaction hash
*
* @throws Will throw if no account is provided
* @throws Will throw if the network is unsupported
* @throws Will throw if contract addresses are not available
* @throws Will throw if the transaction simulation fails
* @throws Will throw and properly format any contract-related errors
*
* @example
* const hash = await bridgeAndCall(client, {
* address: '0x...',
* abi: contractAbi,
* functionName: 'someFunction',
* args: [arg1, arg2]
* }, parseEther('0.1'));
*/
async function bridgeAndCall(client, parameters, ethValue, config) {
const { abi, account: account_ = client.account, address, args, chain = client.chain, functionName, } = parameters;
if (typeof account_ === "undefined")
throw new Error("No account");
const account = account_ ? (0, accounts_1.parseAccount)(account_) : null;
const data = (0, viem_1.encodeFunctionData)({
abi,
args,
functionName,
});
try {
const { l1Network, l2Network } = (() => {
switch (chain?.id) {
case chains_1.mainnet.id:
case chains_2.facetMainnet.id:
return { l1Network: chains_1.mainnet, l2Network: chains_2.facetMainnet };
case chains_1.sepolia.id:
case chains_2.facetSepolia.id:
return { l1Network: chains_1.sepolia, l2Network: chains_2.facetSepolia };
default:
return { l1Network: undefined, l2Network: undefined };
}
})();
const { l1Contracts, l2Contracts } = (() => {
if (!l1Network) {
return { l1Contracts: undefined, l2Contracts: undefined };
}
const networkKey = l1Network.name === "Ethereum" ? "mainnet" : "sepolia";
return {
l1Contracts: {
...constants_1.CONTRACT_ADDRESSES.l1[networkKey],
...(config?.contractAddresses?.l1?.[networkKey] ?? {}),
},
l2Contracts: {
...constants_1.CONTRACT_ADDRESSES.l2[networkKey],
...(config?.contractAddresses?.l2?.[networkKey] ?? {}),
},
};
})();
if (!l2Network || !l1Network)
throw new Error("Unsupported network");
if (!l1Contracts || !l2Contracts)
throw new Error("Contract addresses not available");
const fctMintRate = await (0, utils_1.getFctMintRate)(l1Network.id);
const zippedData = solady_1.LibZip.cdCompress(data);
const gasLimit = 50000000n;
const encodedFacetFunctionData = (0, viem_1.encodeFunctionData)({
abi: [
{
type: "function",
name: "bridgeAndCall",
inputs: [
{ type: "address" },
{ type: "uint256" },
{ type: "address" },
{ type: "bytes" },
],
outputs: [],
stateMutability: "nonpayable",
},
],
functionName: "bridgeAndCall",
args: [account.address, ethValue, address, zippedData ?? "0x"],
});
const facetPublicClient = (0, viem_1.createPublicClient)({
chain: l2Network,
transport: (0, viem_1.http)(),
});
const simulationTxn = await facetPublicClient.request({
method: "debug_traceCall",
params: [
{
from: (0, utils_1.applyL1ToL2Alias)(l1Contracts.ETHER_BRIDGE_CONTRACT),
to: l2Contracts.WETH_CONTRACT,
data: encodedFacetFunctionData,
gas: (0, viem_1.toHex)(gasLimit),
value: "0x0",
},
"latest",
{
stateOverrides: {
[(0, utils_1.applyL1ToL2Alias)(l1Contracts.ETHER_BRIDGE_CONTRACT)]: {
balance: (0, viem_1.toHex)(viem_1.maxUint256),
},
},
},
],
});
if (simulationTxn.structLogs.find((log) => log.op === "REVERT")) {
throw Error("Failed to create transaction.");
}
const transactionData = [
(0, viem_1.toHex)(l2Network.id),
l2Contracts.WETH_CONTRACT,
"0x",
(0, viem_1.toHex)(gasLimit),
encodedFacetFunctionData,
"0x",
];
const encodedTransaction = (0, viem_1.concatHex)([(0, viem_1.toHex)(70), (0, viem_1.toRlp)(transactionData)]);
const inputCost = BigInt((0, viem_1.toBytes)(encodedTransaction).byteLength) * 8n;
const fctMintAmount = inputCost * fctMintRate;
const l1TxnData = (0, viem_1.encodeFunctionData)({
abi: constants_1.etherBridgeAbi,
args: [account.address, address, zippedData, gasLimit],
functionName: "bridgeAndCall",
});
const l1TransactionHash = await (0, actions_1.sendTransaction)(client, {
to: l1Contracts.ETHER_BRIDGE_CONTRACT,
data: l1TxnData,
value: ethValue,
chain,
account,
});
const facetTransactionHash = (0, utils_1.computeFacetTransactionHash)(l1TransactionHash, (0, utils_1.applyL1ToL2Alias)(l1Contracts.ETHER_BRIDGE_CONTRACT), l2Contracts.WETH_CONTRACT, 0n, encodedFacetFunctionData, gasLimit, fctMintAmount);
return facetTransactionHash;
}
catch (error) {
throw (0, viem_1.getContractError)(error, {
abi,
address,
args,
docsPath: "/docs/contract/writeContract",
functionName,
sender: account?.address,
});
}
}