Bridge and Withdraw
Interface
/**
* Parameters for preparing a cross-chain bridge and withdrawal transaction
* @interface PrepareBridgeAndWithdrawTransactionDataParams
* @extends {Omit<PrepareWithdrawTransactionDataParams, 'chainId'>}
* @property {ChainId} sourceChainId - Chain ID where shares currently exist
* @property {ChainId} destinationChainId - Chain ID where assets will be withdrawn
* @property {number} offerAmount - Amount of vault shares to withdraw
* @property {number} [deadline] - Unix timestamp when the request expires (optional)
* @property {number} [slippage] - Maximum acceptable slippage percentage (optional)
* @property {Address} userAddress - Ethereum address of the user making the withdrawal
* @property {VaultKey} vaultKey - Unique identifier for the vault
* @property {TokenKey} wantTokenSymbol - Symbol of the token the user wants to receive
*/
interface PrepareBridgeAndWithdrawTransactionDataParams
extends Omit<PrepareWithdrawTransactionDataParams, 'chainId'> {
sourceChainId: ChainId;
destinationChainId: ChainId;
}
/**
* Combined result containing both bridge and withdraw transaction data
* @interface BridgeAndWithdrawResult
*/
interface BridgeAndWithdrawTransactionData {
bridgeTransactionData: BridgeTransactionData; // Data for bridging shares to destination chain
withdrawTransactionData: WithdrawTransactionData; // Data for withdraw request on destination chain
}
Function Overview
import { prepareWithdrawAndBridge, TokenKeys } from '@molecularlabs/nucleus-frontend';
const COOLVAULT_VAULT_KEY = VaultKeys.COOLVAULT;
// Prepare withdrawal and bridge transaction data
const withdrawAndBridgeData = await prepareWithdrawAndBridge({
vaultKey: COOLVAULT_VAULT_KEY,
sourceChainId: 288, // Boba network
destinationChainId: 1, // Ethereum mainnet
userAddress: '0x1234...',
wantAsset: TokenKey.WETH,
redeemAmount: BigInt('1000000000000000000'), // 1 token
previewFee: BigInt('100000000000000'), // Bridge fee (get this from getPreviewFee)
deadline: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now (optional)
slippage: 0.01, // 1% slippage (optional)
});
const {
bridgeData, // Bridge configuration data
withdrawRequestData, // Withdraw request details
} = withdrawAndBridgeData;
// Further destructure the withdrawal request data
const {
abi, // AtomicQueueAbi
address, // Contract address for the atomic queue
functionName, // 'updateAtomicRequest'
args, // [redeemTokenAddress, wantAssetAddress, userRequest]
chainId, // Chain ID for the transaction
} = withdrawRequestData;
Viem
import { createPublicClient, http, createWalletClient, custom } from 'viem';
import { mainnet, arbitrum } from 'viem/chains';
import {
prepareBridgeAndWithdrawTransactionData,
VaultKeys,
TokenKey
} from '@molecularlabs/nucleus-sdk';
// Example using Viem with MetaMask
const publicClient = createPublicClient({
chain: arbitrum,
transport: http()
});
const walletClient = createWalletClient({
chain: arbitrum,
transport: custom(window.ethereum)
});
const COOLVAULT_VAULT_KEY = VaultKeys.COOLVAULT;
async function bridgeAndWithdrawFromVault() {
try {
// Get user's address
const [address] = await walletClient.requestAddresses();
// Prepare bridge and withdraw data
const bridgeAndWithdrawData = await prepareBridgeAndWithdrawTransactionData({
vaultKey: COOLVAULT_VAULT_KEY,
sourceChainId: arbitrum.id, // Arbitrum (where shares are)
destinationChainId: mainnet.id, // Ethereum Mainnet (where to withdraw)
userAddress: address,
wantTokenSymbol: TokenKey.WETH, // Token to receive
offerAmount: 1.5, // Amount of vault shares to withdraw
deadline: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
slippage: 0.005 // 0.5% slippage
});
// First, send the bridge transaction
const bridgeHash = await walletClient.writeContract({
...bridgeAndWithdrawData.bridgeTransactionData,
value: bridgeAndWithdrawData.bridgeTransactionData.value // Include bridge fee
});
// Wait for bridge transaction
const bridgeReceipt = await publicClient.waitForTransactionReceipt({
hash: bridgeHash
});
// Switch to destination chain for withdraw
const withdrawClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum)
});
// Send the withdraw transaction
const withdrawHash = await withdrawClient.writeContract({
...bridgeAndWithdrawData.withdrawTransactionData
});
// Wait for withdraw transaction
const withdrawReceipt = await publicClient.waitForTransactionReceipt({
hash: withdrawHash
});
} catch (error) {
console.error('Bridge and withdraw failed:', error);
}
}
Wagmi Example
import { useState } from 'react';
import { useAccount, useContractWrite, usePrepareContractWrite, useWaitForTransaction, useNetwork, useSwitchNetwork } from 'wagmi';
import { useQuery } from '@tanstack/react-query';
import { mainnet, arbitrum } from 'viem/chains';
import {
prepareBridgeAndWithdrawTransactionData,
VaultKey,
TokenKey
} from '@molecularlabs/nucleus-sdk';
const VAULT_KEY = VaultKey.BOBAETH;
function BridgeAndWithdrawForm() {
const { address } = useAccount();
const { chain } = useNetwork();
const { switchNetwork } = useSwitchNetwork();
const [amount, setAmount] = useState('');
// Fetch bridge and withdraw data
const { data: transactionData, isLoading: isPreparingTransactions, error: prepareError } = useQuery({
queryKey: ['prepareBridgeAndWithdraw', VAULT_KEY, amount, address],
queryFn: async () => {
if (!amount || !address) return null;
return prepareBridgeAndWithdrawTransactionData({
vaultKey: VAULT_KEY,
sourceChainId: arbitrum.id, // Arbitrum (where shares are)
destinationChainId: mainnet.id, // Ethereum Mainnet (where to withdraw)
userAddress: address,
wantTokenSymbol: TokenKey.WETH,
offerAmount: Number(amount),
deadline: Math.floor(Date.now() / 1000) + 3600, // 1 hour
slippage: 0.005 // 0.5%
});
},
enabled: Boolean(amount && address),
});
// Prepare the bridge contract write
const { config: bridgeConfig, error: prepareBridgeError } = usePrepareContractWrite({
address: transactionData?.bridgeTransactionData.address,
abi: transactionData?.bridgeTransactionData.abi,
functionName: transactionData?.bridgeTransactionData.functionName,
args: transactionData?.bridgeTransactionData.args,
value: transactionData?.bridgeTransactionData.value,
chainId: transactionData?.bridgeTransactionData.chainId,
enabled: Boolean(transactionData) && chain?.id === arbitrum.id,
});
// Handle the bridge contract write
const { write: writeBridge, data: bridgeData, error: bridgeError } = useContractWrite(bridgeConfig);
// Handle bridge transaction status
const { isLoading: isBridgePending, isSuccess: isBridgeSuccess } = useWaitForTransaction({
hash: bridgeData?.hash,
});
// Prepare the withdraw contract write
const { config: withdrawConfig, error: prepareWithdrawError } = usePrepareContractWrite({
address: transactionData?.withdrawTransactionData.address,
abi: transactionData?.withdrawTransactionData.abi,
functionName: transactionData?.withdrawTransactionData.functionName,
args: transactionData?.withdrawTransactionData.args,
chainId: transactionData?.withdrawTransactionData.chainId,
enabled: Boolean(transactionData) && isBridgeSuccess && chain?.id === mainnet.id,
});
// Handle the withdraw contract write
const { write: writeWithdraw, data: withdrawData, error: withdrawError } = useContractWrite(withdrawConfig);
// Handle withdraw transaction status
const { isLoading: isWithdrawPending, isSuccess: isWithdrawSuccess } = useWaitForTransaction({
hash: withdrawData?.hash,
});
// Combine all errors
const error = prepareError || prepareBridgeError || prepareWithdrawError || bridgeError || withdrawError;
// Handle the full bridge and withdraw process
const handleBridgeAndWithdraw = async () => {
if (!transactionData) return;
// Ensure we're on the source chain for bridging
if (chain?.id !== arbitrum.id) {
await switchNetwork?.(arbitrum.id);
return;
}
// Execute bridge transaction
writeBridge?.();
};
// Handle withdraw after bridge success
const handleWithdraw = async () => {
if (!transactionData || !isBridgeSuccess) return;
// Switch to destination chain for withdrawal
if (chain?.id !== mainnet.id) {
await switchNetwork?.(mainnet.id);
return;
}
// Execute withdraw transaction
writeWithdraw?.();
};
return (
<div className="max-w-md mx-auto p-6 bg-white rounded-xl shadow-lg">
<h2 className="text-2xl font-bold mb-6">Bridge and Withdraw WETH</h2>
{/* Amount Input */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
Amount (Vault Shares)
</label>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0.0"
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
disabled={isBridgePending || isWithdrawPending}
/>
</div>
{/* Transaction Details */}
<div className="mb-6 text-sm text-gray-600">
<p>From: Arbitrum</p>
<p>To: Ethereum Mainnet</p>
<p>Want Token: WETH</p>
<p>Slippage: 0.5%</p>
</div>
{/* Bridge Button */}
<button
onClick={handleBridgeAndWithdraw}
disabled={!writeBridge || isPreparingTransactions || isBridgePending}
className={`w-full py-3 px-4 rounded-lg font-medium text-white mb-4
${(!writeBridge || isPreparingTransactions || isBridgePending)
? 'bg-gray-400'
: 'bg-blue-600 hover:bg-blue-700'
} transition-colors`}
>
{isPreparingTransactions ? 'Preparing...' : isBridgePending ? 'Bridging...' : 'Bridge Shares'}
</button>
{/* Withdraw Button */}
<button
onClick={handleWithdraw}
disabled={!writeWithdraw || !isBridgeSuccess || isWithdrawPending}
className={`w-full py-3 px-4 rounded-lg font-medium text-white
${(!writeWithdraw || !isBridgeSuccess || isWithdrawPending)
? 'bg-gray-400'
: 'bg-green-600 hover:bg-green-700'
} transition-colors`}
>
{isWithdrawPending ? 'Withdrawing...' : 'Withdraw'}
</button>
{/* Status Messages */}
{isBridgeSuccess && !isWithdrawSuccess && (
<div className="mt-4 p-4 bg-blue-100 text-blue-700 rounded-lg">
Bridge successful! Ready to withdraw.
</div>
)}
{isWithdrawSuccess && (
<div className="mt-4 p-4 bg-green-100 text-green-700 rounded-lg">
Withdrawal successful! Your tokens have been received.
</div>
)}
{error && (
<div className="mt-4 p-4 bg-red-100 text-red-700 rounded-lg">
{error.message}
</div>
)}
{/* Transaction Progress */}
{(isBridgePending || isWithdrawPending) && (
<div className="mt-4">
<div className="h-2 w-full bg-gray-200 rounded-full overflow-hidden">
<div className="h-full bg-blue-600 animate-pulse rounded-full" />
</div>
<p className="text-sm text-gray-600 mt-2">
{isBridgePending ? 'Bridge in progress...' : 'Withdrawal in progress...'}
</p>
</div>
)}
</div>
);
}
export default BridgeAndWithdrawForm;
Last updated