UNPKG

viem

Version:

TypeScript Interface for Ethereum

199 lines 8.22 kB
import { parseAbi } from 'abitype'; import { getTransaction } from '../../actions/public/getTransaction.js'; import { getTransactionReceipt } from '../../actions/public/getTransactionReceipt.js'; import { readContract } from '../../actions/public/readContract.js'; import { sendTransaction, } from '../../actions/wallet/sendTransaction.js'; import { zeroHash } from '../../constants/bytes.js'; import { AccountNotFoundError } from '../../errors/account.js'; import { ClientChainNotConfiguredError } from '../../errors/chain.js'; import { decodeAbiParameters, decodeFunctionData, encodeAbiParameters, encodeFunctionData, isAddressEqual, parseAccount, } from '../../utils/index.js'; import { bootloaderFormalAddress } from '../constants/address.js'; import { CannotClaimSuccessfulDepositError, L2BridgeNotFoundError, LogProofNotFoundError, } from '../errors/bridge.js'; import { undoL1ToL2Alias } from '../utils/bridge/undoL1ToL2Alias.js'; import { getDefaultBridgeAddresses } from './getDefaultBridgeAddresses.js'; import { getLogProof } from './getLogProof.js'; /** * Withdraws funds from the initiated deposit, which failed when finalizing on L2. * If the deposit L2 transaction has failed, it sends an L1 transaction calling `claimFailedDeposit` method of the * L1 bridge, which results in returning L1 tokens back to the depositor. * * @param client - Client to use * @param parameters - {@link ClaimFailedDepositParameters} * @returns hash - The [Transaction](https://viem.sh/docs/glossary/terms#transaction) hash. {@link ClaimFailedDepositReturnType} * * @example * import { createPublicClient, http } from 'viem' * import { privateKeyToAccount } from 'viem/accounts' * import { zksync, mainnet } from 'viem/chains' * import { claimFailedDeposit, publicActionsL2 } from 'viem/zksync' * * const client = createPublicClient({ * chain: mainnet, * transport: http(), * }) * * const clientL2 = createPublicClient({ * chain: zksync, * transport: http(), * }).extend(publicActionsL2()) * * const account = privateKeyToAccount('0x…') * * const hash = await claimFailedDeposit(client, { * client: clientL2, * account, * depositHash: <L2_HASH_OF_FAILED_DEPOSIT>, * }) * * @example Account Hoisting * import { createPublicClient, createWalletClient, http } from 'viem' * import { privateKeyToAccount } from 'viem/accounts' * import { zksync, mainnet } from 'viem/chains' * import { publicActionsL2 } from 'viem/zksync' * * const walletClient = createWalletClient({ * chain: mainnet, * transport: http(), * account: privateKeyToAccount('0x…'), * }) * * const clientL2 = createPublicClient({ * chain: zksync, * transport: http(), * }).extend(publicActionsL2()) * * const hash = await claimFailedDeposit(walletClient, { * client: clientL2, * depositHash: <L2_HASH_OF_FAILED_DEPOSIT>, * }) */ export async function claimFailedDeposit(client, parameters) { const { account: account_ = client.account, chain: chain_ = client.chain, client: l2Client, depositHash, ...rest } = parameters; const account = account_ ? parseAccount(account_) : client.account; if (!account) throw new AccountNotFoundError({ docsPath: '/docs/actions/wallet/sendTransaction', }); if (!l2Client.chain) throw new ClientChainNotConfiguredError(); const receipt = (await getTransactionReceipt(l2Client, { hash: depositHash })); const successL2ToL1LogIndex = receipt.l2ToL1Logs.findIndex((l2ToL1log) => isAddressEqual(l2ToL1log.sender, bootloaderFormalAddress) && l2ToL1log.key === depositHash); const successL2ToL1Log = receipt.l2ToL1Logs[successL2ToL1LogIndex]; if (successL2ToL1Log?.value !== zeroHash) throw new CannotClaimSuccessfulDepositError({ hash: depositHash }); const tx = await getTransaction(l2Client, { hash: depositHash }); // Undo the aliasing, since the Mailbox contract set it as for contract address. const l1BridgeAddress = undoL1ToL2Alias(receipt.from); const l2BridgeAddress = receipt.to; if (!l2BridgeAddress) throw new L2BridgeNotFoundError(); const l1NativeTokenVault = (await getBridgeAddresses(client, l2Client)) .l1NativeTokenVault; let depositSender; let assetId; let assetData; try { const { args } = decodeFunctionData({ abi: parseAbi([ 'function finalizeDeposit(address _l1Sender, address _l2Receiver, address _l1Token, uint256 _amount, bytes _data)', ]), data: tx.input, }); assetData = encodeAbiParameters([{ type: 'uint256' }, { type: 'address' }, { type: 'address' }], [args[3], args[1], args[2]]); assetId = await readContract(client, { address: l1NativeTokenVault, abi: parseAbi(['function assetId(address token) view returns (bytes32)']), functionName: 'assetId', args: [args[2]], }); depositSender = args[0]; if (assetId === zeroHash) throw new Error(`Token ${args[2]} not registered in NTV`); } catch (_e) { const { args } = decodeFunctionData({ abi: parseAbi([ 'function finalizeDeposit(uint256 _chainId, bytes32 _assetId, bytes _transferData)', ]), data: tx.input, }); assetId = args[1]; const transferData = args[2]; const l1TokenAddress = await readContract(client, { address: l1NativeTokenVault, abi: parseAbi([ 'function tokenAddress(bytes32 assetId) view returns (address)', ]), functionName: 'tokenAddress', args: [assetId], }); const transferDataDecoded = decodeAbiParameters([ { type: 'address' }, { type: 'address' }, { type: 'address' }, { type: 'uint256' }, { type: 'bytes' }, ], transferData); assetData = encodeAbiParameters([{ type: 'uint256' }, { type: 'address' }, { type: 'address' }], [transferDataDecoded[3], transferDataDecoded[1], l1TokenAddress]); depositSender = transferDataDecoded[0]; } const proof = await getLogProof(l2Client, { txHash: depositHash, index: successL2ToL1LogIndex, }); if (!proof) throw new LogProofNotFoundError({ hash: depositHash, index: successL2ToL1LogIndex, }); const data = encodeFunctionData({ abi: parseAbi([ 'function bridgeRecoverFailedTransfer(uint256 _chainId, address _depositSender, bytes32 _assetId, bytes _assetData, bytes32 _l2TxHash, uint256 _l2BatchNumber, uint256 _l2MessageIndex, uint16 _l2TxNumberInBatch, bytes32[] _merkleProof)', ]), functionName: 'bridgeRecoverFailedTransfer', args: [ BigInt(l2Client.chain.id), depositSender, assetId, assetData, depositHash, receipt.l1BatchNumber, BigInt(proof.id), Number(receipt.l1BatchTxIndex), proof.proof, ], }); return await sendTransaction(client, { chain: chain_, account, to: l1BridgeAddress, data, ...rest, }); } async function getBridgeAddresses(client, l2Client) { const addresses = await getDefaultBridgeAddresses(l2Client); let l1Nullifier = addresses.l1Nullifier; let l1NativeTokenVault = addresses.l1NativeTokenVault; if (!l1Nullifier) l1Nullifier = await readContract(client, { address: addresses.sharedL1, abi: parseAbi(['function L1_NULLIFIER() view returns (address)']), functionName: 'L1_NULLIFIER', args: [], }); if (!l1NativeTokenVault) l1NativeTokenVault = await readContract(client, { address: addresses.sharedL1, abi: parseAbi(['function nativeTokenVault() view returns (address)']), functionName: 'nativeTokenVault', args: [], }); return { ...addresses, l1Nullifier, l1NativeTokenVault, }; } //# sourceMappingURL=claimFailedDeposit.js.map