UNPKG

@0xfacet/sdk

Version:

A toolkit for Facet blockchain integration.

175 lines (174 loc) 7.35 kB
"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, }); } }