UNPKG

@0xfacet/sdk

Version:

A toolkit for Facet blockchain integration.

172 lines (171 loc) 7.06 kB
import { LibZip } from "solady"; import { concatHex, createPublicClient, encodeFunctionData, getContractError, http, maxUint256, toBytes, toHex, toRlp, } from "viem"; import { parseAccount } from "viem/accounts"; import { sendTransaction } from "viem/actions"; import { mainnet, sepolia } from "viem/chains"; import { CONTRACT_ADDRESSES, etherBridgeAbi } from "../constants"; import { applyL1ToL2Alias, computeFacetTransactionHash, getFctMintRate, } from "../utils"; import { facetMainnet, facetSepolia } from "./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')); */ export 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_ ? parseAccount(account_) : null; const data = encodeFunctionData({ abi, args, functionName, }); try { const { l1Network, l2Network } = (() => { switch (chain?.id) { case mainnet.id: case facetMainnet.id: return { l1Network: mainnet, l2Network: facetMainnet }; case sepolia.id: case facetSepolia.id: return { l1Network: sepolia, l2Network: 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: { ...CONTRACT_ADDRESSES.l1[networkKey], ...(config?.contractAddresses?.l1?.[networkKey] ?? {}), }, l2Contracts: { ...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 getFctMintRate(l1Network.id); const zippedData = LibZip.cdCompress(data); const gasLimit = 50000000n; const encodedFacetFunctionData = 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 = createPublicClient({ chain: l2Network, transport: http(), }); const simulationTxn = await facetPublicClient.request({ method: "debug_traceCall", params: [ { from: applyL1ToL2Alias(l1Contracts.ETHER_BRIDGE_CONTRACT), to: l2Contracts.WETH_CONTRACT, data: encodedFacetFunctionData, gas: toHex(gasLimit), value: "0x0", }, "latest", { stateOverrides: { [applyL1ToL2Alias(l1Contracts.ETHER_BRIDGE_CONTRACT)]: { balance: toHex(maxUint256), }, }, }, ], }); if (simulationTxn.structLogs.find((log) => log.op === "REVERT")) { throw Error("Failed to create transaction."); } const transactionData = [ toHex(l2Network.id), l2Contracts.WETH_CONTRACT, "0x", toHex(gasLimit), encodedFacetFunctionData, "0x", ]; const encodedTransaction = concatHex([toHex(70), toRlp(transactionData)]); const inputCost = BigInt(toBytes(encodedTransaction).byteLength) * 8n; const fctMintAmount = inputCost * fctMintRate; const l1TxnData = encodeFunctionData({ abi: etherBridgeAbi, args: [account.address, address, zippedData, gasLimit], functionName: "bridgeAndCall", }); const l1TransactionHash = await sendTransaction(client, { to: l1Contracts.ETHER_BRIDGE_CONTRACT, data: l1TxnData, value: ethValue, chain, account, }); const facetTransactionHash = computeFacetTransactionHash(l1TransactionHash, applyL1ToL2Alias(l1Contracts.ETHER_BRIDGE_CONTRACT), l2Contracts.WETH_CONTRACT, 0n, encodedFacetFunctionData, gasLimit, fctMintAmount); return facetTransactionHash; } catch (error) { throw getContractError(error, { abi, address, args, docsPath: "/docs/contract/writeContract", functionName, sender: account?.address, }); } }